Files
rog_app/lib/main.dart

516 lines
19 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:io';
//import 'dart:convert';
//import 'dart:developer';
import 'package:rogapp/model/gps_data.dart';
import 'package:rogapp/pages/home/home_page.dart';
import 'package:rogapp/utils/database_gps.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
//import 'package:vm_service/vm_service.dart';
//import 'package:dart_vm_info/dart_vm_info.dart';
import 'package:rogapp/pages/settings/settings_controller.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_binding.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/routes/app_pages.dart';
import 'package:rogapp/utils/location_controller.dart';
import 'package:rogapp/utils/string_values.dart';
import 'package:shared_preferences/shared_preferences.dart';
// import 'package:is_lock_screen/is_lock_screen.dart';
import 'package:rogapp/services/device_info_service.dart';
import 'package:rogapp/services/error_service.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
//import 'dart:async';
//import 'package:get/get.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';
import 'pages/permission/permission.dart';
Map<String, dynamic> deviceInfo = {};
/*
void saveGameState() async {
DestinationController destinationController =
Get.find<DestinationController>();
SharedPreferences pref = await SharedPreferences.getInstance();
pref.setBool("is_in_rog", destinationController.isInRog.value);
pref.setBool(
"rogaining_counted", destinationController.rogainingCounted.value);
pref.setBool("ready_for_goal", DestinationController.ready_for_goal);
}
*/
// 現在のユーザーのIDも一緒に保存するようにします。
void saveGameState() async {
DestinationController destinationController =
Get.find<DestinationController>();
IndexController indexController = Get.find<IndexController>();
SharedPreferences pref = await SharedPreferences.getInstance();
debugPrint("indexController.currentUser = ${indexController.currentUser}");
if(indexController.currentUser.isNotEmpty) {
pref.setInt("user_id", indexController.currentUser[0]["user"]["id"]);
}else{
debugPrint("User is empty....");
}
pref.setBool("is_in_rog", destinationController.isInRog.value);
pref.setBool(
"rogaining_counted", destinationController.rogainingCounted.value);
pref.setBool("ready_for_goal", DestinationController.ready_for_goal);
}
/*
void restoreGame() async {
SharedPreferences pref = await SharedPreferences.getInstance();
DestinationController destinationController =
Get.find<DestinationController>();
destinationController.skipGps = false;
destinationController.isInRog.value = pref.getBool("is_in_rog") ?? false;
destinationController.rogainingCounted.value =
pref.getBool("rogaining_counted") ?? false;
DestinationController.ready_for_goal =
pref.getBool("ready_for_goal") ?? false;
//print(
// "--restored -- destinationController.isInRog.value ${pref.getBool("is_in_rog")} -- ${pref.getBool("rogaining_counted")}");
}
*/
void restoreGame() async {
SharedPreferences pref = await SharedPreferences.getInstance();
IndexController indexController = Get.find<IndexController>();
int? savedUserId = pref.getInt("user_id");
//int? currUserId = indexController.currentUser[0]['user']['id'];
//debugPrint("savedUserId=${savedUserId}, currentUser=${currUserId}");
if (indexController.currentUser.isNotEmpty &&
indexController.currentUser[0]["user"]["id"] == savedUserId) {
DestinationController destinationController =
Get.find<DestinationController>();
destinationController.skipGps = false;
destinationController.isInRog.value = pref.getBool("is_in_rog") ?? false;
destinationController.rogainingCounted.value =
pref.getBool("rogaining_counted") ?? false;
DestinationController.ready_for_goal =
pref.getBool("ready_for_goal") ?? false;
}
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterMapTileCaching.initialise();
final StoreDirectory instanceA = FMTC.instance('OpenStreetMap (A)');
await instanceA.manage.createAsync();
await instanceA.metadata.addAsync(
key: 'sourceURL',
value: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
);
await instanceA.metadata.addAsync(
key: 'validDuration',
value: '14',
);
await instanceA.metadata.addAsync(
key: 'behaviour',
value: 'cacheFirst',
);
deviceInfo = await DeviceInfoService.getDeviceInfo();
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
Get.log('Flutter error: ${details.exception}');
Get.log('Stack trace: ${details.stack}');
ErrorService.reportError(details.exception, details.stack ?? StackTrace.current, deviceInfo);
};
//Get.put(LocationController());
//await PermissionController.checkAndRequestPermissions();
//requestLocationPermission();
// startMemoryMonitoring(); // 2024-4-8 Akira: メモリ使用量のチェックを開始 See #2810
Get.put(SettingsController()); // これを追加
/*
runZonedGuarded(() {
runApp(const ProviderScope(child: MyApp()));
}, (error, stackTrace) {
ErrorService.reportError(error, stackTrace, deviceInfo);
});
*/
runApp(const ProviderScope(child: MyApp()));
//runApp(HomePage()); // MyApp()からHomePage()に変更
//runApp(const MyApp());
}
/*
Future<void> requestLocationPermission() async {
try {
final status = await Permission.locationAlways.request();
if (status == PermissionStatus.granted) {
print('Location permission granted');
} else {
print('Location permission denied');
//await showLocationPermissionDeniedDialog(); // 追加
}
} catch (e) {
print('Error requesting location permission: $e');
}
}
*/
// メモリ使用量の解説https://qiita.com/hukusuke1007/items/e4e987836412e9bc73b9
/*
// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810
// startMemoryMonitoring関数が5分ごとに呼び出され、メモリ使用量をチェックします。
// メモリ使用量が閾値ここでは500MBを超えた場合、エラーメッセージとスタックトレースをレポートします。
//
void startMemoryMonitoring() {
const threshold = 500 * 1024 * 1024; // 500MB
// メモリ使用量情報を取得
final memoryUsage = MemoryUsage.fromJson(DartVMInfo.getAllocationProfile());
if (memoryUsage.heapUsage > threshold) {
final now = DateTime.now().toIso8601String();
final message = 'High memory usage detected at $now: ${memoryUsage.heapUsage} bytes';
print(message);
reportError(message, StackTrace.current);
showMemoryWarningDialog();
}
Timer(const Duration(minutes: 5), startMemoryMonitoring);
}
class MemoryUsage {
final int heapUsage;
MemoryUsage({required this.heapUsage});
factory MemoryUsage.fromJson(Map<String, dynamic> json) {
return MemoryUsage(
heapUsage: json['heapUsage'] as int,
);
}
}
*/
// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810
// reportError関数でエラーレポートを送信します。具体的な実装は、利用するエラー報告サービスによって異なります。
void reportError(String message, StackTrace stackTrace) async {
// エラーレポートの送信処理を実装
// 例: SentryやFirebase Crashlyticsなどのエラー報告サービスを利用
print("ReportError : ${message} . stacktrace : ${stackTrace}");
}
// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810
// showMemoryWarningDialog関数で、メモリ使用量が高い場合にユーザーに警告ダイアログを表示します。
//
void showMemoryWarningDialog() {
if (Get.context != null) {
showDialog(
context: Get.context!,
builder: (context) => AlertDialog(
title: const Text('メモリ使用量の警告'),
content: const Text('アプリのメモリ使用量が高くなっています。アプリを再起動することをお勧めします。'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('OK'),
),
],
),
);
}
}
StreamSubscription<Position>? positionStream;
bool background=false;
DateTime lastGPSCollectedTime=DateTime.now();
String team_name="";
String event_code="";
Future<void> startBackgroundTracking() async {
if (Platform.isIOS && background==false) {
final IndexController indexController = Get.find<IndexController>();
if(indexController.currentUser.length>0) {
team_name = indexController.currentUser[0]["user"]['team_name'];
event_code = indexController.currentUser[0]["user"]["event_code"];
}
background = true;
debugPrint("バックグラウンド処理を開始しました。");
final LocationSettings locationSettings = LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 100,
);
try {
positionStream = Geolocator.getPositionStream(locationSettings: locationSettings)
.listen((Position? position) async {
if (position != null) {
final lat = position.latitude;
final lng = position.longitude;
//final timestamp = DateTime.now();
final accuracy = position.accuracy;
// GPS信号強度がlowの場合はスキップ
if (accuracy > 100) {
debugPrint("GPS signal strength is low. Skipping data saving.");
return;
}
Duration difference = lastGPSCollectedTime.difference(DateTime.now())
.abs();
// 最後にGPS信号を取得した時刻から10秒以上経過、かつ10m以上経過普通に歩くスピード
//debugPrint("時間差:${difference}");
if (difference.inSeconds >= 10 ) {
debugPrint("バックグラウンドでのGPS取得時の処理(10secおき) count=${difference.inSeconds}, time=${DateTime.now()}");
// DBにGPSデータを保存 pages/destination/destination_controller.dart
await addGPStoDB(lat, lng);
lastGPSCollectedTime = DateTime.now();
}
}
}, onError: (error) {
if (error is LocationServiceDisabledException) {
print('Location services are disabled');
} else if (error is PermissionDeniedException) {
print('Location permissions are denied');
} else {
print('Location Error: $error');
}
});
} catch (e) {
print('Error starting background tracking: $e');
}
}else if (Platform.isAndroid && background == false) {
background = true;
debugPrint("バックグラウンド処理を開始しました。");
try {
// 位置情報の権限が許可されているかを確認
await PermissionController.checkAndRequestPermissions();
}catch(e){
print('Error starting background tracking: $e');
}
}
}
Future<void> addGPStoDB(double la, double ln) async {
//debugPrint("in addGPStoDB ${indexController.currentUser}");
GpsDatabaseHelper db = GpsDatabaseHelper.instance;
try {
GpsData gps_data = GpsData(
id: 0,
team_name: team_name,
event_code: event_code,
lat: la,
lon: ln,
is_checkin: 0,
created_at: DateTime.now().millisecondsSinceEpoch);
var res = await db.insertGps(gps_data);
//debugPrint("バックグラウンドでのGPS保存");
} catch (err) {
print("errr ready gps ${err}");
return;
}
}
Future<void> stopBackgroundTracking() async {
if (Platform.isIOS && background==true) {
background=false;
debugPrint("バックグラウンド処理:停止しました。");
await positionStream?.cancel();
positionStream = null;
}else if(Platform.isAndroid && background==true){
background=false;
debugPrint("バックグラウンド処理:停止しました。");
const platform = MethodChannel('location');
try {
await platform.invokeMethod('stopLocationService');
} on PlatformException catch (e) {
print("Failed to stop location service: '${e.message}'.");
}
}
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
// This widget is the root of your application.
@override
void initState() {
super.initState();
if (context.mounted) {
restoreGame();
}
WidgetsBinding.instance.addObserver(this);
/*
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
*/
debugPrint("Start MyAppState...");
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
// void saveGameState() async {
// DestinationController destinationController = Get.find<DestinationController>();
// SharedPreferences pref = await SharedPreferences.getInstance();
// pref.setBool("is_in_rog", destinationController.is_in_rog.value);
// pref.setBool("rogaining_counted", destinationController.rogaining_counted.value);
// }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
LocationController locationController = Get.find<LocationController>();
DestinationController destinationController = Get.find<DestinationController>();
//DestinationController destinationController =
// Get.find<DestinationController>();
switch (state) {
case AppLifecycleState.resumed:
// バックグラウンド処理を停止
if (Platform.isIOS && destinationController.isRunningBackgroundGPS) {
// Foreground に戻った時の処理
debugPrint(" ==(Status Changed)==> RESUMED. フォアグラウンドに戻りました");
locationController.resumePositionStream();
//print("RESUMED");
restoreGame();
stopBackgroundTracking();
destinationController.isRunningBackgroundGPS=false;
destinationController.restartGPS();
} else if (Platform.isAndroid ) {
if( destinationController.isRunningBackgroundGPS ){
const platform = MethodChannel('location');
platform.invokeMethod('stopLocationService');
destinationController.isRunningBackgroundGPS=false;
destinationController.restartGPS();
debugPrint("stopped android location service..");
}
debugPrint("==(Status Changed)==> RESUMED. android フォアグラウンドに戻りました");
locationController.resumePositionStream();
//print("RESUMED");
restoreGame();
}else{
debugPrint("==(Status Changed)==> RESUMED 不明状態");
}
break;
case AppLifecycleState.inactive:
// アプリが非アクティブになったときに発生します。
if (Platform.isIOS && !destinationController.isRunningBackgroundGPS) { // iOSはバックグラウンドでもフロントの処理が生きている。
// これは、別のアプリやシステムのオーバーレイ(着信通話やアラームなど)によって一時的に中断された状態です。
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
//locationController.resumePositionStream();
// 追加: フロントエンドのGPS信号のlistenを停止
locationController.stopPositionStream();
destinationController.isRunningBackgroundGPS=true;
startBackgroundTracking();
}else if(Platform.isAndroid && !destinationController.isRunningBackgroundGPS){
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
}else{
debugPrint("==(Status Changed)==> INACTIVE 不明状態");
}
saveGameState();
break;
case AppLifecycleState.paused:
// バックグラウンドに移行したときの処理
//locationController.resumePositionStream();
debugPrint(" ==(Status Changed)==> PAUSED. バックグラウンド処理。");
if (Platform.isIOS && !destinationController.isRunningBackgroundGPS) {
debugPrint("iOS already running background GPS processing when it's inactive");
} else if(Platform.isAndroid && !destinationController.isRunningBackgroundGPS) {
debugPrint(
" ==(Status Changed)==> PAUSED. Android バックグラウンド処理。");
locationController.stopPositionStream();
const platform = MethodChannel('location');
platform.invokeMethod('startLocationService');
//platform.invokeMethod('stopLocationService');
destinationController.isRunningBackgroundGPS = true;
//startBackgroundTracking();
}
saveGameState();
break;
case AppLifecycleState.detached:
// アプリが終了する直前に発生します。この状態では、アプリはメモリから解放される予定です。
//locationController.resumePositionStream();
debugPrint(" ==(Status Changed)==> DETACHED アプリは終了します。");
saveGameState();
break;
case AppLifecycleState.hidden:
// Web用の特殊な状態で、モバイルアプリでは発生しません。
//locationController.resumePositionStream();
debugPrint(" ==(Status Changed)==> Hidden アプリが隠れた");
saveGameState();
break;
}
}
@override
Widget build(BuildContext context) {
return GetMaterialApp(
translations: StringValues(),
locale: const Locale('ja', 'JP'),
//locale: const Locale('en', 'US'),
fallbackLocale: const Locale('en', 'US'),
title: 'ROGAINING',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 36, 135, 221)),
useMaterial3: true,
),
debugShowCheckedModeBanner: false,
defaultTransition: Transition.cupertino,
opaqueRoute: Get.isOpaqueRouteDefault,
popGesture: Get.isPopGestureEnable,
transitionDuration: const Duration(milliseconds: 230),
initialBinding: IndexBinding(), //HomeBinding(),
initialRoute: AppPages.PERMISSION,
getPages: AppPages.routes,
enableLog: true,
);
}
}