diff --git a/lib/pages/destination/destination_controller.dart b/lib/pages/destination/destination_controller.dart index edad7fe..3baaf21 100644 --- a/lib/pages/destination/destination_controller.dart +++ b/lib/pages/destination/destination_controller.dart @@ -24,7 +24,7 @@ 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 'package:rogapp/widgets/bottom_sheets/bottom_sheet_normal_point.dart'; import 'dart:async'; import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; @@ -33,14 +33,12 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; -import 'package:geolocator/geolocator.dart'; - // 目的地に関連する状態管理とロジックを担当するクラスです。 // class DestinationController extends GetxController { late LocationSettings locationSettings; // 位置情報の設定を保持する変数です。 - Timer? _GPStimer; // GPSタイマーを保持する変数です。 + //Timer? _GPStimer; // GPSタイマーを保持する変数です。 var destinationCount = 0.obs; // 目的地の数を保持するReactive変数です。 List destinations = [].obs; // 目的地のリストを保持するObservable変数です。 @@ -92,7 +90,20 @@ class DestinationController extends GetxController { int _start = 0; // 開始時刻を保持する変数です。 int chekcs = 0; // チェックポイントの数を保持する変数です。 + var rogainingCounted = false.obs; // ロゲイニングがカウントされたかどうかを示すReactive変数です。 + // destinationController.rogainingCountedは、現在のロゲイニングセッションでポイントがカウントされたかどうかを管理するフラグです。 + // + // このフラグは以下のような状況で使用されます: + // + // ロゲイニングを開始したとき、rogainingCountedはfalseに初期化されます。これは、まだポイントがカウントされていないことを示します。 + // チェックポイントに到着し、チェックインが成功したとき、rogainingCountedはtrueに設定されます。これは、そのセッションでポイントがカウントされたことを示します。 + // ロゲイニングを終了したとき、rogainingCountedは再びfalseに設定されます。これは、次のセッションに備えてフラグをリセットするためです。 + // このフラグは、主に以下の目的で使用されます: + // + // ゴール地点でのロジックの制御:rogainingCountedがtrueの場合、つまりポイントがカウントされている場合にのみ、ゴール処理を実行できます。 + // UI の更新:rogainingCountedの状態に基づいて、適切なメッセージやボタンを表示することができます。 + /* //==== Akira .. GPS信号シミュレーション用 ===== ここから、2024-4-5 @@ -317,15 +328,17 @@ class DestinationController extends GetxController { isAtStart.value = true; if (shouldShowBottomSheet) { shouldShowBottomSheet = false; - if (d.cp == -1) return; + + if (d.cp == -1||d.cp==-2||d.cp==0) return; + Widget bottomSheet = BottomSheetNormalPoint(destination: d); + await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.85)), context: Get.context!, isScrollControlled: true, - builder: ((context) => BottomSheetNew( - destination: d, - ))).whenComplete(() { + builder: ((context) => bottomSheet) + ).whenComplete(() { shouldShowBottomSheet = true; skipGps = false; chekcs = 0; @@ -342,15 +355,17 @@ class DestinationController extends GetxController { isInCheckin.value = true; if (shouldShowBottomSheet) { shouldShowBottomSheet = false; - if (d.cp == -1) return; + + if (d.cp == -1||d.cp==-2||d.cp==0) return; + Widget bottomSheet = BottomSheetNormalPoint(destination: d); + await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, - builder: ((context) => BottomSheetNew( - destination: d, - ))).whenComplete(() { + builder: ((context) => bottomSheet) + ).whenComplete(() { shouldShowBottomSheet = true; skipGps = false; chekcs = 0; @@ -412,14 +427,16 @@ class DestinationController extends GetxController { isInCheckin.value = true; if (shouldShowBottomSheet) { shouldShowBottomSheet = false; - if (d.cp == -1) return; + + if (d.cp == -1||d.cp==-2||d.cp==0) return; + Widget bottomSheet = BottomSheetNormalPoint(destination: d); + await showMaterialModalBottomSheet( expand: true, context: Get.context!, backgroundColor: Colors.transparent, - builder: (context) => BottomSheetNew( - destination: d, - )).whenComplete(() { + builder: (context) => bottomSheet + ).whenComplete(() { shouldShowBottomSheet = true; skipGps = false; chekcs = 0; @@ -506,15 +523,17 @@ class DestinationController extends GetxController { isAtStart.value = true; if (shouldShowBottomSheet) { shouldShowBottomSheet = false; - if (d.cp == -1) return; + + if (d.cp == -1||d.cp==-2||d.cp==0) return; + Widget bottomSheet = BottomSheetNormalPoint(destination: d); + await showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), context: Get.context!, isScrollControlled: true, - builder: ((context) => BottomSheetNew( - destination: d, - ))).whenComplete(() { + builder: ((context) => bottomSheet) + ).whenComplete(() { shouldShowBottomSheet = true; //print("----- finished start -------"); skipGps = false; @@ -932,11 +951,11 @@ class DestinationController extends GetxController { // locationController.locationMarkerPositionStream.listen( (locationMarkerPosition) { - if (locationMarkerPosition != null) { + //if (locationMarkerPosition != null) { handleLocationUpdate(locationMarkerPosition); - } + //} }, onError: (err) { - print("Error: $err"); + print("Location Error: $err"); }); startGame(); @@ -957,7 +976,7 @@ class DestinationController extends GetxController { // void handleLocationUpdate(LocationMarkerPosition? position) async { try { - final DestinationController destinationController = Get.find(); + //final DestinationController destinationController = Get.find(); //final signalStrength = locationController.getGpsSignalStrength(); okToUseGPS = false; @@ -1209,10 +1228,7 @@ class DestinationController extends GetxController { // 地図を現在位置に中央揃えする関数です。 // void centerMapToCurrentLocation() { - assert(() { - print("center is ${currentLat}, ${currentLon}"); - return true; - }()); + //print("center is ${currentLat}, ${currentLon}"); // Akira ... 状況によって呼ぶか呼ばないか if (currentLat != 0 || currentLon != 0) { indexController.mapController.move(LatLng(currentLat, currentLon), 17.0); diff --git a/lib/pages/destination_map/destination_map_page.dart b/lib/pages/destination_map/destination_map_page.dart index 3ec876e..ca7e594 100644 --- a/lib/pages/destination_map/destination_map_page.dart +++ b/lib/pages/destination_map/destination_map_page.dart @@ -12,8 +12,24 @@ import 'package:rogapp/pages/destination/destination_controller.dart'; import 'package:rogapp/pages/index/index_controller.dart'; import 'package:rogapp/utils/text_util.dart'; import 'package:rogapp/widgets/base_layer_widget.dart'; -import 'package:rogapp/widgets/bottom_sheet_new.dart'; +//import 'package:rogapp/widgets/bottom_sheet_new.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_start.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_goal.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_normal_point.dart'; +// FlutterMapウィジェットを使用して、地図を表示します。 +// IndexControllerから目的地のリストを取得し、マーカーとしてマップ上に表示します。 +// マーカーがタップされると、BottomSheetウィジェットを表示します。 +// 現在地の表示、ルートの表示、ベースレイヤーの表示などの機能を提供します。 +// 主なロジック: +// FlutterMapウィジェットを使用して、地図を表示します。 +// IndexControllerから目的地のリストを取得し、MarkerLayerを使用してマーカーを表示します。 +// getMarkerShapeメソッドを使用して、マーカーの見た目をカスタマイズします。目的地の種類に応じて、異なるマーカーを表示します。 +// マーカーがタップされると、festuretoDestinationメソッドを使用してGeoJSONFeatureをDestinationオブジェクトに変換し、showModalBottomSheetを使用してBottomSheetウィジェットを表示します。 +// CurrentLocationLayerを使用して、現在地をマップ上に表示します。 +// PolylineLayerを使用して、ルートをマップ上に表示します。getPointsメソッドを使用して、ルートの座標を取得します。 +// BaseLayerを使用して、マップのベースレイヤーを表示します。 +// class DestinationMapPage extends StatelessWidget { DestinationMapPage({Key? key}) : super(key: key); @@ -54,14 +70,23 @@ class DestinationMapPage extends StatelessWidget { indexController.currentDestinationFeature.add(d); //indexController.getAction(); + Widget bottomSheet; + if (d.cp == -1 || d.cp == 0) { + bottomSheet = BottomSheetStart(destination: d); + } else if (d.cp == -2 || d.cp == 0) { + bottomSheet = BottomSheetGoal(destination: d); + } else { + bottomSheet = BottomSheetNormalPoint(destination: d); + } + showModalBottomSheet( context: Get.context!, isScrollControlled: true, constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.85)), - builder: ((context) => BottomSheetNew( - destination: d, - ))).whenComplete(() { + builder: ((context) => bottomSheet ), + + ).whenComplete(() { //print("---- set skip gps to false -----"); destinationController.skipGps = false; }); diff --git a/lib/pages/index/index_controller.dart b/lib/pages/index/index_controller.dart index 7a3b573..d011fce 100644 --- a/lib/pages/index/index_controller.dart +++ b/lib/pages/index/index_controller.dart @@ -408,11 +408,11 @@ class IndexController extends GetxController { if (value == null) { // APIからのレスポンスがnullの場合 print("LocationService.loadLocationsBound からの回答がnullのため、マップをリロード"); - //DestinationController destinationController = Get.find(); // 追加 - //final tk = currentUser[0]["token"]; // 追加 - //if (tk != null) { // 追加 - // destinationController.fixMapBound(tk); // 追加 - //} // 追加 + DestinationController destinationController = Get.find(); // 追加 + final tk = currentUser[0]["token"]; // 追加 + if (tk != null) { // 追加 + destinationController.fixMapBound(tk); // 追加 + } // 追加 return; } isLoading.value = false; // ローディング状態をfalseに設定 diff --git a/lib/pages/index/index_page.dart b/lib/pages/index/index_page.dart index 2db7e2e..b026b48 100644 --- a/lib/pages/index/index_page.dart +++ b/lib/pages/index/index_page.dart @@ -104,13 +104,39 @@ class IndexPage extends GetView { // タップすることでGPS信号の強弱をシミュレーションできるようにする // Akira 2024-4-5 // + Obx(() { + if (locationController.isSimulationMode) { + return DropdownButton( + value: locationController.getSimulatedSignalStrength(), + onChanged: (value) { + debugPrint("DropDown changed!"); + locationController.setSimulatedSignalStrength(value!); + }, + items: ['low', 'medium', 'high', 'real'] + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ); + } else { + return InkWell( + onTap: () { + locationController.setSimulatedSignalStrength('high'); + }, + child: const Icon(Icons.info), + ); + } + }), + /* Obx(() => locationController.isSimulationMode ? DropdownButton( value: locationController.getSimulatedSignalStrength(), onChanged: (value) { locationController.setSimulatedSignalStrength(value!); }, - items: ['high', 'medium', 'low'] + items: ['low', 'medium', 'high','real'] .map>((String value) { return DropdownMenuItem( value: value, @@ -120,6 +146,7 @@ class IndexPage extends GetView { ) : Container(), ), + */ ], ), // bottomNavigationBar: BottomAppBar( diff --git a/lib/pages/search/search_page.dart b/lib/pages/search/search_page.dart index e6d5469..406c6f6 100644 --- a/lib/pages/search/search_page.dart +++ b/lib/pages/search/search_page.dart @@ -6,7 +6,9 @@ import 'package:rogapp/model/destination.dart'; import 'package:rogapp/pages/destination/destination_controller.dart'; import 'package:rogapp/pages/index/index_controller.dart'; import 'package:rogapp/pages/search/search_controller.dart'; -import 'package:rogapp/widgets/bottom_sheet_new.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_start.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_goal.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_normal_point.dart'; class SearchPage extends StatelessWidget { SearchPage({Key? key}) : super(key: key); @@ -75,15 +77,22 @@ class SearchPage extends StatelessWidget { Destination des = destinationController.festuretoDestination(suggestion); Get.back(); + + Widget bottomSheet; + if (des.cp == -1 || des.cp == 0) { + bottomSheet = BottomSheetStart(destination: des); + } else if (des.cp == -2 || des.cp == 0) { + bottomSheet = BottomSheetGoal(destination: des); + } else { + bottomSheet = BottomSheetNormalPoint(destination: des); + } showModalBottomSheet( constraints: BoxConstraints.loose(Size(Get.width, Get.height * 0.75)), isScrollControlled: true, context: context, - //builder: (context) => BottomSheetWidget(), - builder: ((context) => BottomSheetNew( - destination: des, - ))); + builder: ((context) => bottomSheet) + ); }, suggestionsCallback: (pattern) async { @@ -106,29 +115,6 @@ class SearchPage extends StatelessWidget { }, ), ), - // Obx(() => - // ListView.builder( - // itemCount: searchController.searchResults.length, - // itemBuilder: (context, index){ - // return ListTile( - // title: searchController.searchResults[index].properties!["location_name"] != null ? Text(searchController.searchResults[index].properties!["location_name"]) : Text(""), - // subtitle: searchController.searchResults[index].properties!["category"] != null ? Text(searchController.searchResults[index].properties!["category"]) : Text(""), - // leading: getImage(index), - // onTap: (){ - // indexController.currentFeature.clear(); - // indexController.currentFeature.add(searchController.searchResults[index]); - // Get.back(); - // showModalBottomSheet( - // isScrollControlled: true, - // context: context, - // //builder: (context) => BottomSheetWidget(), - // builder:((context) => BottomSheetNew()) - // ); - // }, - // ); - // }, - // ), - // ) ); } } diff --git a/lib/utils/database_gps.dart b/lib/utils/database_gps.dart index 857c21d..11a0bed 100644 --- a/lib/utils/database_gps.dart +++ b/lib/utils/database_gps.dart @@ -45,20 +45,20 @@ class GpsDatabaseHelper { Future insertGps(GpsData gps) async { try { - print("---- try insering ${gps.toMap()}"); + //print("---- try insering ${gps.toMap()}"); Database db = await instance.database; int? nextOrder = Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(id) FROM gps')); nextOrder = nextOrder ?? 0; nextOrder = nextOrder + 1; gps.id = nextOrder; - print("---- insering ${gps.toMap()}"); + //print("---- insering ${gps.toMap()}"); int res = await db.insert( 'gps', gps.toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); - print("------ database helper insert $res-----------::::::::"); + //print("------ database helper insert $res-----------::::::::"); return res; } catch (err) { print("------ error $err-----------::::::::"); diff --git a/lib/utils/location_controller.dart b/lib/utils/location_controller.dart index 9ee55fc..7e79ca1 100644 --- a/lib/utils/location_controller.dart +++ b/lib/utils/location_controller.dart @@ -35,9 +35,6 @@ class LocationController extends GetxController { StreamSubscription? positionStream; // 位置情報のストリームを保持する変数です。StreamSubscription型で宣言されています。 - //===== Akira Added 2024-4-9 start - // GPSシミュレーション用の変数を追加 - bool isSimulationMode = true; LatLng? lastValidLocation; // GPSシミュレーション用のメソッドを追加 @@ -45,13 +42,52 @@ class LocationController extends GetxController { isSimulationMode = value; } - String latestSignalStrength="low"; + // ====== Akira , GPS信号強度をシミュレート ==== ここから + // + + //===== Akira Added 2024-4-9 start + // GPSシミュレーション用の変数を追加 ===> 本番では false にする。 + bool isSimulationMode = true; + + // GPS信号強度をシミュレートするための変数 + final Rx _simulatedSignalStrength = Rx('low'); + + // GPS信号強度をシミュレートするための関数 + void setSimulatedSignalStrength(String strength) { + if( strength!='real') { + isSimulationMode = true; + _simulatedSignalStrength.value = strength; + latestSignalStrength.value = strength; + }else{ + isSimulationMode = false; + _simulatedSignalStrength.value = strength; + } + } + + // シミュレートされた信号強度を取得するための関数 + String getSimulatedSignalStrength() { + debugPrint("strength : ${_simulatedSignalStrength.value}"); + return _simulatedSignalStrength.value; + } + + // + // ====== Akira , GPS信号強度をシミュレート ==== ここまで + + + // GPS信号が弱い場合のフラグ. 本番では、true,high にする。 + bool isGpsSignalWeak = true; + RxString latestSignalStrength = 'low'.obs; + //final _latestSignalStrength = 'low'.obs; // 初期値を設定 + //String get latestSignalStrength => _latestSignalStrength.value; + Stream get gpsSignalStrengthStream => latestSignalStrength.stream; + // GPS信号の強弱を判断するメソッドを追加. 10m 以内:強、30m以内:中、それ以上:弱 // String getGpsSignalStrength(Position? position) { if (position == null) { - latestSignalStrength="low"; + latestSignalStrength.value = "low"; + isGpsSignalWeak = true; return 'low'; } final accuracy = position.accuracy; @@ -59,13 +95,16 @@ class LocationController extends GetxController { return _simulatedSignalStrength.value; // GPS信号強度シミュレーション }else { if (accuracy <= 10) { - latestSignalStrength="high"; + latestSignalStrength.value = "high"; + isGpsSignalWeak = false; return 'high'; } else if (accuracy <= 30) { - latestSignalStrength="medium"; + latestSignalStrength.value = "medium"; + isGpsSignalWeak = false; return 'medium'; } else { - latestSignalStrength="low"; + latestSignalStrength.value = "low"; + isGpsSignalWeak = true; return 'low'; } } @@ -74,11 +113,18 @@ class LocationController extends GetxController { // 現在位置を調整するメソッドを追加 LatLng? adjustCurrentLocation(Position? position) { if (position == null) { - return null; + if( lastValidLocation!=null ) { + //debugPrint("=== adjustCurrentLocation (Position:Null and using LastValidLocation ${lastValidLocation})==="); + return LatLng(lastValidLocation!.latitude, lastValidLocation!.longitude); + }else { + print("=== adjustCurrentLocation (Position:Null and No LastValidLocation ... )==="); + return null; + } //return lastValidLocation ?? LatLng(0, 0); } final signalStrength = getGpsSignalStrength(position); if (signalStrength == 'high' || signalStrength == 'medium') { + //debugPrint("=== adjustCurrentLocation (Position:Get and return Valid location:${position} ... )==="); lastValidLocation = LatLng(position.latitude, position.longitude); } return lastValidLocation ?? LatLng(position.latitude, position.longitude); @@ -93,25 +139,6 @@ class LocationController extends GetxController { bool isStreamPaused = false; // 位置情報のストリームが一時停止中かどうかを示すフラグです。bool型で宣言されています。 - // ====== Akira , GPS信号強度をシミュレート ==== ここから - // - - // GPS信号強度をシミュレートするための変数 - final Rx _simulatedSignalStrength = Rx('high'); - - // GPS信号強度をシミュレートするための関数 - void setSimulatedSignalStrength(String strength) { - _simulatedSignalStrength.value = strength; - } - - // シミュレートされた信号強度を取得するための関数 - String getSimulatedSignalStrength() { - return _simulatedSignalStrength.value; - } - - // - // ====== Akira , GPS信号強度をシミュレート ==== ここから - // 位置マーカーの位置情報のストリームを取得するゲッター関数です。 // locationMarkerPositionStreamController.streamを返します。 // @@ -223,7 +250,7 @@ class LocationController extends GetxController { return; } - // 位置情報の設定を行います。 + // 位置情報の設定を行います。z11 // Set up the location options const locationOptions = LocationSettings(accuracy: LocationAccuracy.high, distanceFilter: 0); @@ -235,38 +262,30 @@ class LocationController extends GetxController { // positionStream = Geolocator.getPositionStream(locationSettings: locationOptions).listen( (Position? position) { + final signalStrength = getGpsSignalStrength(position); + if (signalStrength == 'low') { + isGpsSignalWeak = true; + } else { + isGpsSignalWeak = false; + } + final adjustedLocation = adjustCurrentLocation(position); - if (adjustedLocation!=null) { + if (adjustedLocation != null) { final locationMarkerPosition = LocationMarkerPosition( latitude: adjustedLocation.latitude, longitude: adjustedLocation.longitude, accuracy: position?.accuracy ?? 0, ); - locationMarkerPositionStreamController.add(locationMarkerPosition); - }else{ + handleLocationUpdate(locationMarkerPosition); + //locationMarkerPositionStreamController.add(locationMarkerPosition); // 位置データ送信 + } else { // 位置情報が取得できなかった場合、 // locationMarkerPositionStreamControllerにnullを追加します。 - locationMarkerPositionStreamController.add(null); + locationMarkerPositionStreamController.add(null); // null 送信? + //forceUpdateLocation(Position? position); + } - /* - if (position != null) { - // ストリームから位置情報を受信した場合、 - // LocationMarkerPositionオブジェクトを作成し、 - // locationMarkerPositionStreamControllerに追加します。 - // - final LocationMarkerPosition locationMarkerPosition = - LocationMarkerPosition( - latitude: position.latitude, - longitude: position.longitude, - accuracy: position.accuracy); - locationMarkerPositionStreamController.add(locationMarkerPosition); - } else { - // 位置情報が取得できなかった場合、 - // locationMarkerPositionStreamControllerにnullを追加します。 - locationMarkerPositionStreamController.add(null); - } - */ - }, + }, onError: (e) { // エラーが発生した場合、locationMarkerPositionStreamControllerにエラーを追加します。 locationMarkerPositionStreamController.addError(e); @@ -314,6 +333,36 @@ class LocationController extends GetxController { } } + void handleLocationUpdate(LocationMarkerPosition? position) async { + if (position != null) { + /* + currentPosition.value = position; + final locationMarkerPosition = LocationMarkerPosition( + latitude: position.latitude, + longitude: position.longitude, + accuracy: position.accuracy, + ); + */ + locationMarkerPositionStreamController.add(position); + } + } + + // このメソッドは、現在の位置情報を locationMarkerPositionStreamController に送信します。 + // + void forceUpdateLocation(Position? position) { + if (position != null) { + final adjustedLocation = adjustCurrentLocation(position); + if (adjustedLocation != null) { + final locationMarkerPosition = LocationMarkerPosition( + latitude: adjustedLocation.latitude, + longitude: adjustedLocation.longitude, + accuracy: position.accuracy, + ); + locationMarkerPositionStreamController.add(locationMarkerPosition); + } + } + } + // コントローラーのクローズ時に呼び出されるライフサイクルメソッドです。 // positionStreamをキャンセルします。 // diff --git a/lib/widgets/bottom_sheets/bottom_sheet_base.dart b/lib/widgets/bottom_sheets/bottom_sheet_base.dart new file mode 100644 index 0000000..a8d48b8 --- /dev/null +++ b/lib/widgets/bottom_sheets/bottom_sheet_base.dart @@ -0,0 +1,325 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:rogapp/model/destination.dart'; +import 'package:rogapp/pages/index/index_controller.dart'; +import 'package:rogapp/utils/const.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:rogapp/pages/destination/destination_controller.dart'; + + +class BottomSheetBase extends StatelessWidget { + + BottomSheetBase({super.key, required this.destination,this.isAlreadyCheckedIn = false}); + + final IndexController indexController = Get.find(); + final DestinationController destinationController = Get.find(); + + final bool isAlreadyCheckedIn; // すでにチェックイン済みかどうかのフラグ + final Destination destination; // 目的地オブジェクト + + + // そのポイントの写真を取得するメソッド + // + Image getImage() { + String serverUrl = ConstValues.currentServer(); + + // currentDestinationFeature(destinationそのもの)のphotoデータのチェック + // Nouffer のコードでは、indexController.rogMode によって、destination(currentDestinationFeature) かGeoJSONFeature(currentFeature)の違いで処理が分かれていた。 + // しかし、現在のコードでは GeoJSONFeature gf = indexController.currentFeature[0]; は使われていない。 + // + if (indexController.currentDestinationFeature[0].photos! == "") { + // 定義されていなければ、empty_image.png を選択 + return const Image(image: AssetImage('assets/images/empty_image.png')); + } else { + String photo = indexController.currentDestinationFeature[0].photos!; + if (photo.contains('http')) { + // photo にhttpの文字が含まれていれば、ネットから拾ってくる。エラーならempty_image.pngを表示。 + return Image( + image: NetworkImage( + indexController.currentDestinationFeature[0].photos!, + ), + errorBuilder: + (BuildContext context, Object exception, StackTrace? stackTrace) { + return Image.asset("assets/images/empty_image.png"); + }, + ); + } else { + // photo にhttpの文字が含まれていなければ、指定サーバー($serverUrl/media/compressed/*)から拾ってくる。エラーならempty_image.pngを表示。 + return Image( + image: NetworkImage( + '$serverUrl/media/compressed/${indexController.currentDestinationFeature[0].photos!}', + ), + errorBuilder: + (BuildContext context, Object exception, StackTrace? stackTrace) { + return Image.asset("assets/images/empty_image.png"); + }, + ); + } + } + } + + // URLを開くためのメソッドです。 + // url_launcherパッケージを使用して、指定されたURLを開きます。 + // + void _launchURL(url) async { + if (!await launchUrl(url)) throw 'Could not launch $url'; + } + + // 指定されたlocationidが目的地リストに含まれているかどうかを確認するメソッドです。 + // destinationController.destinationsリストを走査し、locationidが一致する目的地があるかどうかを返します。 + // + bool isInDestination(String locationid) { + int lid = int.parse(locationid); + if (destinationController.destinations + .where((element) => element.location_id == lid) + .isNotEmpty) { + return true; + } else { + return false; + } + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: buildWidgets(context), + ), + ); + } + + // このメソッドはListを返します。 + List buildWidgets(BuildContext context) { + return [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + MaterialButton( + onPressed: () { + Get.back(); + }, + color: Colors.blue, + textColor: Colors.white, + padding: const EdgeInsets.all(16), + shape: const CircleBorder(), + child: const Icon(Icons.arrow_back_ios, size: 14), + ), + Expanded( + child: Container( + alignment: Alignment.center, + child: Obx(() => Text("${destination.sub_loc_id} : ${destination.name}", + style: const TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold))), + ), + ), + ], + ), + ), + Row( + children: [Expanded(child: SizedBox(height: 260.0, child: Obx(() => getImage())))], + ), + Obx(() => Padding( + padding: const EdgeInsets.all(8.0), + child: getDetails(context), + )), + const SizedBox(height: 60.0), + ]; + } + + Widget getDetails(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const Icon(Icons.roundabout_left), + const SizedBox( + width: 8.0, + ), + destination.address != null && destination.address!.isNotEmpty + ? getDetailsItem( + context, + "address".tr, + destination.address ?? '', + ) + : const SizedBox( + width: 0.0, + height: 0, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const Icon(Icons.phone), + const SizedBox( + width: 8.0, + ), + destination.phone != null && destination.phone!.isNotEmpty + ? getDetailsItem( + context, + "telephone".tr, + destination.phone ?? '', + ) + : const SizedBox( + width: 0.0, + height: 0, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const Icon(Icons.email), + const SizedBox( + width: 8.0, + ), + destination.email != null && destination.email!.isNotEmpty + ? getDetailsItem( + context, + "email".tr, + destination.email ?? '', + ) + : const SizedBox( + width: 0.0, + height: 0, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const Icon(Icons.language), + const SizedBox( + width: 8.0, + ), + destination.webcontents != null && + destination.webcontents!.isNotEmpty + ? getDetailsItem( + context, + "web".tr, + destination.webcontents ?? '', + isUrl: true, + ) + : const SizedBox( + width: 0.0, + height: 0, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const SizedBox( + width: 8.0, + ), + indexController.currentFeature[0] + .properties!["remark"] != + null && + (indexController.currentFeature[0] + .properties!["remark"] as String) + .isNotEmpty + ? getDetailsItem( + context, + "remarks".tr, + indexController.currentFeature[0] + .properties!["remark"] ?? + '', + isUrl: false) + : const SizedBox( + width: 0.0, + height: 0, + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.onPrimaryContainer), + onPressed: () async { + // print( + // "dist to start ${destinationController.distanceToStart()}"); + Get.back(); + //print("---- go to ----"); + // GeoJSONMultiPoint mp = indexController + // .currentFeature[0] as GeoJSONMultiPoint; + Position position = + await Geolocator.getCurrentPosition( + desiredAccuracy: + LocationAccuracy.bestForNavigation, + forceAndroidLocationManager: true); + //print("------- position -------- $position"); + Destination ds = Destination( + lat: position.latitude, + lon: position.longitude); + + Destination tp = Destination( + lat: destination.lat, lon: destination.lon); + + destinationController + .destinationMatrixFromCurrentPoint([ds, tp]); + + // TODO: Implement "ここへ行く" functionality + }, + child: Text( + "ルート", + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + const SizedBox( + width: 10, + ), + ], + ), + ], + ); + } + + + + Widget getDetailsItem(BuildContext context, String label, String text, + {bool isUrl = false}) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(label), + const SizedBox( + width: 10.0, + ), + InkWell( + onTap: () { + if (isUrl) { + _launchURL(destination.webcontents); + } + }, + child: SizedBox( + width: MediaQuery.of(context).size.width - + (MediaQuery.of(context).size.width * 0.35), + child: Text( + text, + textAlign: TextAlign.justify, + style: TextStyle( + color: isUrl ? Colors.blue : Colors.black, + ), + ), + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/widgets/bottom_sheets/bottom_sheet_buy_point.dart b/lib/widgets/bottom_sheets/bottom_sheet_buy_point.dart new file mode 100644 index 0000000..be9f9e8 --- /dev/null +++ b/lib/widgets/bottom_sheets/bottom_sheet_buy_point.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:rogapp/pages/destination/destination_controller.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_normal_point.dart'; + +class BottomSheetBuyPoint extends BottomSheetNormalPoint { + BottomSheetBuyPoint({super.key, required super.destination}); + + final DestinationController destinationController = Get.find(); + + @override + List buildWidgets(BuildContext context) { + // super.buildWidgets(context) を継承して、追加のウィジェットをリストに組み込む + return [ + ...super.buildWidgets(context), + ElevatedButton( + onPressed: () async { + // 「買い物チェックイン」ボタンがタップされた際の処理 + // TODO: Implement QR code scanning and buy point processing logic + }, + child: const Text('買い物チェックイン'), + ), + ]; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: buildWidgets(context), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/bottom_sheets/bottom_sheet_goal.dart b/lib/widgets/bottom_sheets/bottom_sheet_goal.dart new file mode 100644 index 0000000..715b2ef --- /dev/null +++ b/lib/widgets/bottom_sheets/bottom_sheet_goal.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:rogapp/model/destination.dart'; +import 'package:rogapp/pages/destination/destination_controller.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_base.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:rogapp/pages/camera/camera_page.dart'; +import 'dart:ui' as ui; + +class BottomSheetGoal extends BottomSheetBase { + BottomSheetGoal({super.key, required super.destination}); + + final DestinationController destinationController = Get.find(); + + @override + List buildWidgets(BuildContext context) { + // super.buildWidgets(context) を継承して、追加のウィジェットをリストに組み込む + return [ + ...super.buildWidgets(context), + ElevatedButton( + onPressed: destinationController.distanceToStart() <= 100 + ? () async { + // 「ゴール時間撮影」ボタンがタップされた際の処理 + Destination cdest = destinationController.festuretoDestination(indexController.currentFeature[0]); + var distance = const Distance(); + double distanceToDest = distance.as( + LengthUnit.Meter, + LatLng( + destinationController.currentLat, destinationController.currentLon), + LatLng(cdest.lat!, cdest.lon!)); + + if (destinationController.rogainingCounted.value == true && + // destinationController.distanceToStart() <= 500 && ... GPS信号が弱い時でもOKとする。 + (destinationController.distanceToStart() <= 500 || destinationController.isGpsSignalWeak() ) && + destination.cp == -1 && + DestinationController.ready_for_goal == true) { + + destinationController.isAtGoal.value = true; + destinationController.photos.clear(); + await showModalBottomSheet( + constraints: BoxConstraints.loose( + ui.Size(Get.width, Get.height * 0.75)), + context: Get.context!, + isScrollControlled: true, + builder: ((context) => CameraPage( + destination: destination, + ))).whenComplete(() { + destinationController.skipGps = false; + destinationController.chekcs = 0; + destinationController.isAtGoal.value = false; + }); + + } + } + : null, + child: const Text('ゴール時間撮影'), + ), + ]; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: buildWidgets(context), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/bottom_sheets/bottom_sheet_normal_point.dart b/lib/widgets/bottom_sheets/bottom_sheet_normal_point.dart new file mode 100644 index 0000000..bc68202 --- /dev/null +++ b/lib/widgets/bottom_sheets/bottom_sheet_normal_point.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:rogapp/pages/destination/destination_controller.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_base.dart'; + +class BottomSheetNormalPoint extends BottomSheetBase { + BottomSheetNormalPoint({super.key, required super.destination}); + + final DestinationController destinationController = Get.find(); + + @override + List buildWidgets(BuildContext context) { + // super.buildWidgets(context) を継承して、追加のウィジェットをリストに組み込む + return [ + ...super.buildWidgets(context), + ElevatedButton( + onPressed: destinationController.distanceToStart() <= 100 + ? () async { + if (destinationController.rogainingCounted.value == true && + (destinationController.distanceToStart() <= 500 || + destinationController.isGpsSignalWeak()) && + destination.cp! > 0) { + // isAlreadyCheckedIn == true + // 「チェックイン撮影」ボタンがタップされた際の処理 + try { + Get.back(); + await destinationController.callforCheckin(destination); + // ここで、CameraPageへの遷移が行われる。 + + } catch (e) { + // エラーハンドリング + Get.snackbar( + 'Error', + 'An error occurred while processing check-in.', + backgroundColor: Colors.red, + colorText: Colors.white, + duration: Duration(seconds: 3), + ); + // 必要に応じてエラーログを記録 + print('Error processing check-in: $e'); + } + } else if (destination.checkedin == true) { // 取り消しの場合? + ; + } + } + : null, + + child: Obx( () => Text( + destinationController.isInRog.value ? 'チェックイン取消' : 'チェックイン', + ), + ), + ), + ]; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: buildWidgets(context), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/bottom_sheets/bottom_sheet_start.dart b/lib/widgets/bottom_sheets/bottom_sheet_start.dart new file mode 100644 index 0000000..85f4993 --- /dev/null +++ b/lib/widgets/bottom_sheets/bottom_sheet_start.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:rogapp/pages/destination/destination_controller.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_base.dart'; +import 'package:rogapp/main.dart'; +import 'package:rogapp/services/external_service.dart'; + +class BottomSheetStart extends BottomSheetBase { + + BottomSheetStart({super.key, required super.destination}); + + final DestinationController destinationController = Get.find(); + + @override + List buildWidgets(BuildContext context) { + // super.buildWidgets(context) を継承して、追加のウィジェットをリストに組み込む + return [ + ...super.buildWidgets(context), + ElevatedButton( + onPressed: destinationController.distanceToStart() <= 100 + ? () async { + // ボタンがタップされた際の処理 + // Check conditions to show confirmation dialog + if (destinationController.isInRog.value == false && + (destinationController.distanceToStart() <= 500 || destinationController.isGpsSignalWeak() ) && //追加 Akira 2024-4-5 + (destination.cp == -1 || destination.cp == 0 ) && + destinationController.rogainingCounted.value == false) { + // ロゲがまだ開始されていない: destinationController.isInRog.value == false + // かつ (開始ポイントまでの距離が 500m 以内 destinationController.distanceToStart() <= 500 + // または GPS信号強度が弱い場合: destinationController.isGpsSignalWeak() + // ) かつ + // そのポイントのCP番号が -1 または 0 の場合: + // かつ ポイントがカウントされていない場合: destinationController.rogainingCounted.value=false + // にロゲイニング開始ができる。 + // + print("counted ${destinationController.rogainingCounted.value}"); + + // Show confirmation dialog + Get.dialog( + AlertDialog( + title: const Text("確認"), //confirm + content: const Text( + "ロゲを開始すると、今までのロゲデータが全てクリアされます。本当に開始しますか?"), //are you sure + actions: [ + TextButton( + child: const Text("いいえ"), //no + onPressed: () { + Get.back(); // Close the dialog + }, + ), + TextButton( + child: const Text("はい"), //yes + onPressed: () async { + // Clear data and start game logic here + destinationController.isInRog.value = true; + destinationController.resetRogaining(); + destinationController.addToRogaining( + destinationController.currentLat, + destinationController.currentLon, + destination.location_id!, + ); + saveGameState(); // main.dart 参照 // ゲームの状態を保存する。 + await ExternalService().startRogaining(); + Get.back(); // Close the dialog and potentially navigate away + }, + ), + ], + ), + barrierDismissible: + false, // User must tap a button to close the dialog + ); + } else if (destinationController.isInRog.value == true && + destination.cp != -1 && destination.cp != 0 ) { + //print("counted ${destinationController.rogainingCounted.value}"); + // Existing logic for other conditions + + Get.back(); + await destinationController.callforCheckin(destination); + } else { + return; + } + + } + : null, + child: Obx( + () => Text( + destinationController.isInRog.value ? '競技中' : 'ロゲ開始', + ), + ), + ), + ]; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: buildWidgets(context), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/bottom_sheets/bottom_sheet_start_goal.dart b/lib/widgets/bottom_sheets/bottom_sheet_start_goal.dart new file mode 100644 index 0000000..766dc1f --- /dev/null +++ b/lib/widgets/bottom_sheets/bottom_sheet_start_goal.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:rogapp/model/destination.dart'; +import 'package:rogapp/pages/destination/destination_controller.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_base.dart'; + +class BottomSheetStartGoal extends BottomSheetBase { + BottomSheetStartGoal({Key? key, required Destination destination}) + : super(key: key, destination: destination); + + final DestinationController destinationController = + Get.find(); + + @override + List buildWidgets(BuildContext context) { + // super.buildWidgets(context) を継承して、追加のウィジェットをリストに組み込む + return [ + ...super.buildWidgets(context), + ElevatedButton( + onPressed: destinationController.distanceToStart() <= 100 + ? () async { + // 「ロゲ開始」または「ゴール時間撮影」ボタンがタップされた際の処理 + // TODO: Implement start game or goal time capture logic based on game state + } + : null, + child: Obx( + () => Text( + destinationController.isInRog.value ? 'ゴール時間撮影' : 'ロゲ開始', + ), + ), + ), + ]; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: buildWidgets(context), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/destination_widget.dart b/lib/widgets/destination_widget.dart index 7a59b0c..ee72818 100644 --- a/lib/widgets/destination_widget.dart +++ b/lib/widgets/destination_widget.dart @@ -5,7 +5,9 @@ import 'package:rogapp/pages/destination/destination_controller.dart'; import 'package:rogapp/pages/index/index_controller.dart'; import 'package:rogapp/utils/const.dart'; import 'package:rogapp/utils/database_helper.dart'; -import 'package:rogapp/widgets/bottom_sheet_new.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_start.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_goal.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_normal_point.dart'; import 'package:timeline_tile/timeline_tile.dart'; class DestinationWidget extends StatelessWidget { @@ -192,15 +194,23 @@ class DestinationWidget extends StatelessWidget { // "--- ndexController.currentDestinationFeature ----- ${indexController.currentDestinationFeature[0].name} ----"); //indexController.getAction(); + Widget bottomSheet; + if (fs.cp == -1 || fs.cp == 0) { + bottomSheet = BottomSheetStart(destination: fs); + } else if (fs.cp == -2 || fs.cp == 0) { + bottomSheet = BottomSheetGoal(destination: fs); + } else { + bottomSheet = BottomSheetNormalPoint(destination: fs); + } + showModalBottomSheet( constraints: BoxConstraints.loose( Size(Get.width, Get.height * 0.85)), context: context, isScrollControlled: true, //builder:((context) => BottomSheetWidget()) - builder: ((context) => BottomSheetNew( - destination: fs, - ))); + builder: ((context) => bottomSheet) + ); } }, onLongPress: () { diff --git a/lib/widgets/gps_status.dart b/lib/widgets/gps_status.dart index 6ee5c16..45dd6cd 100644 --- a/lib/widgets/gps_status.dart +++ b/lib/widgets/gps_status.dart @@ -10,7 +10,7 @@ class GpsSignalStrengthIndicator extends StatelessWidget { // コンストラクタにminimizedパラメータを追加し、デフォルト値をfalseに設定 GpsSignalStrengthIndicator({ - Key? key, + super.key, required this.locationController, this.minimized = false, // ここでデフォルト値を指定 }); @@ -19,8 +19,8 @@ class GpsSignalStrengthIndicator extends StatelessWidget { Widget build(BuildContext context) { // final LocationController locationController = Get.find(); return Obx(() { - String signalStrength = locationController.latestSignalStrength; - print("signalStrength=${signalStrength}"); + String signalStrength = locationController.latestSignalStrength.value; + debugPrint("GpsSignalStrengthIndicator : signalStrength=${signalStrength}"); IconData iconData; Color backgroundColor; String text; diff --git a/lib/widgets/list_widget.dart b/lib/widgets/list_widget.dart index d944666..a371252 100644 --- a/lib/widgets/list_widget.dart +++ b/lib/widgets/list_widget.dart @@ -6,7 +6,9 @@ import 'package:rogapp/pages/destination/destination_controller.dart'; import 'package:rogapp/pages/index/index_controller.dart'; import 'package:rogapp/services/maxtrix_service.dart'; import 'package:rogapp/utils/const.dart'; -import 'package:rogapp/widgets/bottom_sheet_new.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_start.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_goal.dart'; +import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_normal_point.dart'; class ListWidget extends StatefulWidget { const ListWidget({Key? key}) : super(key: key); @@ -15,6 +17,15 @@ class ListWidget extends StatefulWidget { State createState() => _ListWidgetState(); } +// IndexControllerから目的地のリストを取得し、ListView.builderを使用してリストを表示します。 +// 各リストアイテムは、目的地の画像、名前、カテゴリ、サブロケーションID、現在地からの距離を表示します。 +// リストアイテムがタップされると、changeCurrentFeatureメソッドを呼び出して現在の目的地を更新し、 BottomSheetウィジェットを表示します。 +// 主なロジック: +// IndexControllerから目的地のリストを取得し、ListView.builderを使用してリストを構築します。 +// getImageメソッドを使用して、目的地の画像を取得し表示します。画像が存在しない場合は、デフォルトの画像を表示します。 +// matrixDistanceメソッドを使用して、現在地から目的地までの距離を計算し表示します。 +// リストアイテムがタップされると、changeCurrentFeatureメソッドを呼び出して現在の目的地を更新し、showModalBottomSheetを使用してBottomSheetウィジェットを表示します。 +// class _ListWidgetState extends State { final IndexController indexController = Get.find(); @@ -42,6 +53,7 @@ class _ListWidgetState extends State { } } + // 未使用? void changeCurrentFeature(GeoJSONFeature fs) { if (indexController.currentFeature.isNotEmpty) { indexController.currentFeature.clear(); @@ -117,15 +129,23 @@ class _ListWidgetState extends State { Destination des = destinationController.festuretoDestination(gf); changeCurrentFeature(gf); + + Widget bottomSheet; + if (des.cp == -1 || des.cp == 0) { + bottomSheet = BottomSheetStart(destination: des); + } else if (des.cp == -2 || des.cp == 0) { + bottomSheet = BottomSheetGoal(destination: des); + } else { + bottomSheet = BottomSheetNormalPoint(destination: des); + } + showModalBottomSheet( constraints: BoxConstraints.loose( Size(Get.width, Get.height * 0.85)), isScrollControlled: true, context: context, - //builder: (context) => BottomSheetWidget(), - builder: ((context) => BottomSheetNew( - destination: des, - ))); + builder: ((context) => bottomSheet ), + ); }, leading: getImage(index), title: indexController.locations[0].features[index]! diff --git a/lib/widgets/map_widget.dart b/lib/widgets/map_widget.dart index 905f064..7485bd6 100644 --- a/lib/widgets/map_widget.dart +++ b/lib/widgets/map_widget.dart @@ -2,7 +2,6 @@ import 'dart:async'; 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:flutter_map_marker_cluster/flutter_map_marker_cluster.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:geojson_vi/geojson_vi.dart'; import 'package:get/get.dart'; @@ -17,22 +16,8 @@ import 'package:rogapp/widgets/base_layer_widget.dart'; import 'package:rogapp/widgets/bottom_sheet_new.dart'; import 'package:rogapp/widgets/current_position_widget.dart'; import 'package:rogapp/widgets/game_state_view.dart'; +import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart'; -// map_widget.dartファイルは、アプリ内の地図表示を担当するウィジェットを定義しています。以下に、主要な部分を解説します。 -// 地図表示に関連する主要な機能を提供しています。以下のような機能が含まれています。 -// -// 地図の表示と操作 -// マーカーの表示とカスタマイズ -// ルートの表示 -// 現在位置の表示 -// アイドル状態の処理 -// ローディングインジケーターの表示 -// ゲーム状態の表示 -// 現在位置ボタンの表示 - - -// StatefulWidgetを継承したクラスで、地図表示のメインウィジェットです。 -// class MapWidget extends StatefulWidget { const MapWidget({Key? key}) : super(key: key); @@ -40,36 +25,131 @@ class MapWidget extends StatefulWidget { State createState() => _MapWidgetState(); } -// MapWidgetの状態を管理するクラスです。 -// class _MapWidgetState extends State { final IndexController indexController = Get.find(); - // IndexControllerのインスタンスを保持します。 final DestinationController destinationController = Get.find(); - // DestinationControllerのインスタンスを保持します。 final LocationController locationController = Get.find(); - // LocationControllerのインスタンスを保持します。 - StreamSubscription? subscription; // 地図イベントの購読を保持します。 - Timer? _timer; // アイドル状態のタイマーを保持します。 + late MapController mapController; + final Completer mapControllerCompleter = Completer(); - // 地図上のマーカーのUIを生成するメソッドです。 - // GeoJSONFeatureを受け取り、マーカーのUIを返します。 - // - Widget getMarkerShape(GeoJSONFeature i, BuildContext context) { + StreamSubscription? subscription; + Timer? _timer; + + Map _markerCache = {}; + List _markers = []; + + @override + void initState() { + super.initState(); + _startIdleTimer(); + mapController = MapController(); + indexController.mapController = mapController; + //_initMarkers(); + +// indexController.mapController = MapController(initCompleter: mapControllerCompleter); + + } + + @override + void dispose() { + mapController?.dispose(); + _timer?.cancel(); + super.dispose(); + } + + void _startIdleTimer() { + _timer = Timer(const Duration(milliseconds: (1000 * 10)), _centerMapOnUser); + } + + void _resetTimer() { + _timer?.cancel(); + _startIdleTimer(); + } + + void _centerMapOnUser() { + destinationController.centerMapToCurrentLocation(); + } + + Future _initMarkers() async { + List markers = await _getMarkers(); + setState(() { + _markers = markers; + }); + } + + Future> _getMarkers() async { + List markers = []; + if (indexController.locations.isNotEmpty && indexController.locations[0].features.isNotEmpty) { + for (var feature in indexController.locations[0].features) { + GeoJSONMultiPoint point = feature!.geometry as GeoJSONMultiPoint; + LatLng latLng = LatLng(point.coordinates[0][1], point.coordinates[0][0]); + + markers.add(Marker( + point: latLng, + width: 30.0, + height: 30.0, + child: getMarkerShape(feature), + )); + + /* + if (_markerCache.containsKey(latLng)) { + markers.add(_markerCache[latLng]!); + } else { + Marker marker = Marker( + point: latLng, + width: 30.0, + height: 30.0, + child: getMarkerShape(feature), +// child: getMarkerShape(feature, context), + ); + _markerCache[latLng] = marker; + markers.add(marker); + } + */ + } + } + return markers; + } + + /* + Future> _getMarkers() async { + List markers = []; + for (var feature in indexController.locations[0].features) { + GeoJSONMultiPoint point = feature!.geometry as GeoJSONMultiPoint; + LatLng latLng = LatLng(point.coordinates[0][1], point.coordinates[0][0]); + + if (_markerCache.containsKey(latLng)) { + markers.add(_markerCache[latLng]!); + } else { + Marker marker = Marker( + point: latLng, + width: 30.0, + height: 30.0, + builder: (ctx) => getMarkerShape(feature, ctx), + //child: null, + ); + _markerCache[latLng] = marker; + markers.add(marker); + } + } + return markers; + } + */ + +// Widget getMarkerShape(GeoJSONFeature i, BuildContext context) { + Widget getMarkerShape(GeoJSONFeature i) { + final String labelText = TextUtils.getDisplayTextFeture(i); + //final double maxWidth = labelText.length * 40.0; GeoJSONMultiPoint p = i.geometry as GeoJSONMultiPoint; - //print("lat is ${p.geoSerie!.geoPoints[0].latitude} and lon is ${p.geoSerie!.geoPoints[0].longitude}"); - //RegExp regex = RegExp(r'([.]*0)(?!.*\d)'); return InkWell( onTap: () { GeoJSONFeature? fs = indexController.getFeatureForLatLong( p.coordinates[0][1], p.coordinates[0][0]); - //print("------- fs $fs------"); if (fs != null) { indexController.currentFeature.clear(); indexController.currentFeature.add(fs); - //print("----- fs is ${fs.properties!['photos']}"); Destination des = destinationController.festuretoDestination(fs); @@ -77,15 +157,14 @@ class _MapWidgetState extends State { db.getDestinationByLatLon(des.lat!, des.lon!).then((value) { destinationController.shouldShowBottomSheet = false; showModalBottomSheet( - constraints: - BoxConstraints.loose(Size(Get.width, Get.height * 0.85)), - context: context, - isScrollControlled: true, - isDismissible: true, - builder: ((context) => BottomSheetNew( - destination: des, isAlreadyCheckedIn: value.isNotEmpty)) - //builder:((context) => BottomSheetWidget()) - ).whenComplete(() { + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.85)), + context: context, + isScrollControlled: true, + isDismissible: true, + builder: ((context) => BottomSheetNew( + destination: des, isAlreadyCheckedIn: value.isNotEmpty)), + ).whenComplete(() { destinationController.shouldShowBottomSheet = true; destinationController.skipGps = false; }); @@ -94,82 +173,86 @@ class _MapWidgetState extends State { }, child: Stack( fit: StackFit.expand, - //mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - height: 32, - width: 32, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.transparent, - border: Border.all( - color: i.properties!['buy_point'] > 0 - ? Colors.blue - : Colors.red, - width: 3, - style: BorderStyle.solid)), - child: Stack( - alignment: Alignment.center, - children: [ - const Icon( - Icons.circle, - size: 6.0, - ), - i.properties!['cp'] == -1 - ? Transform.translate( - offset: const Offset(18, 0), - child: Transform.rotate( - alignment: Alignment.centerLeft, - origin: Offset.fromDirection(1, 26), - angle: 270 * pi / 180, - child: const Icon( - Icons.play_arrow_outlined, - color: Colors.red, - size: 70, - )), - ) - : Container( - color: Colors.transparent, + height: 32, + width: 32, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.transparent, + border: Border.all( + color: i.properties!['buy_point'] > 0 ? Colors.blue : Colors.red, + width: 3, + style: BorderStyle.solid, + ), + ), + child: Stack( + alignment: Alignment.center, + children: [ + const Icon( + Icons.circle, + size: 6.0, + ), + i.properties!['cp'] == -1 + ? Transform.translate( + offset: const Offset(18, 0), + child: Transform.rotate( + alignment: Alignment.centerLeft, + origin: Offset.fromDirection(1, 26), + angle: 270 * pi / 180, + child: const Icon( + Icons.play_arrow_outlined, + color: Colors.red, + size: 70, + ), ), - ], - )), + ) + : Container( + color: Colors.transparent, + ), + ], + ), + ), Transform.translate( - offset: const Offset(45, 0), + offset: const Offset(30, 0), child: Align( alignment: Alignment.center, child: Container( - color: Colors.purple.withOpacity(0.2), - // child: Text(TextUtils.getDisplayTextFeture(i), - // style: const TextStyle( - // fontSize: 16, - // fontWeight: FontWeight.bold, - // color: Colors.red, - // ))), - child: Stack( - children: [ - // Text with white outline - Text( - TextUtils.getDisplayTextFeture(i), - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, - foreground: Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = 1 - ..color = Colors.white, - ), + color: Colors.transparent, + constraints: const BoxConstraints(maxWidth: 500), // 最大幅を設定 + //constraints: BoxConstraints(maxWidth: maxWidth), // 最大幅を設定 + //color: Colors.purple.withOpacity(0.2), + child: Stack( + children: [ + Text( + labelText, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + foreground: Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2 + ..color = Colors.white, ), - // Text with black fill - Text( - TextUtils.getDisplayTextFeture(i), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, - color: Colors.black, - ), + overflow: TextOverflow.ellipsis, // テキストが長すぎる場合に`...`で省略 + //softWrap: true, // 複数行に渡って表示 + //overflow: TextOverflow.visible, // テキストが切れないように + ), + Text( + labelText, + //TextUtils.getDisplayTextFeture(i), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: Colors.black, ), - ], - )), + overflow: TextOverflow.ellipsis, // テキストが長すぎる場合に`...`で省略 + //softWrap: true, // 複数行に渡って表示 + //overflow: TextOverflow.visible, // テキストが切れないように + ), + ], + ), + ), ), ) ], @@ -177,11 +260,7 @@ class _MapWidgetState extends State { ); } - // ルートポイントをLatLngのリストに変換するメソッドです。 - // IndexControllerのroutePointsからLatLngのリストを生成しています。 - // List? getPoints() { - //print("##### --- route point ${indexController.routePoints.length}"); List pts = []; for (PointLatLng p in indexController.routePoints) { LatLng l = LatLng(p.latitude, p.longitude); @@ -190,87 +269,27 @@ class _MapWidgetState extends State { return pts; } - // ウィジェットの初期化時に呼び出されるメソッドです。 - // _startIdleTimerメソッドを呼び出して、アイドル状態のタイマーを開始します。 - // - @override - void initState() { - super.initState(); - _startIdleTimer(); // _startIdleTimerメソッドを呼び出してアイドル状態のタイマーを開始しています。 - } - - // アイドル状態のタイマーを開始するメソッドです。 - // 一定時間後に_centerMapOnUserメソッドを呼び出すようにタイマーを設定しています。 - // - void _startIdleTimer() { - _timer = Timer(const Duration(milliseconds: (1000 * 10)), _centerMapOnUser); - } - - // アイドル状態のタイマーをリセットするメソッドです。 - // - void _resetTimer() { - _timer?.cancel(); - _startIdleTimer(); - } - - // 地図をユーザーの現在位置に中央揃えするメソッドです。 - // - void _centerMapOnUser() { - assert(() { - print("showBottomSheet ${destinationController.shouldShowBottomSheet}"); - return true; - }()); - // 2024-04-03 Akira Log enabled only debug mode.. - // - //if (destinationController.shouldShowBottomSheet) { - destinationController.centerMapToCurrentLocation(); - //} - } - - // ウィジェットのUIを構築するメソッドです。 - // FlutterMapウィジェットを使用して地図を表示します。 - // layersプロパティに、ベースレイヤー、ルートレイヤー、現在位置レイヤー、マーカーレイヤーを設定します。 - // PopupControllerを使用して、ポップアップの制御を行います。 - // IndexControllerのisLoading変数に基づいて、ローディングインジケーターを表示します。 - // GameStateWidgetとCurrentPositionウィジェットを重ねて表示します。 - // @override Widget build(BuildContext context) { - // print( - // "---------- rog mode is ${indexController.rog_mode.value.toString()}----------"); - - final PopupController popupController = PopupController(); + //final PopupController popupController = PopupController(); return Stack( children: [ - // IndexControllerのisLoading変数に基づいて、ローディングインジケーターを表示します。 - // isLoadingがtrueの場合はCircularProgressIndicatorを表示し、falseの場合は地図を表示します。 Obx(() => indexController.isLoading.value == true ? const Padding( padding: EdgeInsets.only(top: 60.0), child: CircularProgressIndicator(), ) : FlutterMap( - // 地図の表示を担当 - mapController: indexController.mapController, + mapController: mapController, + //mapController: indexController.mapController, options: MapOptions( - // 地図の初期設定(最大ズームレベル、初期位置、ズームレベルなど)を行っています。 maxZoom: 18.4, onMapReady: () { - // print("Map controller ready!!"); ... working corretly - indexController.isMapControllerReady.value = true; // Added Akira,2024-4-6 for #2800 - - subscription = indexController.mapController.mapEventStream - .listen((MapEvent mapEvent) { - if (mapEvent is MapEventMoveStart) { - // print(DateTime.now().toString() + - // ' [MapEventMoveStart] START'); - // do something - } - if (mapEvent is MapEventMoveEnd) {} - }); + _initMarkers(); + //indexController.isMapControllerReady.value = true; }, - - initialCenter: const LatLng(37.15319600454702, 139.58765950528198), + initialCenter: + const LatLng(37.15319600454702, 139.58765950528198), bounds: indexController.currentBound.isNotEmpty ? indexController.currentBound[0] : LatLngBounds.fromPoints([ @@ -280,42 +299,39 @@ class _MapWidgetState extends State { initialZoom: 1, interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag, - onPositionChanged: (MapPosition pos, hasGesture) { - // 地図の位置が変更された際の処理を行います。 - //print("map position changed ${pos.center!.latitude}"); if (hasGesture) { _resetTimer(); } indexController.currentBound = [pos.bounds!]; }, - onTap: (_, __) => popupController - .hideAllPopups(), // Hide popup when the map is tapped. + onMapEvent: (MapEvent mapEvent) { + if (mapEvent is MapEventMove) { + destinationController.shouldShowBottomSheet = true; + } + }, + //onTap: (_, __) => popupController.hideAllPopups(), ), children: [ const BaseLayer(), Obx( () => indexController.routePointLenght > 0 ? PolylineLayer( - // ルートの表示を担当 polylines: [ Polyline( - points: getPoints()!, // ルートのポイントを設定しています。 - strokeWidth: 6.0, - color: Colors.indigo), + points: getPoints()!, + strokeWidth: 6.0, + color: Colors.indigo, + ), ], ) : Container(), ), CurrentLocationLayer( - // 現在位置の表示を担当 positionStream: locationController .locationMarkerPositionStreamController.stream, - // locationMarkerPositionStreamController.streamを設定して、現在位置の更新を監視しています。 - alignDirectionOnUpdate: AlignOnUpdate.never, - //turnOnHeadingUpdate: TurnOnHeadingUpdate.never, + //alignDirectionOnUpdate: AlignOnUpdate.never, style: const LocationMarkerStyle( - // styleプロパティで、現在位置のマーカーのスタイルを設定しています。 marker: Stack( children: [ CircleAvatar( @@ -323,70 +339,39 @@ class _MapWidgetState extends State { backgroundColor: Colors.blue, child: Icon(Icons.navigation, color: Colors.white), ), - //if (locationController.getGpsSignalStrength() == 'low') - // child: Icon(Icons.warning, color: Colors.red), ], ), - /* - marker: DefaultLocationMarker( - child: Icon( - Icons.navigation, - color: Colors.yellowAccent, - ), - ), - */ markerSize: Size(27, 27), markerDirection: MarkerDirection.heading, ), + //child: const Icon(Icons.navigation), ), - indexController.locations.isNotEmpty && - indexController.locations[0].features.isNotEmpty - ? MarkerLayer( - // マーカーの表示を担当 - markers: - indexController.locations[0].features.map((i) { - //print("i si ${i.properties!['location_id']}"); - - //RegExp regex = RegExp(r'([.]*0)(?!.*\d)'); - GeoJSONMultiPoint p = - i!.geometry as GeoJSONMultiPoint; - //print( - // "lat is ${p.geoSerie!.geoPoints[0].latitude} and lon is ${p.geoSerie!.geoPoints[0].longitude}"); - return Marker( - alignment: Alignment.center, - height: 27.0, - width: 127.0, - point: LatLng( - p.coordinates[0][1], p.coordinates[0][0]), - child: getMarkerShape(i, context)); - // マーカーのUIを生成しています。 - // マーカーのアイコン、ラベル、色などをカスタマイズしています。 - }).toList(), - ) - : const Center(child: CircularProgressIndicator()), + FutureBuilder>( + future: indexController.locations.isNotEmpty ? _getMarkers() : null, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return const Center(child: Text('マーカーの読み込みに失敗しました')); + } else { + return MarkerLayer(markers: snapshot.data ?? []); + } + }, + ), + //MarkerLayer(markers: indexController.locations.isNotEmpty ? _getMarkers() : []), ], )), const Positioned(top: 0, left: 0, child: GameStateWidget()), - // ゲーム状態の表示を担当。ゲームの状態(開始、終了など)を表示するカスタムウィジェットです。 const Positioned(bottom: 10, right: 10, child: CurrentPosition()), - // 現在位置ボタンの表示を担当。現在位置に移動するためのボタンを表示するカスタムウィジェットです。 StreamBuilder( stream: locationController.locationMarkerPositionStream, builder: (context, snapshot) { - if (snapshot.hasData) { - print("Display current marker"); - } else { - print("Not display current marker"); + if (!snapshot.hasData) { + print("====== Not display current marker"); } return Container(); }, ) - - // const Positioned( - // bottom: 10, - // left: 0, - // child: DebugWidget(), - // ) ], ); } diff --git a/lib/widgets/map_widget.dart.20240413 b/lib/widgets/map_widget.dart.20240413 new file mode 100644 index 0000000..905f064 --- /dev/null +++ b/lib/widgets/map_widget.dart.20240413 @@ -0,0 +1,393 @@ +import 'dart:async'; +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:flutter_map_marker_cluster/flutter_map_marker_cluster.dart'; +import 'package:flutter_polyline_points/flutter_polyline_points.dart'; +import 'package:geojson_vi/geojson_vi.dart'; +import 'package:get/get.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:rogapp/model/destination.dart'; +import 'package:rogapp/pages/destination/destination_controller.dart'; +import 'package:rogapp/pages/index/index_controller.dart'; +import 'package:rogapp/utils/database_helper.dart'; +import 'package:rogapp/utils/location_controller.dart'; +import 'package:rogapp/utils/text_util.dart'; +import 'package:rogapp/widgets/base_layer_widget.dart'; +import 'package:rogapp/widgets/bottom_sheet_new.dart'; +import 'package:rogapp/widgets/current_position_widget.dart'; +import 'package:rogapp/widgets/game_state_view.dart'; + +// map_widget.dartファイルは、アプリ内の地図表示を担当するウィジェットを定義しています。以下に、主要な部分を解説します。 +// 地図表示に関連する主要な機能を提供しています。以下のような機能が含まれています。 +// +// 地図の表示と操作 +// マーカーの表示とカスタマイズ +// ルートの表示 +// 現在位置の表示 +// アイドル状態の処理 +// ローディングインジケーターの表示 +// ゲーム状態の表示 +// 現在位置ボタンの表示 + + +// StatefulWidgetを継承したクラスで、地図表示のメインウィジェットです。 +// +class MapWidget extends StatefulWidget { + const MapWidget({Key? key}) : super(key: key); + + @override + State createState() => _MapWidgetState(); +} + +// MapWidgetの状態を管理するクラスです。 +// +class _MapWidgetState extends State { + final IndexController indexController = Get.find(); + // IndexControllerのインスタンスを保持します。 + final DestinationController destinationController = + Get.find(); + // DestinationControllerのインスタンスを保持します。 + final LocationController locationController = Get.find(); + // LocationControllerのインスタンスを保持します。 + + StreamSubscription? subscription; // 地図イベントの購読を保持します。 + Timer? _timer; // アイドル状態のタイマーを保持します。 + + // 地図上のマーカーのUIを生成するメソッドです。 + // GeoJSONFeatureを受け取り、マーカーのUIを返します。 + // + Widget getMarkerShape(GeoJSONFeature i, BuildContext context) { + GeoJSONMultiPoint p = i.geometry as GeoJSONMultiPoint; + //print("lat is ${p.geoSerie!.geoPoints[0].latitude} and lon is ${p.geoSerie!.geoPoints[0].longitude}"); + //RegExp regex = RegExp(r'([.]*0)(?!.*\d)'); + return InkWell( + onTap: () { + GeoJSONFeature? fs = indexController.getFeatureForLatLong( + p.coordinates[0][1], p.coordinates[0][0]); + //print("------- fs $fs------"); + if (fs != null) { + indexController.currentFeature.clear(); + indexController.currentFeature.add(fs); + //print("----- fs is ${fs.properties!['photos']}"); + + Destination des = destinationController.festuretoDestination(fs); + + DatabaseHelper db = DatabaseHelper.instance; + db.getDestinationByLatLon(des.lat!, des.lon!).then((value) { + destinationController.shouldShowBottomSheet = false; + showModalBottomSheet( + constraints: + BoxConstraints.loose(Size(Get.width, Get.height * 0.85)), + context: context, + isScrollControlled: true, + isDismissible: true, + builder: ((context) => BottomSheetNew( + destination: des, isAlreadyCheckedIn: value.isNotEmpty)) + //builder:((context) => BottomSheetWidget()) + ).whenComplete(() { + destinationController.shouldShowBottomSheet = true; + destinationController.skipGps = false; + }); + }); + } + }, + child: Stack( + fit: StackFit.expand, + //mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 32, + width: 32, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.transparent, + border: Border.all( + color: i.properties!['buy_point'] > 0 + ? Colors.blue + : Colors.red, + width: 3, + style: BorderStyle.solid)), + child: Stack( + alignment: Alignment.center, + children: [ + const Icon( + Icons.circle, + size: 6.0, + ), + i.properties!['cp'] == -1 + ? Transform.translate( + offset: const Offset(18, 0), + child: Transform.rotate( + alignment: Alignment.centerLeft, + origin: Offset.fromDirection(1, 26), + angle: 270 * pi / 180, + child: const Icon( + Icons.play_arrow_outlined, + color: Colors.red, + size: 70, + )), + ) + : Container( + color: Colors.transparent, + ), + ], + )), + Transform.translate( + offset: const Offset(45, 0), + child: Align( + alignment: Alignment.center, + child: Container( + color: Colors.purple.withOpacity(0.2), + // child: Text(TextUtils.getDisplayTextFeture(i), + // style: const TextStyle( + // fontSize: 16, + // fontWeight: FontWeight.bold, + // color: Colors.red, + // ))), + child: Stack( + children: [ + // Text with white outline + Text( + TextUtils.getDisplayTextFeture(i), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + foreground: Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 1 + ..color = Colors.white, + ), + ), + // Text with black fill + Text( + TextUtils.getDisplayTextFeture(i), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + ), + ], + )), + ), + ) + ], + ), + ); + } + + // ルートポイントをLatLngのリストに変換するメソッドです。 + // IndexControllerのroutePointsからLatLngのリストを生成しています。 + // + List? getPoints() { + //print("##### --- route point ${indexController.routePoints.length}"); + List pts = []; + for (PointLatLng p in indexController.routePoints) { + LatLng l = LatLng(p.latitude, p.longitude); + pts.add(l); + } + return pts; + } + + // ウィジェットの初期化時に呼び出されるメソッドです。 + // _startIdleTimerメソッドを呼び出して、アイドル状態のタイマーを開始します。 + // + @override + void initState() { + super.initState(); + _startIdleTimer(); // _startIdleTimerメソッドを呼び出してアイドル状態のタイマーを開始しています。 + } + + // アイドル状態のタイマーを開始するメソッドです。 + // 一定時間後に_centerMapOnUserメソッドを呼び出すようにタイマーを設定しています。 + // + void _startIdleTimer() { + _timer = Timer(const Duration(milliseconds: (1000 * 10)), _centerMapOnUser); + } + + // アイドル状態のタイマーをリセットするメソッドです。 + // + void _resetTimer() { + _timer?.cancel(); + _startIdleTimer(); + } + + // 地図をユーザーの現在位置に中央揃えするメソッドです。 + // + void _centerMapOnUser() { + assert(() { + print("showBottomSheet ${destinationController.shouldShowBottomSheet}"); + return true; + }()); + // 2024-04-03 Akira Log enabled only debug mode.. + // + //if (destinationController.shouldShowBottomSheet) { + destinationController.centerMapToCurrentLocation(); + //} + } + + // ウィジェットのUIを構築するメソッドです。 + // FlutterMapウィジェットを使用して地図を表示します。 + // layersプロパティに、ベースレイヤー、ルートレイヤー、現在位置レイヤー、マーカーレイヤーを設定します。 + // PopupControllerを使用して、ポップアップの制御を行います。 + // IndexControllerのisLoading変数に基づいて、ローディングインジケーターを表示します。 + // GameStateWidgetとCurrentPositionウィジェットを重ねて表示します。 + // + @override + Widget build(BuildContext context) { + // print( + // "---------- rog mode is ${indexController.rog_mode.value.toString()}----------"); + + final PopupController popupController = PopupController(); + return Stack( + children: [ + // IndexControllerのisLoading変数に基づいて、ローディングインジケーターを表示します。 + // isLoadingがtrueの場合はCircularProgressIndicatorを表示し、falseの場合は地図を表示します。 + Obx(() => indexController.isLoading.value == true + ? const Padding( + padding: EdgeInsets.only(top: 60.0), + child: CircularProgressIndicator(), + ) + : FlutterMap( + // 地図の表示を担当 + mapController: indexController.mapController, + options: MapOptions( + // 地図の初期設定(最大ズームレベル、初期位置、ズームレベルなど)を行っています。 + maxZoom: 18.4, + onMapReady: () { + // print("Map controller ready!!"); ... working corretly + indexController.isMapControllerReady.value = true; // Added Akira,2024-4-6 for #2800 + + subscription = indexController.mapController.mapEventStream + .listen((MapEvent mapEvent) { + if (mapEvent is MapEventMoveStart) { + // print(DateTime.now().toString() + + // ' [MapEventMoveStart] START'); + // do something + } + if (mapEvent is MapEventMoveEnd) {} + }); + }, + + initialCenter: const LatLng(37.15319600454702, 139.58765950528198), + bounds: indexController.currentBound.isNotEmpty + ? indexController.currentBound[0] + : LatLngBounds.fromPoints([ + const LatLng(35.03999881162295, 136.40587119778962), + const LatLng(36.642756778706904, 137.95226720406063) + ]), + initialZoom: 1, + interactiveFlags: + InteractiveFlag.pinchZoom | InteractiveFlag.drag, + + onPositionChanged: (MapPosition pos, hasGesture) { + // 地図の位置が変更された際の処理を行います。 + //print("map position changed ${pos.center!.latitude}"); + if (hasGesture) { + _resetTimer(); + } + indexController.currentBound = [pos.bounds!]; + }, + onTap: (_, __) => popupController + .hideAllPopups(), // Hide popup when the map is tapped. + ), + children: [ + const BaseLayer(), + Obx( + () => indexController.routePointLenght > 0 + ? PolylineLayer( + // ルートの表示を担当 + polylines: [ + Polyline( + points: getPoints()!, // ルートのポイントを設定しています。 + strokeWidth: 6.0, + color: Colors.indigo), + ], + ) + : Container(), + ), + CurrentLocationLayer( + // 現在位置の表示を担当 + positionStream: locationController + .locationMarkerPositionStreamController.stream, + // locationMarkerPositionStreamController.streamを設定して、現在位置の更新を監視しています。 + alignDirectionOnUpdate: AlignOnUpdate.never, + //turnOnHeadingUpdate: TurnOnHeadingUpdate.never, + style: const LocationMarkerStyle( + // styleプロパティで、現在位置のマーカーのスタイルを設定しています。 + marker: Stack( + children: [ + CircleAvatar( + radius: 13.5, + backgroundColor: Colors.blue, + child: Icon(Icons.navigation, color: Colors.white), + ), + //if (locationController.getGpsSignalStrength() == 'low') + // child: Icon(Icons.warning, color: Colors.red), + ], + ), + /* + marker: DefaultLocationMarker( + child: Icon( + Icons.navigation, + color: Colors.yellowAccent, + ), + ), + */ + markerSize: Size(27, 27), + markerDirection: MarkerDirection.heading, + ), + ), + indexController.locations.isNotEmpty && + indexController.locations[0].features.isNotEmpty + ? MarkerLayer( + // マーカーの表示を担当 + markers: + indexController.locations[0].features.map((i) { + //print("i si ${i.properties!['location_id']}"); + + //RegExp regex = RegExp(r'([.]*0)(?!.*\d)'); + GeoJSONMultiPoint p = + i!.geometry as GeoJSONMultiPoint; + //print( + // "lat is ${p.geoSerie!.geoPoints[0].latitude} and lon is ${p.geoSerie!.geoPoints[0].longitude}"); + return Marker( + alignment: Alignment.center, + height: 27.0, + width: 127.0, + point: LatLng( + p.coordinates[0][1], p.coordinates[0][0]), + child: getMarkerShape(i, context)); + // マーカーのUIを生成しています。 + // マーカーのアイコン、ラベル、色などをカスタマイズしています。 + }).toList(), + ) + : const Center(child: CircularProgressIndicator()), + ], + )), + const Positioned(top: 0, left: 0, child: GameStateWidget()), + // ゲーム状態の表示を担当。ゲームの状態(開始、終了など)を表示するカスタムウィジェットです。 + const Positioned(bottom: 10, right: 10, child: CurrentPosition()), + // 現在位置ボタンの表示を担当。現在位置に移動するためのボタンを表示するカスタムウィジェットです。 + StreamBuilder( + stream: locationController.locationMarkerPositionStream, + builder: (context, snapshot) { + if (snapshot.hasData) { + print("Display current marker"); + } else { + print("Not display current marker"); + } + return Container(); + }, + ) + + // const Positioned( + // bottom: 10, + // left: 0, + // child: DebugWidget(), + // ) + ], + ); + } +}