From 7a97127a19640f08d4649f1152c89aed7313eb78 Mon Sep 17 00:00:00 2001 From: Akira Date: Thu, 2 May 2024 11:51:52 +0900 Subject: [PATCH] =?UTF-8?q?iOS=20GPS=20=E8=A8=AD=E5=AE=9A=E3=81=AB?= =?UTF-8?q?=E3=82=88=E3=82=8B=E3=83=9D=E3=83=83=E3=83=97=E3=82=A2=E3=83=83?= =?UTF-8?q?=E3=83=97=E3=80=81=E3=83=90=E3=83=83=E3=82=AF=E3=82=B0=E3=83=A9?= =?UTF-8?q?=E3=82=A6=E3=83=B3=E3=83=89=E5=87=A6=E7=90=86=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=82Android=20=E3=81=AF=E6=9C=AA=E7=A2=BA=E8=AA=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/src/main/AndroidManifest.xml | 7 + .../com/example/rogapp/LocationService.kt | 98 ++++++++++ .../kotlin/com/example/rogapp/MainActivity.kt | 27 +++ ios/Podfile.lock | 12 +- ios/Runner.xcodeproj/project.pbxproj | 42 ++--- ios/Runner/Info.plist | 8 + lib/main.dart | 169 +++++++++++++++++- .../destination/destination_controller.dart | 79 +++++++- lib/pages/gps/gps_page.dart | 3 + lib/pages/index/index_controller.dart | 112 ++++++++++-- lib/pages/index/index_page.dart | 3 + lib/utils/location_controller.dart | 4 +- lib/widgets/map_widget.dart | 3 + 13 files changed, 515 insertions(+), 52 deletions(-) create mode 100644 android/app/src/main/kotlin/com/example/rogapp/LocationService.kt diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index aa8933d..a4562a9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,8 @@ + + +   diff --git a/android/app/src/main/kotlin/com/example/rogapp/LocationService.kt b/android/app/src/main/kotlin/com/example/rogapp/LocationService.kt new file mode 100644 index 0000000..5f1ef6f --- /dev/null +++ b/android/app/src/main/kotlin/com/example/rogapp/LocationService.kt @@ -0,0 +1,98 @@ +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.IBinder +import androidx.core.app.NotificationCompat + +class LocationService : Service() { + + override fun onBind(intent: Intent?): IBinder? { + return null + } + + override fun onCreate() { + super.onCreate() + // フォアグラウンドサービスの設定 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + "location", + "Location", + NotificationManager.IMPORTANCE_LOW + ) + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } + val notification = NotificationCompat.Builder(this, "location") + .setContentTitle("Tracking location...") + .setContentText("Location: null") + .setSmallIcon(R.drawable.ic_launcher) + .setOngoing(true) + .build() + startForeground(1, notification) + } + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + // バックグラウンドでの位置情報取得処理を実装 + val locationSettings = LocationSettings.Builder() + .setAccuracy(LocationAccuracy.HIGH) + .setDistanceFilter(100.0f) + .build() + + try { + Geolocator.getPositionStream(locationSettings) + .catch { e -> + // エラーハンドリング + println("Location Error: $e") + null + } + .filterNotNull() + .filter { position -> + // GPS信号がlow以上の場合のみ記録 + position.accuracy <= 100 + } + .onEach { position -> + val lat = position.latitude + val lng = position.longitude + val timestamp = System.currentTimeMillis() + + // データベースに位置情報を保存 + addGPStoDB(lat, lng, timestamp) + } + .launchIn(CoroutineScope(Dispatchers.IO)) + } catch (e: Exception) { + println("Error starting background tracking: $e") + } + + return START_STICKY + } + + private fun addGPStoDB(lat: Double, lng: Double, isCheckin: Int = 0) { + try { + val context = applicationContext + val preferences = context.getSharedPreferences("RogPreferences", Context.MODE_PRIVATE) + val teamName = preferences.getString("team_name", "") ?: "" + val eventCode = preferences.getString("event_code", "") ?: "" + + if (teamName.isNotEmpty() && eventCode.isNotEmpty()) { + val gpsData = GpsData( + id = 0, + team_name = teamName, + event_code = eventCode, + lat = lat, + lon = lng, + is_checkin = isCheckin, + created_at = System.currentTimeMillis() + ) + + GlobalScope.launch { + val db = GpsDatabaseHelper.getInstance(context) + db.insertGps(gpsData) + } + } + } catch (e: Exception) { + println("Error adding GPS data to DB: $e") + } + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/rogapp/MainActivity.kt b/android/app/src/main/kotlin/com/example/rogapp/MainActivity.kt index 9c7fd0b..4b5d3a9 100644 --- a/android/app/src/main/kotlin/com/example/rogapp/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/rogapp/MainActivity.kt @@ -2,5 +2,32 @@ package com.example.rogapp import io.flutter.embedding.android.FlutterActivity +import androidx.annotation.NonNull +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel + class MainActivity: FlutterActivity() { + private val CHANNEL = "location" + + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { + call, result -> + when (call.method) { + "startLocationService" -> { + val intent = Intent(this, LocationService::class.java) + startService(intent) + result.success(null) + } + "stopLocationService" -> { + val intent = Intent(this, LocationService::class.java) + stopService(intent) + result.success(null) + } + else -> { + result.notImplemented() + } + } + } + } } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4fcd653..a1b8ae5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -11,9 +11,9 @@ PODS: - Flutter - flutter_keyboard_visibility (0.0.1): - Flutter - - FMDB (2.7.5): - - FMDB/standard (= 2.7.5) - - FMDB/standard (2.7.5) + - FMDB (2.7.10): + - FMDB/standard (= 2.7.10) + - FMDB/standard (2.7.10) - geolocator_apple (1.2.0): - Flutter - image_gallery_saver (2.0.2): @@ -35,7 +35,7 @@ PODS: - qr_code_scanner (0.2.0): - Flutter - MTBBarcodeScanner - - ReachabilitySwift (5.0.0) + - ReachabilitySwift (5.2.1) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -121,7 +121,7 @@ SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_compass: cbbd285cea1584c7ac9c4e0c3e1f17cbea55e855 flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 - FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + FMDB: eae540775bf7d0c87a5af926ae37af69effe5a19 geolocator_apple: 9157311f654584b9bb72686c55fc02a97b73f461 image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425 @@ -132,7 +132,7 @@ SPEC CHECKSUMS: permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 pointer_interceptor_ios: 9280618c0b2eeb80081a343924aa8ad756c21375 qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e - ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + ReachabilitySwift: 5ae15e16814b5f9ef568963fb2c87aeb49158c66 shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 sqflite: 50a33e1d72bd59ee092a519a35d107502757ebed url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 8af03db..09cbe28 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -138,8 +138,8 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - CD1CB185AC38B6B245F9A672 /* [CP] Embed Pods Frameworks */, - 4D62FB08D65E9D3D4D84B418 /* [CP] Copy Pods Resources */, + E16D8B21E5C6C68755A033DB /* [CP] Embed Pods Frameworks */, + 03349FDCCEEF7287E57A0F48 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -198,23 +198,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 4D62FB08D65E9D3D4D84B418 /* [CP] Copy Pods Resources */ = { + 03349FDCCEEF7287E57A0F48 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -231,6 +215,22 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -244,7 +244,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; 9ACF47601C5E8A391157E87A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -268,7 +268,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - CD1CB185AC38B6B245F9A672 /* [CP] Embed Pods Frameworks */ = { + E16D8B21E5C6C68755A033DB /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index a1fd8ff..e6d4768 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -67,5 +67,13 @@ FLTEnableImpeller + UIBackgroundModes + + location + + NSLocationWhenInUseUsageDescription + This app needs access to location when open. + NSLocationAlwaysUsageDescription + This app needs access to location when in the background. diff --git a/lib/main.dart b/lib/main.dart index f1e9831..99a0fa4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,12 @@ import 'dart:async'; +import 'dart:io'; //import 'dart:convert'; //import 'dart:developer'; +import 'package:rogapp/model/gps_data.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'; @@ -23,6 +27,10 @@ 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'; + Map deviceInfo = {}; @@ -114,6 +122,8 @@ void main() async { //Get.put(LocationController()); + requestLocationPermission(); + // startMemoryMonitoring(); // 2024-4-8 Akira: メモリ使用量のチェックを開始 See #2810 Get.put(SettingsController()); // これを追加 @@ -126,6 +136,21 @@ void main() async { //runApp(const MyApp()); } +Future 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 /* @@ -192,6 +217,99 @@ void showMemoryWarningDialog() { } } +StreamSubscription? positionStream; +bool background=false; +DateTime lastGPSCollectedTime=DateTime.now(); +String team_name=""; +String event_code=""; + +Future startBackgroundTracking() async { + if (Platform.isIOS && background==false) { + + final IndexController indexController = Get.find(); + 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'); + } + } +} + +Future 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 stopBackgroundTracking() async { + if (Platform.isIOS && background==true) { + background=false; + debugPrint("バックグラウンド処理:停止しました。"); + await positionStream?.cancel(); + positionStream = null; + } +} + class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @@ -209,8 +327,11 @@ class _MyAppState extends State with WidgetsBindingObserver { restoreGame(); } WidgetsBinding.instance.addObserver(this); + + debugPrint("Start MyAppState..."); } + @override void dispose() { WidgetsBinding.instance.removeObserver(this); @@ -227,37 +348,69 @@ class _MyAppState extends State with WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) { LocationController locationController = Get.find(); + DestinationController destinationController = Get.find(); //DestinationController destinationController = // Get.find(); switch (state) { case AppLifecycleState.resumed: + // Foreground に戻った時の処理 + debugPrint(" ==(Status Changed)==> RESUMED. フォアグラウンドに戻りました"); locationController.resumePositionStream(); //print("RESUMED"); restoreGame(); + + // バックグラウンド処理を停止 + if (Platform.isIOS && destinationController.isRunningBackgroundGPS) { + stopBackgroundTracking(); + destinationController.isRunningBackgroundGPS=false; + destinationController.restartGPS(); + + } else if (Platform.isAndroid) { + const platform = MethodChannel('location'); + platform.invokeMethod('stopLocationService'); + } break; case AppLifecycleState.inactive: - locationController.resumePositionStream(); - //print("INACTIVE"); + // アプリが非アクティブになったときに発生します。 + // これは、別のアプリやシステムのオーバーレイ(着信通話やアラームなど)によって一時的に中断された状態です。 + debugPrint(" ==(Status Changed)==> PAUSED. 非アクティブ処理。"); + //locationController.resumePositionStream(); + + // 追加: フロントエンドのGPS信号のlistenを停止 + locationController.stopPositionStream(); + + if (Platform.isIOS ) { // iOSはバックグラウンドでもフロントの処理が生きている。 + destinationController.isRunningBackgroundGPS=true; + startBackgroundTracking(); + }else if(Platform.isAndroid){ + const platform = MethodChannel('location'); + platform.invokeMethod('startLocationService'); + } + saveGameState(); break; case AppLifecycleState.paused: - locationController.resumePositionStream(); - //print("PAUSED"); + // バックグラウンドに移行したときの処理 + //locationController.resumePositionStream(); + debugPrint(" ==(Status Changed)==> PAUSED. バックグラウンド処理。"); saveGameState(); break; case AppLifecycleState.detached: - locationController.resumePositionStream(); - //print("DETACHED"); + // アプリが終了する直前に発生します。この状態では、アプリはメモリから解放される予定です。 + //locationController.resumePositionStream(); + debugPrint(" ==(Status Changed)==> DETACHED アプリは終了します。"); saveGameState(); break; case AppLifecycleState.hidden: - locationController.resumePositionStream(); - //print("DETACHED"); + // Web用の特殊な状態で、モバイルアプリでは発生しません。 + //locationController.resumePositionStream(); + debugPrint(" ==(Status Changed)==> Hidden アプリが隠れた"); saveGameState(); break; } } + @override Widget build(BuildContext context) { return GetMaterialApp( diff --git a/lib/pages/destination/destination_controller.dart b/lib/pages/destination/destination_controller.dart index 51f0ddb..544f876 100644 --- a/lib/pages/destination/destination_controller.dart +++ b/lib/pages/destination/destination_controller.dart @@ -946,6 +946,7 @@ class DestinationController extends GetxController { is_checkin: isCheckin, created_at: DateTime.now().millisecondsSinceEpoch); var res = await db.insertGps(gps_data); + //debugPrint("Saved GPS data into DB..."); } } catch (err) { print("errr ready gps ${err}"); @@ -1202,7 +1203,10 @@ class DestinationController extends GetxController { } // コントローラーの初期化時に呼び出されるライフサイクルメソッドです。 - // + // + bool inError=false; + bool isRunningBackgroundGPS=false; + @override void onInit() async { super.onInit(); @@ -1225,7 +1229,53 @@ class DestinationController extends GetxController { handleLocationUpdate(locationMarkerPosition); //} }, onError: (err) { - print("Location Error: $err"); + if(inError==false){ + inError = true; + debugPrint("Location Error: $err"); + // エラーが発生した場合、locationMarkerPositionStreamControllerにエラーを追加します。 + locationController.locationMarkerPositionStreamController.addError(err); + + // ここにエラー発生時の処理を追加します。 + if (err is LocationServiceDisabledException) { + // 位置情報サービスが無効になっている場合の処理 + print('Location services are disabled'); + Get.snackbar( + 'エラー', + '位置情報サービスが無効になっています。設定画面から位置情報サービスを有効にして下さい。不明な場合にはエンジニアスタッフにお問い合わせください。', + backgroundColor: Colors.red, + colorText: Colors.white, + duration: Duration(seconds: 3), + ); + inError = false; + } else if (err is PermissionDeniedException) { + // 位置情報の権限がない場合の処理 + print('Location permissions are denied'); + Get.snackbar( + 'エラー', + '位置情報サービスが許可されていません。設定画面から岐阜ナビの位置情報サービスを許可して下さい。不明な場合にはエンジニアスタッフにお問い合わせください。', + backgroundColor: Colors.red, + colorText: Colors.white, + duration: Duration(seconds: 3), + ); + inError = false; + } else { + // その他のエラーの場合の処理 + print('Location Error: $err'); + Get.snackbar( + 'エラー', + '位置情報サービスに問題が発生しました。位置情報サービスを再起動していますので少しお待ちください。', + backgroundColor: Colors.red, + colorText: Colors.white, + duration: Duration(seconds: 3), + ); + + // GPSデータのListenを再開する処理を追加 + if( isRunningBackgroundGPS==false && inError ) { + restartGPS(); + } + } + } + //print("Location Error: $err"); }); startGame(); @@ -1233,6 +1283,14 @@ class DestinationController extends GetxController { checkGPSDataReceived(); } + void restartGPS(){ + // GPSデータのListenを再開する処理を追加 + Future.delayed(Duration(seconds: 5), () { + locationController.startPositionStream(); + inError=false; + }); + } + // コントローラーのクローズ時に呼び出されるライフサイクルメソッドです。 // @override @@ -1248,18 +1306,23 @@ class DestinationController extends GetxController { // // 要検討:GPSデータの追加に失敗した場合のエラーハンドリングを追加することをお勧めします。 // + double prevLat = 0.0; // 直前の位置 + double prevLon = 0.0; + void handleLocationUpdate(LocationMarkerPosition? position) async { try { //final DestinationController destinationController = Get.find(); //final signalStrength = locationController.getGpsSignalStrength(); okToUseGPS = false; - double prevLat = currentLat; // 直前の位置 - double prevLon = currentLon; if (position != null) { currentLat = position.latitude; currentLon = position.longitude; + if( prevLat==0.0 ){ + prevLat = currentLat; + prevLon = currentLon; + } lastValidGPSLocation = LatLng(currentLat, currentLon); okToUseGPS = true; lastGPSDataReceivedTime = DateTime.now(); @@ -1316,7 +1379,8 @@ class DestinationController extends GetxController { Duration difference = lastGPSCollectedTime.difference(DateTime.now()) .abs(); // 最後にGPS信号を取得した時刻から10秒以上経過、かつ10m以上経過(普通に歩くスピード) - if (difference.inSeconds >= 10 && distanceToDest >= 10) { + //debugPrint("時間差:${difference.inSeconds}, 距離差:${distanceToDest}"); + if (difference.inSeconds >= 10 || distanceToDest >= 30) { // print( // "^^^^^^^^ GPS data collected ${DateFormat('kk:mm:ss \n EEE d MMM').format(DateTime.now())}, ^^^ ${position.latitude}, ${position.longitude}"); @@ -1333,11 +1397,14 @@ class DestinationController extends GetxController { if (isInRog.value) { await addGPStoDB(position.latitude, position.longitude); lastGPSCollectedTime = DateTime.now(); + prevLat = position.latitude; + prevLon = position.longitude; + debugPrint("フロントエンドでのGPS保存(時間差:${difference.inSeconds}, 距離差:${distanceToDest}) : Time=${lastGPSCollectedTime}"); } } } } catch(e) { - debugPrint("Error: ${e}"); + debugPrint("handleLocationUpdate Error: ${e}"); } finally { /* Akira , 2024-4-5 if (position != null && diff --git a/lib/pages/gps/gps_page.dart b/lib/pages/gps/gps_page.dart index a8f17e5..088c21b 100644 --- a/lib/pages/gps/gps_page.dart +++ b/lib/pages/gps/gps_page.dart @@ -72,6 +72,7 @@ class _GpsPageState extends State { ], )), ), + /* Container( color: Colors.transparent, child: i.is_checkin == 1 @@ -93,6 +94,8 @@ class _GpsPageState extends State { color: Colors.black, )) : Container()), + + */ ], ); } diff --git a/lib/pages/index/index_controller.dart b/lib/pages/index/index_controller.dart index 503a36a..9d91d8d 100644 --- a/lib/pages/index/index_controller.dart +++ b/lib/pages/index/index_controller.dart @@ -6,6 +6,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:geojson_vi/geojson_vi.dart'; +import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:latlong2/latlong.dart'; import 'package:rogapp/model/destination.dart'; @@ -120,11 +121,76 @@ class IndexController extends GetxController { } } + Future _checkLocationPermission() async { + if (Get.context == null) { + debugPrint('Get.context is null in _checkLocationPermission'); + return; + } + LocationPermission permission = await Geolocator.checkPermission(); + //permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + debugPrint('GPS : Denied'); + await showLocationPermissionDeniedDialog(); + } else if (permission == LocationPermission.deniedForever) { + debugPrint('GPS : Denied forever'); + await showLocationPermissionDeniedDialog(); + }else if (permission == LocationPermission.whileInUse){ + debugPrint('GPS : While-In-Use'); + await showLocationPermissionDeniedDialog(); + + }else{ + debugPrint("Permission is no problem...."); + } + } + + + // 追加 + Future showLocationPermissionDeniedDialog() async { + if (Get.context != null) { + print('Showing location permission denied dialog'); + await showDialog( + context: Get.context!, + barrierDismissible: false, + builder: (BuildContext context) { + return WillPopScope( + onWillPop: () async => false, + child: AlertDialog( + title: Text('位置情報の許可が必要です'), + content: Text('設定>プライバシーとセキュリティ>位置情報サービス を開いて、岐阜ナビを探し、「位置情報の許可」を「常に」にして下さい。'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text('OK'), + ), + ], + ), + ); + }, + ); + } else { + print('Get.context is null in showLocationPermissionDeniedDialog'); + // Get.contextがnullの場合の処理 + print('Location permission denied, but context is null'); + } + } + + @override void onInit() { _connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus); super.onInit(); + + print('IndexController onInit called'); // デバッグ用の出力を追加 + + } + + void checkPermission() + { + debugPrint("MapControllerの初期化が完了したら、位置情報の許可をチェックする"); + _checkLocationPermission(); } @override @@ -425,9 +491,11 @@ class IndexController extends GetxController { // 2024-04-03 Akira .. Update the code . See ticket 2800. // // 2024-4-8 Akira : See 2809 - // IndexControllerクラスでは、Future.delayedの呼び出しをunawaitedで囲んで、非同期処理の結果を待たずに先に進むようにしました。これにより、メモリリークを防ぐことができます + // IndexControllerクラスでは、Future.delayedの呼び出しをunawaitedで囲んで、 + // 非同期処理の結果を待たずに先に進むようにしました。これにより、メモリリークを防ぐことができます // - // 要検討:Future.delayedを使用して非同期処理を待たずに先に進むようにしていますが、これによってメモリリークが発生する可能性があります。非同期処理の結果を適切に処理することを検討してください。 + // 要検討:Future.delayedを使用して非同期処理を待たずに先に進むようにしていますが、 + // これによってメモリリークが発生する可能性があります。非同期処理の結果を適切に処理することを検討してください。 // void loadLocationsBound() async { if (isCustomAreaSelected.value == true) { @@ -437,11 +505,6 @@ class IndexController extends GetxController { // MapControllerの初期化が完了するまで待機 await waitForMapControllerReady(); - // Akira 追加:2024-4-6 #2800 - //await waitForMapControllerReady(); // MapControllerの初期化が完了するまで待機 - // Akira 追加:2024-4-6 #2800 - // ==> remove 2024-4-8 - locations.clear(); String cat = currentCat.isNotEmpty ? currentCat[0] : ""; if (currentCat.isNotEmpty && currentCat[0] == "-all-") { @@ -458,14 +521,16 @@ class IndexController extends GetxController { */ LatLngBounds bounds = mapController.bounds!; + if (bounds == null) { + // MapControllerの初期化が完了していない場合は処理を行わない + return; + } + currentBound.clear(); currentBound.add(bounds); isLoading.value = true; // ローディング状態をtrueに設定 -// unawaited( Future.delayed(const Duration(seconds: 1), () async { -// remove - //print("bounds --- (${bounds.southWest.latitude},${bounds.southWest.longitude}),(${bounds.northWest.latitude},${bounds.northWest.longitude}),(${bounds.northEast.latitude},${bounds.northEast.longitude}),(${bounds.southEast.latitude},${bounds.southEast.longitude})"); // 要検討:APIからのレスポンスがnullの場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。 @@ -481,6 +546,7 @@ class IndexController extends GetxController { bounds.southEast.longitude, cat ); + /* if (value == null) { // APIからのレスポンスがnullの場合 print("LocationService.loadLocationsBound からの回答がnullのため、マップをリロード"); @@ -491,8 +557,33 @@ class IndexController extends GetxController { } // 追加 return; } + */ isLoading.value = false; // ローディング状態をfalseに設定 + if (value == null) { + // APIからのレスポンスがnullの場合 + print("LocationService.loadLocationsBound からの回答がnullです"); + } else { + if (value.features.isEmpty) { + if (showPopup == false) { + return; + } + Get.snackbar( + "Too many Points", + "please zoom in", + backgroundColor: Colors.yellow, + colorText: Colors.white, + icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + ); + showPopup = false; + } + if (value.features.isNotEmpty) { + locations.add(value); + } + } + /* if (value != null && value.features.isEmpty) { if (showPopup == false) { return; @@ -513,6 +604,7 @@ class IndexController extends GetxController { if (value != null && value.features.isNotEmpty) { locations.add(value); } + */ } catch ( e) { print("Error in loadLocationsBound: $e"); // エラーが発生した場合のリトライ処理や適切なエラーメッセージの表示を行う diff --git a/lib/pages/index/index_page.dart b/lib/pages/index/index_page.dart index bf55333..f2b1e8d 100644 --- a/lib/pages/index/index_page.dart +++ b/lib/pages/index/index_page.dart @@ -104,6 +104,7 @@ class IndexPage extends GetView { // タップすることでGPS信号の強弱をシミュレーションできるようにする // Akira 2024-4-5 // + /* Obx(() { if (locationController.isSimulationMode) { return DropdownButton( @@ -129,6 +130,8 @@ class IndexPage extends GetView { ); } }), + + */ /* Obx(() => locationController.isSimulationMode ? DropdownButton( diff --git a/lib/utils/location_controller.dart b/lib/utils/location_controller.dart index f43850f..0e261a5 100644 --- a/lib/utils/location_controller.dart +++ b/lib/utils/location_controller.dart @@ -49,7 +49,7 @@ class LocationController extends GetxController { //===== Akira Added 2024-4-9 start // GPSシミュレーション用の変数を追加 ===> 本番では false にする。 - bool isSimulationMode = true; + bool isSimulationMode = false; // GPS信号強度をシミュレートするための変数 final Rx _simulatedSignalStrength = Rx('low'); @@ -93,6 +93,7 @@ class LocationController extends GetxController { return 'low'; } final accuracy = position.accuracy; + //debugPrint("getGpsSignalStrength : ${accuracy}"); if(isSimulationMode){ return _simulatedSignalStrength.value; // GPS信号強度シミュレーション }else { @@ -346,6 +347,7 @@ class LocationController extends GetxController { } } + void handleLocationUpdate(LocationMarkerPosition? position) async { if (position != null) { //debugPrint("position = ${position}"); diff --git a/lib/widgets/map_widget.dart b/lib/widgets/map_widget.dart index 0c4d931..ba4a817 100644 --- a/lib/widgets/map_widget.dart +++ b/lib/widgets/map_widget.dart @@ -70,9 +70,12 @@ class _MapWidgetState extends State with WidgetsBindingObserver { // MapControllerの初期化が完了するまで待機 WidgetsBinding.instance.addPostFrameCallback((_) { + debugPrint("MapControllerの初期化が完了"); setState(() { indexController.isMapControllerReady.value = true; }); + // MapControllerの初期化が完了したら、IndexControllerのonInitを呼び出す + indexController.checkPermission(); }); late MapResetController mapResetController = MapResetController();