import 'dart:io'; import 'dart:typed_data'; import 'package:camera_camera/camera_camera.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; import 'package:geojson_vi/geojson_vi.dart'; import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:latlong2/latlong.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:rogapp/main.dart'; import 'package:rogapp/model/destination.dart'; import 'package:rogapp/model/gps_data.dart'; import 'package:rogapp/pages/camera/camera_page.dart'; import 'package:rogapp/pages/camera/custom_camera_view.dart'; import 'package:rogapp/pages/index/index_controller.dart'; import 'package:rogapp/pages/team/team_controller.dart'; import 'package:rogapp/routes/app_pages.dart'; import 'package:rogapp/services/DatabaseService.dart'; import 'package:rogapp/services/destination_service.dart'; import 'package:rogapp/services/external_service.dart'; import 'package:rogapp/services/location_service.dart'; import 'package:rogapp/services/maxtrix_service.dart'; import 'package:rogapp/services/perfecture_service.dart'; import 'package:rogapp/utils/database_gps.dart'; import 'package:rogapp/utils/database_helper.dart'; import 'package:rogapp/utils/location_controller.dart'; import 'package:rogapp/widgets/bottom_sheet_new.dart'; import 'dart:async'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; import 'package:rogapp/widgets/debug_widget.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:rogapp/utils/const.dart'; import 'package:logger/logger.dart'; import 'package:rogapp/pages/permission/permission.dart'; // 目的地に関連する状態管理とロジックを担当するクラスです。 // class DestinationController extends GetxController { late LocationSettings locationSettings; // 位置情報の設定を保持する変数です。 //late TeamController teamController = TeamController(); //Timer? _GPStimer; // GPSタイマーを保持する変数です。 var destinationCount = 0.obs; // 目的地の数を保持するReactive変数です。 List destinations = [].obs; // 目的地のリストを保持するObservable変数です。 double currentLat = 0.0; // 現在の緯度と経度を保持する変数です。 double currentLon = 0.0; double lastValidLat = 0.0; // 最後に中・強信号で拾ったGPS位置。 // ロゲ開始を屋内でやったら 0 のままなので、屋外で行うこと。 double lastValidLon = 0.0; DateTime lastGPSCollectedTime = DateTime.now(); // 最後にGPSデータが収集された時刻を保持する変数です。 bool shouldShowBottomSheet = true; // ボトムシートを表示すべきかどうかを示すフラグです。 static bool gps_push_started = false; // ゲームの状態を示す静的変数です。 static bool game_started = false; static bool ready_for_goal = false; bool skip_10s = false; // 10秒間のスキップフラグを示す変数です。 List currentSelectedDestinations = [].obs; // 現在選択されている目的地のリストを保持するObservable変数です。 var isInCheckin = false.obs; // ゲームの状態を示すReactive変数です。 var isInRog = false.obs; var isAtStart = false.obs; var isAtGoal = false.obs; var isPhotoShoot = false.obs; DateTime lastGoalAt = DateTime.now().subtract(const Duration(days: 1)); // 最後にゴールした時刻を保持する変数です。 //List rogainings = [].obs; bool checkingIn = false; // チェックイン中かどうかを示すフラグです。 var isGpsSelected = true.obs; // GPSが選択されているかどうかを示すReactive変数です。 BuildContext? context; // ビルドコンテキストを保持する変数です。 List gps = ["-- stating --"].obs; // GPSと位置情報の許可に関する情報を保持するObservable変数です。 List locationPermission = [" -- starting -- "].obs; var travelMode = 0.obs; // 移動モードを保持するReactive変数です。 bool skipGps = false; // GPSをスキップするかどうかを示すフラグです。 bool okToUseGPS = false; // 最新のGPS情報を使用して良いかを示すフラグ。 Map matrix = {}; // 行列データを保持する変数です。 final photos = [].obs; // 写真のリストを保持するReactive変数です。 final IndexController indexController = Get.find(); // IndexControllerのインスタンスを保持する変数です。 final LocationController locationController = Get.put(LocationController()); // LocationControllerのインスタンスを保持する変数です。 final DatabaseService dbService = DatabaseService(); // DatabaseServiceのインスタンスを保持する変数です。 int _start = 0; // 開始時刻を保持する変数です。 int chekcs = 0; // チェックポイントの数を保持する変数です。 var rogainingCounted = false.obs; // ロゲイニングがカウントされたかどうかを示すReactive変数です。 // destinationController.rogainingCountedは、現在のロゲイニングセッションでポイントがカウントされたかどうかを管理するフラグです。 // // このフラグは以下のような状況で使用されます: // // ロゲイニングを開始したとき、rogainingCountedはfalseに初期化されます。これは、まだポイントがカウントされていないことを示します。 // チェックポイントに到着し、チェックインが成功したとき、rogainingCountedはtrueに設定されます。これは、そのセッションでポイントがカウントされたことを示します。 // ロゲイニングを終了したとき、rogainingCountedは再びfalseに設定されます。これは、次のセッションに備えてフラグをリセットするためです。 // このフラグは、主に以下の目的で使用されます: // // ゴール地点でのロジックの制御:rogainingCountedがtrueの場合、つまりポイントがカウントされている場合にのみ、ゴール処理を実行できます。 // UI の更新:rogainingCountedの状態に基づいて、適切なメッセージやボタンを表示することができます。 bool isMapControllerReady = false; LatLng lastValidGPSLocation = LatLng(0, 0); DateTime lastGPSDataReceivedTime = DateTime.now(); DateTime lastPopupShownTime = DateTime.now().subtract(Duration(minutes: 10)); bool isPopupShown = false; bool hasReceivedGPSData = true; var isCheckingIn = false.obs; // チェックイン操作中はtrueになり、重複してポップアップが出ないようにするもの。 var isRouteShowing = false.obs; // ルートが表示されているかどうかを示すReactive変数 /* //==== Akira .. GPS信号シミュレーション用 ===== ここから、2024-4-5 // bool kDebugMode = true; // シミュレーションモードのフラグ RxBool isSimulationMode = RxBool(true); // シミュレーションモードを切り替えるための関数 void toggleSimulationMode(bool value) { isSimulationMode.value = value; } // 現在位置の取得メソッドを追加 LatLng getCurrentLocation() { return LatLng(lastValidLat, lastValidLon); } // // GPS信号の強弱を判断するメソッドを追加します。 // String getGpsSignalStrength() { // デバッグモードかつシミュレーションモードの場合は、シミュレートされた信号強度を返す print("kDebugMode : ${kDebugMode}, isSimulationMode : ${isSimulationMode.value}"); if (kDebugMode && isSimulationMode.value) { return locationController.getSimulatedSignalStrength(); } // 通常モードの場合は、実際の信号強度を返す final accuracy = locationController.currentPosition.value?.accuracy ?? double.infinity; if (accuracy <= 10) { return 'high'; } else if (accuracy <= 30) { return 'medium'; } else { return 'low'; } } // //==== Akira .. GPS信号シミュレーション用 ======= ここまで */ // ルートをクリアする関数です。 void clearRoute() { indexController.routePoints.clear(); indexController.routePointLenght.value = 0; isRouteShowing.value = false; } void showGPSDataNotReceivedPopup() { if (Get.context != null) { Get.dialog( AlertDialog( title: Text('GPS信号が受信できません'), content: Text('GPS信号が受信できる場所に移動してください。'), actions: [ TextButton( onPressed: () => Get.back(), child: Text('OK'), ), ], ), ); } else { // Get.contextがnullの場合の処理を追加 print('GPS signal not received, but context is null'); } } // 最後に有効なGPSデータを受け取ってから10分以上経過している場合にのみメッセージを表示するようにします。 // void checkGPSDataReceived() { if (!hasReceivedGPSData) { //debugPrint("GPS信号を全く受信していない。"); if (!isPopupShown) { // ポップアップしていない。 showGPSDataNotReceivedPopup(); lastPopupShownTime = DateTime.now(); isPopupShown = true; } } else { if (DateTime.now().difference(lastGPSDataReceivedTime).inSeconds >= 600) { // 前回GPS信号を受信してから10分経過。 if (!isPopupShown && DateTime.now().difference(lastPopupShownTime).inMinutes >= 3) { // 前回ポップアップしてから3分経過してなければ showGPSDataNotReceivedPopup(); lastPopupShownTime = DateTime.now(); isPopupShown = true; } } else { isPopupShown = false; } } } // 日時をフォーマットされた文字列に変換する関数です。 // String getFormatedTime(DateTime datetime) { return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime); } // 追加:Akira 2024-4-5 // GPS信号の精度が一定値以上の場合、GPS信号が弱いと判断する // bool isGpsSignalWeak() { final accuracy = locationController.currentPosition.value?.accuracy; if (accuracy == null) { return true; // 位置情報が取得できていない場合、GPS信号が弱いと見なす } return accuracy > 60; //return locationController.currentPosition.value?.accuracy ?? double.infinity > 50; } // Destination festuretoDestination(GeoJSONFeature fs) { GeoJSONMultiPoint mp = fs.geometry as GeoJSONMultiPoint; LatLng pt = LatLng(mp.coordinates[0][1], mp.coordinates[0][0]); //print("----- ${indexController.currentFeature[0].properties} -----"); return Destination( name: fs.properties!["location_name"], sub_loc_id: fs.properties!["sub_loc_id"], address: fs.properties!["address"], phone: fs.properties!["phone"], email: fs.properties!["email"], webcontents: fs.properties!["webcontents"], videos: fs.properties!["videos"], category: fs.properties!["category"], series: 1, lat: pt.latitude, lon: pt.longitude, location_id: fs.properties!["location_id"], list_order: 1, photos: fs.properties!["photos"], checkin_radious: fs.properties!["checkin_radius"], auto_checkin: fs.properties!["auto_checkin"] == true ? 1 : 0, cp: fs.properties!["cp"], checkin_point: fs.properties!["checkin_point"], buy_point: fs.properties!["buy_point"], selected: false, checkedin: false, hidden_location: fs.properties!["hidden_location"] == true ? 1 : 0, tags: fs.properties!["tags"]); } // 指定された目的地の位置情報に基づいてタイマーを開始する関数です。 // CP情報(fs)と現在位置からCPまでの距離distance を引数として渡します。 // Future startTimerLocation(GeoJSONFeature fs, double distance) async { //print("---- in startTimer ----"); // print("---- is in rog is $is_in_rog ----"); double checkinRadious = fs.properties!['checkin_radius'] ?? double.infinity; // CPのcheckin_radiusを取得し、checkinRadius に代入。値がなければinfinityとする。 if (checkinRadious >= distance) { // checkinRadious以内に入ったら、 indexController.currentFeature.clear(); // indexController.currentFeatureを空にします。 Destination d = festuretoDestination(fs); // festuretoDestination(fs)を呼び出し、GeoJSONFeatureオブジェクトfsからDestinationオブジェクトdを作成します。 // print("----- destination lenght is ${destinations.length} -----"); indexController.currentFeature.add(fs); // indexController.currentFeatureにfsを追加します。 //print("---- before calling startTimer ----"); await startTimer(d, distance); // startTimer(d, distance)を非同期で呼び出し、その完了を待機します。 return; } } // 指定された目的地に対してタイマーを開始する関数です。 // 目的地の位置情報を取得し、チェックイン半径内にいるかどうかを確認します。 // 写真撮影モードの場合は、ボトムシートを表示して写真撮影を行います。 // 目的地がデータベースに存在しない場合は、新しい目的地としてデータベースに挿入します。 // 目的地に応じて、チェックイン、ゴール、買い物ポイントの処理を行います。 // // 2024-4-8 akira: GPS信号が弱い場合でも、最後に取得した位置情報を使用してチェックインやゴールの処理を続行できるようになります。また、チェックインやゴールの処理では、GPS信号の精度チェックを緩和することで、GPS信号が弱い場合でもボタンを押せるようになります。 // // 要検討:エラーが発生した場合のエラーハンドリングを追加し、適切なメッセージを表示することを検討してください。 // // 引数:CPオブジェクトと現在地からCPまでの距離を渡す。 // Future startTimer(Destination d, double distance) async { //print("=== passed dest is ${d.location_id} ${d.checkedin} ===="); skipGps = true; //debugPrint("---- in startTimer ----"); DatabaseHelper db = DatabaseHelper.instance; List ds = await db.getDestinationByLatLon(d.lat!, d.lon!); //指定位置のオブジェクトのリストを取得。 Destination? dss; if (ds.isNotEmpty) { dss = ds.first; // 取得したリストが空でない場合、dss変数に最初の要素を代入します。 } // 変数を計算 double checkinRadious = d.checkin_radious ?? double.infinity; // 反応半径 bool autoCheckin = d.auto_checkin == 0 ? false : true; // 自動チェックイン bool buyPoint = dss != null && dss.buy_point != null && dss.buy_point! > 0 // 買い物ポイント ? true : false; bool buyPointImageAdded = // 買い物画像 dss != null && dss.buypoint_image != null ? true : false; bool buyPointCanceled = // 買い物キャンセル dss != null && dss.buy_point != null && dss.buy_point == 0 ? true : false; bool locationAlreadyCheckedIn = // チェックイン済みか ds.isNotEmpty && ds[0].checkedin == true ? true : false; bool isuserLoggedIn = indexController.currentUser.isNotEmpty ? true : false; // ログイン済みか /* // スタートとゴールは除外 debugPrint("startTimer CP=${d.cp}"); if (d.cp == -1 || d.cp == 0 || d.cp == -2) { skipGps = false; return; } */ // 初期化。GPS信号が強くても弱くても if (checkinRadious >= distance || checkinRadious == -1) { //currentSelectedDestinations.add(d); // 目的地として登録する。 //debugPrint("目的地の初期化"); indexController.currentDestinationFeature.clear(); indexController.currentDestinationFeature.add(d); // print( // "---- checked in as ${indexController.currentDestinationFeature[0].checkedin.toString()} ----"); } else { // ここには来ないのでは? debugPrint("検出範囲外..."); // GPS信号が弱い場合でも、チェックインやゴールの処理を続行する // comment out by Akira, 2024-4-5 // skipGps = false; // return; // GPS信号が弱い場合、最後に取得した高いまたは中程度の位置情報を使用 if (okToUseGPS) { double lastValidDistance = Geolocator.distanceBetween( lastValidLat, lastValidLon, d.lat!, d.lon! ); /* double lastValidDistance = distance.as( LengthUnit.Meter, LatLng(lastValidLat, lastValidLon), LatLng(d.lat!, d.lon!), ); */ if (checkinRadious >= lastValidDistance || checkinRadious == -1) { // 反応半径内か、距離無視CPなら indexController.currentDestinationFeature.clear(); indexController.currentDestinationFeature.add(d); } else { skipGps = false; return; } } else { skipGps = false; return; } } if (isPhotoShoot.value == true) { // 写真撮影するなら ... isPhotoShoot=True にしてる場所がない。 debugPrint("isPhotoShoot.value == true ... will camera popup"); photos.clear(); // まず既存の写真をクリア if (shouldShowBottomSheet) { // ボトムシートを使うべきなら shouldShowBottomSheet = false; if (d.cp == -1) return; // CPは開始点なら戻る。 // カメラページをポップアップ await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, builder: ((context) => CameraPage(destination: d))) .whenComplete(() { shouldShowBottomSheet = true; skipGps = false; chekcs = 0; isInCheckin.value = false; }); } return; } // 写真撮影モードでない場合 if (ds.isEmpty) { debugPrint("* 目的地がない場合 ==> 検知半径=-1の場合"); // print("----- in location popup cp - ${d.cp}----"); if ((d.cp == -1 || d.cp==0 ) && DateTime.now().difference(lastGoalAt).inHours >= 24) { debugPrint("**1: 開始CPで、最後にゴールしてから24時間経過していれば、"); chekcs = 1; //start // print("~~~~ calling start ~~~~"); print("---- in start -----"); chekcs = 1; // スタート地点で前のゴールから24時間経過 isInCheckin.value = true; isAtStart.value = true; if (shouldShowBottomSheet) { shouldShowBottomSheet = false; // bottom_sheet を起動させない。 Widget bottomSheet = BottomSheetNew(destination: d); await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.85)), context: Get.context!, isScrollControlled: true, builder: ((context) => bottomSheet) ).whenComplete(() { shouldShowBottomSheet = true; // bottom_sheet 起動許可 skipGps = false; chekcs = 0; // ボトムシートモード=1, isAtStart.value = false; isInCheckin.value = false; }); } return; // 以下の条件分岐を追加 } else if (ds.isNotEmpty && ds[0].checkedin == true) { // 目的地がDBに存在し、すでにチェックインしている場合は自動ポップアップを表示しない debugPrint("チェックイン済み"); return; } else if (isInRog.value == true && indexController.rogMode.value == 1 && (locationAlreadyCheckedIn==null || locationAlreadyCheckedIn==false) && d.cp != -1 && d.cp != 0 && d.cp != -2) { debugPrint("**2: 標準CP まだチェックインしていない。"); // print("----- in location popup checkin cp - ${d.cp}----"); chekcs = 2; // 標準CP isInCheckin.value = true; if (shouldShowBottomSheet) { shouldShowBottomSheet = false; Widget bottomSheet = BottomSheetNew(destination: d); await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, builder: ((context) => bottomSheet) ).whenComplete(() { shouldShowBottomSheet = true; skipGps = false; chekcs = 0; isInCheckin.value = false; }); } return; } } // 以降、検知範囲にある場合。 //debugPrint("検知範囲にある場合"); // print("---- location checkin radious ${d.checkin_radious} ----"); // print("---- already checked in $locationAlreadyCheckedIn ----"); if ((checkinRadious >= distance || checkinRadious == -1) && locationAlreadyCheckedIn == false && isInRog.value == true && !isCheckingIn.value) { debugPrint("* 検知範囲または距離無視CPで、ゲーム中でまだチェックインしていない。"); if (autoCheckin) { // 自動チェックインなら if (!checkingIn) { debugPrint("** 自動チェックインの場合"); //print( // "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ make checkin ${d.sub_loc_id}@@@@@@@@@@@"); makeCheckin(d, true, ""); // チェックインして if (d.cp != -1 && d.cp != -2 && d.cp != 0 ) { rogainingCounted.value = true; // ゴール用チェックイン済み } skipGps = false; } return; // 戻る } else { // それ以外 debugPrint("* 自動チェックイン以外の場合"); // print("--- hidden loc ${d.hidden_location} ----"); // ask for checkin if (d.hidden_location != null && d.hidden_location == 0 && // 隠しCPフラグ==0 ... 通常CP isInRog.value == true && d.cp != -1 && d.cp != -2 && d.cp != 0) { // 隠しCPの場合、 debugPrint("**3 通常CPの場合"); chekcs = 3; isInCheckin.value = true; isCheckingIn.value = true; photos.clear(); // print("--- calling checkin ---"); if (shouldShowBottomSheet) { shouldShowBottomSheet = false; await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, builder: ((context) => CameraPage( destination: d, ))).whenComplete(() { shouldShowBottomSheet = true; skipGps = false; rogainingCounted.value = true; chekcs = 0; isInCheckin.value = false; isCheckingIn.value = false; }); } return; } else if (isInRog.value == true && (locationAlreadyCheckedIn==null || locationAlreadyCheckedIn==false) && d.cp != -1 && d.cp != -2 && d.cp != 0) { // 通常CP debugPrint("**4 通常CP以外の場合....どんな場合?"); chekcs = 4; isInCheckin.value = true; if (shouldShowBottomSheet) { shouldShowBottomSheet = false; Widget bottomSheet = BottomSheetNew(destination: d); await showMaterialModalBottomSheet( expand: true, context: Get.context!, backgroundColor: Colors.transparent, builder: (context) => bottomSheet ).whenComplete(() { shouldShowBottomSheet = true; skipGps = false; chekcs = 0; isInCheckin.value = false; }); } return; } } } else if ((checkinRadious >= distance || checkinRadious == -1) && locationAlreadyCheckedIn == true && buyPointImageAdded == false && ds.isNotEmpty && buyPoint == true && buyPointCanceled == false && isInRog.value == true) { // チェックイン後で買い物ポイントの場合。 debugPrint("**5 チェックイン後で買い物ポイントの場合"); chekcs = 5; isInCheckin.value = true; photos.clear(); //print("--- open buy point $buyPointImageAdded ${d.buypoint_image} ----"); if (shouldShowBottomSheet) { shouldShowBottomSheet = false; if (d.cp == -1 && d.cp != -2 && d.cp != 0) return; await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, builder: ((context) => CameraPage( destination: d, buyPointPhoto: true, dbDest: ds.first, ))).whenComplete(() { shouldShowBottomSheet = true; skipGps = false; rogainingCounted.value = true; chekcs = 0; isInCheckin.value = false; }); } return; } // print("---- cp --- ${d.cp} -----"); // print("--- at goal $is_at_goal ---"); // print("--- rog counted $rogaining_counted ---"); // print("--- loc already checked in $locationAlreadyCheckedIn ---"); // print( // "==== date diff is ${DateTime.now().difference(last_goal_at).inHours} ===="); if (isuserLoggedIn && (d.cp == -2 || d.cp == 0 || d.cp == -1 ) && // Goal CP locationAlreadyCheckedIn && skip_10s == false) { //check for rogaining if (isAtGoal.value == false && rogainingCounted.value) { //goal //print("---- in goal -----"); debugPrint("**5 ゴールで時計撮影の場合"); chekcs = 5; // Goal 時計撮影 isAtGoal.value = true; photos.clear(); if (shouldShowBottomSheet) { shouldShowBottomSheet = false; if (d.cp == -1) return; await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, builder: ((context) => CameraPage( destination: d, ))).whenComplete(() { shouldShowBottomSheet = true; skipGps = false; chekcs = 0; isAtGoal.value = false; }); } return; } else if (isInRog.value == false && indexController.rogMode.value == 1 && DateTime.now().difference(lastGoalAt).inHours >= 24) { //start //print("---- in start -----"); debugPrint("**5 スタートの場合で最後のゴールから24時間経過している場合"); chekcs = 6; // start point isAtStart.value = true; if (shouldShowBottomSheet) { shouldShowBottomSheet = false; if (d.cp != -1 && d.cp != 0) return; Widget bottomSheet = BottomSheetNew(destination: d); await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, builder: ((context) => bottomSheet) ).whenComplete(() { shouldShowBottomSheet = true; //print("----- finished start -------"); skipGps = false; chekcs = 0; isAtStart.value = false; }); } return; } } //print("==== _chekcs $chekcs ===="); if (chekcs == 0) { //debugPrint("いずれにも当てはまらないので、処理スキップ"); skipGps = false; } return; } // ロゲイニングをリセットする関数です。 // ゲームの状態をリセットし、データベースからデータを削除します。 // Future resetRogaining({bool isgoal = false}) async { //print("----- resetting --------"); isInCheckin.value = false; isInRog.value = false; isAtStart.value = false; isAtGoal.value = false; isGpsSelected.value = true; skipGps = false; ready_for_goal = false; _start = 0; chekcs = 0; rogainingCounted.value = false; DatabaseHelper db = DatabaseHelper.instance; if (isgoal == false) { await db.deleteAllDestinations(); await db.deleteAllRogaining(); } int? latgoal = await db.latestGoal(); if (latgoal != null) { lastGoalAt = DateTime.fromMicrosecondsSinceEpoch(latgoal); //print("===== last goal : $last_goal_at ====="); } dbService.updateDatabase(); } // すべての目的地を削除する関数です。 // void deleteAllDestinations() { DatabaseHelper db = DatabaseHelper.instance; db.deleteAllDestinations().then((value) { populateDestinations(); }); } // カメラを開いて写真を撮影する関数です。 // void openCamera(BuildContext context, Destination? destination) { photos.clear(); Navigator.push( context, MaterialPageRoute( builder: (_) => CustomCameraView( onImageCaptured: (imagePath) { photos.add(File(imagePath)); }, destination: destination, ), /* builder: (_) => CameraCamera( resolutionPreset: ResolutionPreset.medium, onFile: (file) { photos.add(file); Navigator.pop(context); //print("----image file is : $file----"); //setState(() {}); }, ) */ ), ); } // ルートポイントを取得する関数です。 // void getRoutePoints() { indexController.routePoints = []; indexController.routePointLenght.value = 0; DestinationService.getDestinationLine(destinations, matrix)?.then((value) { indexController.routePoints = value; indexController.routePointLenght.value = indexController.routePoints.length; }); } // 指定された緯度と経度に対応する目的地を取得する関数です。 // Future getDestinationForLatLong(double lat, double long) async { for (final d in destinations) { if (lat == d.lat && long == d.lon) { return d; } } return null; } // チェックインの呼び出しを行う関数です。 // 指定された目的地に対してチェックインの処理を行います。 // Future callforCheckin(Destination d) async { bool autoCheckin = d.auto_checkin == 0 ? false : true; print("---- f- checkin ${d.sub_loc_id} ----"); if (autoCheckin) { if (!checkingIn) { makeCheckin(d, true, ""); if (d.cp != -1 && d.cp != 0 && d.cp != -2) { rogainingCounted.value = true; } } } else { //print("--- hidden loc ${d.hidden_location} ----"); // ask for checkin //print("is rog ---- ${is_in_rog.value} ----"); if (d.hidden_location != null && d.hidden_location == 0 && isInRog.value == true && d.cp != -1 && d.cp != 0 && d.cp != -2) { chekcs = 3; photos.clear(); isInCheckin.value = true; final result = await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, builder: ((context) => CameraPage( manulaCheckin: true, destination: d, ))); if (result ?? false) { debugPrint("==> Checkin complete...."); if (d.buy_point != null && d.buy_point! > 0) { skipGps = true; photos.clear(); DatabaseHelper db = DatabaseHelper.instance; List ds = await db.getDestinationByLatLon(d.lat!, d.lon!); Destination? dss; if (ds.isNotEmpty) { dss = ds.first; } await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, builder: ((context) => CameraPage( buyPointPhoto: true, destination: d, dbDest: dss, ))).whenComplete(() { skipGps = false; rogainingCounted.value = true; chekcs = 0; isInCheckin.value = false; //Get.back(); }); } } else { debugPrint("キャンセルされました"); Get.snackbar( "キャンセルされました", "チェックインしていません。必要ならもう一度チェックポイントをタップして下さい。", backgroundColor: Colors.yellow, colorText: Colors.black, icon: const Icon( Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), snackPosition: SnackPosition.TOP, duration: const Duration(seconds: 3), ); } } else { Get.snackbar( "ロゲが始まっていません", "ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります", backgroundColor: Colors.yellow, colorText: Colors.black, icon: const Icon( Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), snackPosition: SnackPosition.TOP, duration: const Duration(seconds: 3), ); } /* await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, builder: ((context) => CameraPage( manulaCheckin: true, destination: d, ))).whenComplete(() async { if (d.buy_point != null && d.buy_point! > 0) { skipGps = true; photos.clear(); DatabaseHelper db = DatabaseHelper.instance; List ds = await db.getDestinationByLatLon(d.lat!, d.lon!); Destination? dss; if (ds.isNotEmpty) { dss = ds.first; } await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, builder: ((context) => CameraPage( buyPointPhoto: true, destination: d, dbDest: dss, ))).whenComplete(() { skipGps = false; rogainingCounted.value = true; chekcs = 0; isInCheckin.value = false; //Get.back(); }); } else { skipGps = false; chekcs = 0; isInCheckin.value = false; } }); } else { Get.snackbar( "ロゲが始まっていません", "ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります", 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) // backgroundColor: Colors.yellow, ); } */ } } // GPSデータをデータベースに追加する関数です。 // Future addGPStoDB(double la, double ln, {isCheckin = 0}) async { //debugPrint("in addGPStoDB ${indexController.currentUser}"); try { GpsDatabaseHelper db = GpsDatabaseHelper.instance; if(indexController.currentUser.length>0){ final team_name = indexController.currentUser[0]["user"]['team_name']; final event_code = indexController.currentUser[0]["user"]["event_code"]; GpsData gps_data = GpsData( id: 0, team_name: team_name, event_code: event_code, lat: la, lon: ln, is_checkin: isCheckin, created_at: DateTime.now().millisecondsSinceEpoch); var res = await db.insertGps(gps_data); //debugPrint("Saved GPS data into DB...:${gps_data}"); } } catch (err) { print("errr ready gps ${err}"); return; } } // チェックインを確認する関数です。 // ゲームが開始されていない場合は、ゲームを開始します。 // 目的地のリストを走査し、現在位置がチェックイン半径内にある場合は、チェックインの処理を行います。 // GPSデータの送信を開始します。 // // 2024-4-8 Akira : See 2809 // checkForCheckinメソッドの再帰呼び出しをunawaitedで囲んで、非同期処理の結果を待たずに先に進むようにしました。また、再帰呼び出しの前に一定時間待機するようにしました。 // Future checkForCheckin() async { //print("--- Start of checkForCheckin function ---"); dbService.updateDatabase(); await Future.delayed(const Duration(milliseconds: 3000)); game_started = true; try { // ここで、エラー if( indexController.locations.length>0 ) { indexController.locations[0].features.forEach((fs) async { GeoJSONMultiPoint mp = fs!.geometry as GeoJSONMultiPoint; LatLng pt = LatLng(mp.coordinates[0][1], mp.coordinates[0][0]); double latFs = pt.latitude; double lonFs = pt.longitude; var distanceFs = const Distance(); double distFs = distanceFs.as(LengthUnit.Meter, LatLng(latFs, lonFs), LatLng(currentLat, currentLon)); Destination des = festuretoDestination(fs); if (distFs <= des.checkin_radious! && skipGps == false //&& des.isCheckedIn == false && des.cp!=0 && des.cp!=-1 && des.cp!=-2) { await startTimerLocation(fs, distFs); // Note: You cannot break out of forEach. If you need to stop processing, you might have to reconsider using forEach. } }); if (gps_push_started == false) { unawaited(pushGPStoServer()); } } //print("--- 123 ---- $skip_gps----"); } catch (e) { print("An error occurred: $e"); // await checkForCheckin(); } finally { await Future.delayed(const Duration(seconds: 1)); // 一定時間待機してから再帰呼び出し //print("--- End of checkForCheckin function, calling recursively ---"); unawaited( checkForCheckin() ); } } // GPSデータをサーバーにプッシュする関数です。 // Future pushGPStoServer() async { // print( // "^^^^^^^^ ${DateFormat('kk:mm:ss \n EEE d MMM').format(DateTime.now())}"); try { gps_push_started = true; ExternalService().pushGPS(); } catch (e) { //print("An error occurred: $e"); //await pushGPStoServer(); } finally { //print("--- End of pushGPStoServer function, calling recursively ---"); await Future.delayed(const Duration(seconds: 5 * 60)); await pushGPStoServer(); } } // ロゲイニングにデータを追加する関数です。 // void addToRogaining(double lat, double lon, int destinationId) async { DatabaseHelper db = DatabaseHelper.instance; List d = await db.getDestinationById(destinationId); if (d.isEmpty) { Destination df = festuretoDestination(indexController.currentFeature[0]); //print("--- made checkin ${df.location_id} ----"); makeCheckin(df, true, ""); } isInRog.value = true; saveGameState(); } // 買い物ポイントをキャンセルする関数です。 // Future cancelBuyPoint(Destination destination) async { DatabaseHelper db = DatabaseHelper.instance; await db.updateCancelBuyPoint(destination); populateDestinations(); } // 指定されたパスの画像をギャラリーに保存する関数です。 // _saveImageFromPath(String imagePath) async { try { // Read the image file from the given path File imageFile = File(imagePath); Uint8List imageBytes = await imageFile.readAsBytes(); // Save the image to the gallery final result = await ImageGallerySaver.saveImage(imageBytes); //print("--- save result --- ${result}"); } catch(e, stackTrace){ print('エラーが発生しました: $e'); print('スタックトレース: $stackTrace'); } } Future _saveImageToGallery(String imagePath) async { final status = await PermissionController.checkStoragePermission(); if(!status){ await PermissionController.requestStoragePermission(); } /* final status = await Permission.storage.status; if (!status.isGranted) { final result = await Permission.storage.request(); if (!result.isGranted) { // ユーザーがストレージの権限を拒否した場合の処理 showDialog( context: Get.context!, builder: (BuildContext context) { return AlertDialog( title: Text('ストレージの権限が必要です'), content: Text( '画像をギャラリーに保存するには、ストレージの権限が必要です。アプリの設定画面で権限を許可してください。'), actions: [ TextButton( child: Text('キャンセル'), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( child: Text('設定'), onPressed: () { Navigator.of(context).pop(); openAppSettings(); // アプリの設定画面を開く }, ), ], ); } ); return; } } */ try { final result = await ImageGallerySaver.saveFile(imagePath); print('Image saved to gallery: $result'); } catch (e) { print('Failed to save image to gallery: $e'); } } // 買い物ポイントを作成する関数です。 指定された目的地に対して買い物ポイントの処理を行います。 // // 買い物ポイントの作成に失敗した場合のエラーハンドリングを追加することを検討してください。 // Future makeBuyPoint(Destination destination, String imageurl) async { DatabaseHelper db = DatabaseHelper.instance; await db.updateBuyPoint(destination, imageurl); populateDestinations(); //await _saveImageFromPath(imageurl); await _saveImageToGallery(imageurl); if (indexController.currentUser.isNotEmpty) { double cpNum = destination.cp!; //int teamId = indexController.teamId.value; // teamIdを使用 int userId = indexController.currentUser[0]["user"]["id"]; //print("--- Pressed -----"); String team = indexController.currentUser[0]["user"]['team_name']; //print("--- _team : ${_team}-----"); String eventCode = indexController.currentUser[0]["user"]["event_code"]; //print("--- _event_code : ${_event_code}-----"); String token = indexController.currentUser[0]["token"]; //print("--- _token : ${_token}-----"); DateTime now = DateTime.now(); String formattedDate = DateFormat('yyyy-MM-dd HH:mm:ss').format(now); //print("------ checkin event $eventCode ------"); ExternalService() .makeCheckpoint(userId, token, formattedDate, team, cpNum.round(), eventCode, imageurl) .then((value) { //print("------Ext service check point $value ------"); }); } } // チェックインを行う関数です。 指定された目的地に対してチェックインの処理を行います。 // // 要検討:チェックインのリクエストが失敗した場合のエラーハンドリングを追加することをお勧めします。 // Future makeCheckin( Destination destination, bool action, String imageurl) async { try { // print("~~~~ calling checkin function ~~~~"); // print( // "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ressssss ${destination.sub_loc_id}@@@@@@@@@@@"); DatabaseHelper db = DatabaseHelper.instance; List ddd = await db.getDestinationByLatLon(destination.lat!, destination.lon!); if (ddd.isEmpty) { destination.checkedin = true; destination.checkin_image = imageurl; await db.insertDestination(destination); // print("~~~~ inserted into db ~~~~"); } if (imageurl == null || imageurl.isEmpty) { if (photos.isNotEmpty) { // imageurlが空の場合は、destinationのcheckin_imageプロパティを使用する debugPrint("photos = ${photos}"); imageurl = photos[0].path; } debugPrint("imageurl = ${imageurl}"); //await _saveImageFromPath(imageurl!); } if (imageurl.isNotEmpty) { await _saveImageToGallery(imageurl); } populateDestinations(); /// post to NATNAT if (indexController.currentUser.isNotEmpty) { double cpNum = destination.cp!; //int teamId = indexController.teamId.value; // teamIdを使用 //Team team0 = teamController.teams[0]; //print("team={team0}"); int userId = indexController.currentUser[0]["user"]["id"]; //print("--- Pressed -----"); String team = indexController.currentUser[0]["user"]['team_name']; //print("--- _team : ${_team}-----"); String eventCode = indexController.currentUser[0]["user"]["event_code"]; //print("--- _event_code : ${_event_code}-----"); String token = indexController.currentUser[0]["token"]; //print("--- _token : ${_token}-----"); DateTime now = DateTime.now(); String formattedDate = DateFormat('yyyy-MM-dd HH:mm:ss').format(now); await addGPStoDB(currentLat, currentLon, isCheckin: 1); // print("------ checkin event $eventCode ------"); ExternalService() .makeCheckpoint( userId, // teamIdを使用 token, formattedDate, team, cpNum.round(), eventCode, imageurl) .then((value) { // print("------Ext service check point $value ------"); }); } // dbService.updateDatabase(); }catch(e, stacktrace){ print("エラー:${e}"); //print("stack : ${stacktrace}"); }finally{ dbService.updateDatabase(); } } // チェックインを削除する関数です。 // Future removeCheckin(int cp) { dbService.updateDatabase(); return ExternalService().removeCheckin(cp); } // ゲームを開始する関数です。 // Future startGame() async { debugPrint("------ starting game ------"); if (game_started == false) { await checkForCheckin(); } } Timer? gpsCheckTimer; // 一定間隔でGPSデータの受信状態をチェックするタイマー void startGPSCheckTimer() { gpsCheckTimer = Timer.periodic(Duration(seconds: 5), (timer) { checkGPSDataReceived(); }); } // コントローラーの初期化時に呼び出されるライフサイクルメソッドです。 // bool inError=false; bool isRunningBackgroundGPS=false; int activeEngineCount = 0; @override void onInit() async { super.onInit(); /* WidgetsBinding.instance.addPostFrameCallback((_) async { await PermissionController.checkAndRequestPermissions(); }); */ startGPSCheckTimer(); // MapControllerの初期化完了を待機するフラグを設定 WidgetsBinding.instance.addPostFrameCallback((_) { //checkGPSDataReceived(); removed 2024-5-4 isMapControllerReady = true; }); // 要検討:エラーメッセージを表示するなどの適切な処理を追加することを検討してください。 // // locationController からデバイスの受け取るGPS情報を取得し、 // handleLocationUpdate を呼び出している。 // locationController.locationMarkerPositionStream.listen( (locationMarkerPosition) { //if (locationMarkerPosition != null) { handleLocationUpdate(locationMarkerPosition); //} }, onError: (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(); //checkGPSDataReceived(); } void restartGPS(){ // GPSデータのListenを再開する処理を追加 Future.delayed(Duration(seconds: 5), () { locationController.startPositionStream(); inError=false; }); } // コントローラーのクローズ時に呼び出されるライフサイクルメソッドです。 // @override void onClose() { gpsCheckTimer?.cancel(); locationController.stopPositionStream(); super.onClose(); } // 位置情報の更新を処理する関数です。 // 現在位置とスタート地点との距離を計算します。 // 現在位置と前回の位置情報との距離と時間差を確認し、一定の条件を満たす場合はGPSデータをデータベースに追加します。 // // 要検討:GPSデータの追加に失敗した場合のエラーハンドリングを追加することをお勧めします。 // double prevLat = 0.0; // 直前の位置 double prevLon = 0.0; bool gpsDebugMode=false; void handleLocationUpdate(LocationMarkerPosition? position) async { //debugPrint("DestinationController.handleLocationUpdate"); try { //final DestinationController destinationController = Get.find(); //final signalStrength = locationController.getGpsSignalStrength(); okToUseGPS = false; if (position != null) { currentLat = position.latitude; currentLon = position.longitude; if( prevLat==0.0 ){ prevLat = currentLat; prevLon = currentLon; } lastValidGPSLocation = LatLng(currentLat, currentLon); lastValidLat = currentLat; lastValidLon = currentLon; okToUseGPS = true; lastGPSDataReceivedTime = DateTime.now(); hasReceivedGPSData = true; } else { debugPrint("....position is null...."); checkGPSDataReceived(); // 信号強度が低い場合、最後に取得した高いまたは中程度の位置情報を使用 // 但し、最初から高精度のものがない場合、どうするか? // // GPSデータが受信できない場合、最後に有効なGPSデータを使用 position = LocationMarkerPosition( latitude: lastValidGPSLocation.latitude, longitude: lastValidGPSLocation.longitude, accuracy: 0, ); currentLat = position.latitude; currentLon = position.longitude; okToUseGPS = false; /* if (lastValidLat != 0.0 && lastValidLon != 0.0) { currentLat = lastValidLat; currentLon = lastValidLon; okToUseGPS = true; } else { // GPSの届く場所に行って、信号を拾ってください。とメッセージを出す。 position = null; print("GPSの届く場所に行って、信号を拾ってください。"); Get.snackbar( "GPS信号を正確に拾えていません", "空が大きく見えるところへ行ってGPS信号を拾ってください。", icon: const Icon( Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), snackPosition: SnackPosition.TOP, duration: const Duration(seconds: 3), backgroundColor: Colors.yellow, ); } */ } if (okToUseGPS && position != null) { // スタート位置から150m離れたら、ready_for_goal if (distanceToStart() >= 150) { ready_for_goal = true; } var distance = const Distance(); double distanceToDest = distance.as( LengthUnit.Meter, LatLng(position.latitude, position.longitude), LatLng(prevLat, prevLon) ); Duration difference = lastGPSCollectedTime.difference(DateTime.now()) .abs(); // 最後にGPS信号を取得した時刻から10秒以上経過、かつ10m以上経過(普通に歩くスピード) //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}"); LogManager().addLog( "GPS : $currentLat, $currentLon - ${DateTime .now() .hour}:${DateTime .now() .minute}:${DateTime .now() .second}:${DateTime .now() .microsecond}"); if (isInRog.value) { await addGPStoDB(position.latitude, position.longitude); lastGPSCollectedTime = DateTime.now(); prevLat = position.latitude; prevLon = position.longitude; gpsDebugMode ? debugPrint("フロントエンドでのGPS保存(時間差:${difference.inSeconds}, 距離差:${distanceToDest}) : Time=${lastGPSCollectedTime}"):null; } } } } catch(e) { debugPrint("handleLocationUpdate Error: ${e}"); } finally { /* Akira , 2024-4-5 if (position != null && (position.latitude != 0 && position.longitude != 0)) { currentLat = position.latitude; currentLon = position.longitude; } */ if (okToUseGPS) { // 位置情報が取得できた場合、精度に関わらず最後の位置情報を更新 //currentLat = position.latitude; //currentLon = position.longitude; } } } // スタート地点までの距離を計算する関数です。 // double distanceToStart() { if (indexController.locations.isEmpty) { return 1000000000; } //print("=== gfs len == ${indexController.locations[0].collection.length}"); double distanceToDest = double.infinity; if (indexController.locations[0].features.isEmpty) { return distanceToDest; } GeoJSONFeature? gfs = indexController.locations[0].features.firstWhere( (element) => festuretoDestination(element!).cp == -1, orElse: () => null, // Provide a null value if no element is found ); //print("gfs : ${gfs}"); if (gfs == null) { return distanceToDest; } //final currentLocation = getCurrentLocation(); // GPS信号中以上での現在位置 Destination des = festuretoDestination(gfs); //print("=== gfs == ${des.toMap()}"); var distance = const Distance(); distanceToDest = distance.as(LengthUnit.Meter, LatLng(currentLat,currentLon), LatLng(des.lat!, des.lon!)); // LatLng(currentLat, currentLon), LatLng(des.lat!, des.lon!)); //print("==== dist==${distanceToDest}"); return distanceToDest; } // 強制チェックイン距離を取得する関数です。 // int getForcedChckinDistance(Destination dest) { if (dest.checkin_radious == -1) { return 10000000000000000; } int _retValue = 100; if (dest.cp == -1) { return 500; } Destination? ds; GeoJSONFeature? gfs = indexController.locations[0].features.firstWhere( (element) => festuretoDestination(element!).cp == -1, orElse: () => null, // Provide a null value if no element is found ); if (gfs == null) { return _retValue; } ds = festuretoDestination(gfs); var distance = const Distance(); double distanceToDest = distance.as(LengthUnit.Meter, LatLng(dest.lat!, dest.lon!), LatLng(ds.lat!, ds.lon!)); if (distanceToDest <= 500) { return 500; } //print("==== forced dist ==${distanceToDest}"); return _retValue; } // ユーザートークンを読み取る関数です。 // readUserToken() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); indexController.userToken = prefs.getString("user_token"); } // コントローラーの準備完了時に呼び出されるライフサイクルメソッドです。 // @override void onReady() async { await readUserToken(); final token = indexController.userToken; if (token != null && token.isNotEmpty) { await indexController.loadUserDetailsForToken(token); fixMapBound(token); }else { Get.toNamed(AppPages.LOGIN)!.then((value) { if (indexController.currentUser.isNotEmpty) { final tk = indexController.currentUser[0]["token"]; fixMapBound(tk); } else { Get.toNamed(AppPages.TRAVEL); PerfectureService.getSubExt("9").then((value) { if (value != null) { LatLngBounds bnds = LatLngBounds( LatLng(value[1], value[0]), LatLng(value[3], value[2])); indexController.mapController .fitBounds(bnds); //.centerZoomFitBounds(bnds); } }); } }); } // 地図のイベントリスナーを設定 indexController.mapController.mapEventStream.listen((MapEvent mapEvent) { if (mapEvent is MapEventMoveEnd) { indexController.loadLocationsBound(indexController.currentUser[0]["user"]["event_code"]); } }); super.onReady(); } // 地図の境界を修正する関数です。 // void fixMapBound(String token) { //String _token = indexController.currentUser[0]["token"]; indexController.switchPage(AppPages.INDEX); if (isMapControllerReady) { LocationService.getLocationsExt(token).then((value) { if (value != null) { //print("--- loc ext is - $value ----"); LatLngBounds bnds = LatLngBounds( LatLng(value[1], value[0]), LatLng(value[3], value[2])); //print("--- bnds is - $bnds ----"); indexController.mapController.fitBounds( bnds, ); indexController.currentBound.clear(); indexController.currentBound.add(bnds); indexController.loadLocationsBound(indexController.currentUser[0]["user"]["event_code"]); centerMapToCurrentLocation(); } }); } else { // MapControllerの初期化が完了していない場合は、遅延して再試行 Future.delayed(Duration(milliseconds: 100), () { fixMapBound(token); }); } } /* void fixMapBound(String token) { indexController.switchPage(AppPages.INDEX); LocationService.getLocationsExt(token).then((value) { if (value != null) { LatLngBounds bnds = LatLngBounds( LatLng(value[1], value[0]), LatLng(value[3], value[2]), ); if (indexController.isMapControllerReady.value) { indexController.mapController.fitBounds( bnds, ); indexController.currentBound.clear(); indexController.currentBound.add(bnds); indexController.loadLocationsBound(); centerMapToCurrentLocation(); } else { // MapControllerが初期化されるまで待機し、その後fitBoundsを実行 WidgetsBinding.instance.addPostFrameCallback((_) { indexController.mapController.fitBounds( bnds, ); indexController.currentBound.clear(); indexController.currentBound.add(bnds); indexController.loadLocationsBound(); centerMapToCurrentLocation(); }); } } }); } */ // 地図を現在位置に中央揃えする関数です。 // void centerMapToCurrentLocation() { //print("center is ${currentLat}, ${currentLon}"); // Akira ... 状況によって呼ぶか呼ばないか if (currentLat != 0 || currentLon != 0) { indexController.mapController.move(LatLng(currentLat, currentLon), 17.0); } } // 接続状態が変更されたときに呼び出される関数です。 // void connectionChanged(String val) { //print('----- %%%%%%%%%%%%%%%%%%%%% ----- $val'); Map res = {}; if (val == "wifi" || val == "mobile") { //int teamId = indexController.teamId.value; // teamIdを使用 String token = indexController.currentUser[0]["token"]; DatabaseHelper db = DatabaseHelper.instance; db.allRogianing().then((value) { value.forEach((e) async { if (e.rog_action_type == 0) { res = await ExternalService().startRogaining(); } else if (e.rog_action_type == 1) { var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!); res = await ExternalService().makeCheckpoint( e.user_id!, // teamId??? token, getFormatedTime(datetime), e.team_name!, e.cp_number!, e.event_code!, e.image!); } else if (e.rog_action_type == 2) { var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!); res = await ExternalService().makeGoal( e.user_id!, // // teamId??? token, e.team_name!, e.image!, getFormatedTime(datetime), e.event_code!); } if (res.isNotEmpty) { db.deleteRogaining(e.id!); } }); }); } } /* // 位置情報の許可を確認する関数です。 // void checkPermission() async { LocationPermission permission = await Geolocator.checkPermission(); if (permission != LocationPermission.whileInUse || permission != LocationPermission.always) { locationPermission.clear(); locationPermission.add(permission.name); permission = await Geolocator.requestPermission(); } } */ // IDに基づいて目的地を取得する関数です。 // Destination? destinationById(int id) { Destination? d; //print("--- target des - $id ----"); for (Destination ss in destinations) { //print("--- des - ${ss.location_id} ----"); if (ss.location_id == id) { d = ss; break; } } return d; } // 目的地を削除する関数です。 // void deleteDestination(Destination d) { //int id = destinations[index].location_id!; //print("---- index ${destinations[index].location_id!}-----"); for (Destination ss in destinations) { if (ss.location_id == d.location_id) { destinations.remove(ss); break; } } DatabaseHelper db = DatabaseHelper.instance; db.deleteDestination(d.location_id!).then((value) { populateDestinations(); }); dbService.updateDatabase(); } // データベースからすべての目的地を削除する関数です。 // void deleteDBDestinations() { DatabaseHelper db = DatabaseHelper.instance; db.deleteAllDestinations().then((value) { populateDestinations(); }); dbService.updateDatabase(); } // ---------- database ------------------/// // 目的地を追加する関数です。 // void addDestinations(Destination dest) { DatabaseHelper db = DatabaseHelper.instance; db.getDestinationByLatLon(dest.lat!, dest.lon!).then((value) { if (value.isNotEmpty) { db.deleteDestination(value[0].location_id!).then((value) { db.insertDestination(dest).then((value) { //print( // "----- destination controller deleted and inserted destination id $value ---- :::::"); populateDestinations(); }); }); } else { db.insertDestination(dest).then((value) { //print("----- destination controller added as new $value--- :::::"); populateDestinations(); }); } }); dbService.updateDatabase(); } // 目的地の選択状態を切り替える関数です。 // void toggleSelection(Destination dest) async { try { DatabaseHelper db = DatabaseHelper.instance; await db.toggleSelecttion(dest); destinations.clear(); db.getDestinations().then((value) { destinationCount.value = 0; currentSelectedDestinations.clear(); for (Destination d in value) { //print("------ destination controller populating destination-------- ${d.checkedin}-------- :::::"); //print("-----populated----- ${d.toMap()}"); if (d.selected!) { currentSelectedDestinations.add(d); } destinations.add(d); } destinationCount.value = destinations.length; }); } catch( e ){ print('Error in toggleSelection: $e'); Get.snackbar( "画面切り替えでエラー", "画面の切り替えができませんでした", backgroundColor: Colors.red, colorText: Colors.white, icon: const Icon( Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), snackPosition: SnackPosition.TOP, duration: const Duration(seconds: 3), //backgroundColor: Colors.yellow, ); } } // ダイアログを表示する関数です。 // buildShowDialog(BuildContext context) { return showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return const Center( child: CircularProgressIndicator(), ); }); } // 現在地点からの目的地の行列を計算する関数です。 // void destinationMatrixFromCurrentPoint(List points) { //buildShowDialog(Get.context!); MatrixService.getDestinations(points).then((mat) { //print(" matrix is ------- $mat"); matrix = mat; try { indexController.routePoints = []; indexController.routePointLenght.value = 0; DestinationService.getDestinationLine(points, matrix)?.then((value) { indexController.routePoints = value; indexController.routePointLenght.value = indexController.routePoints.length; //Get.toNamed(AppPages.TRAVEL); }); destinationCount.value = destinations.length; } catch (_) { skipGps = false; return; } finally { //Get.back(); isRouteShowing.value = true; } }); } // 目的地のリストを取得してObservable変数を更新する関数です。 // void populateDestinations() { DatabaseHelper db = DatabaseHelper.instance; destinations.clear(); db.getDestinations().then((value) { destinationCount.value = 0; for (Destination d in value) { destinations.add(d); } if (destinations.isEmpty) { rogainingCounted.value = false; } }); } // 目的地の順序を変更する関数です。 // void makeOrder(Destination d, int dir) { DatabaseHelper db = DatabaseHelper.instance; db.updateOrder(d, dir).then((value) { populateDestinations(); }); } }