diff --git a/android/app/build.gradle b/android/app/build.gradle index edac07b..f960b1f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,3 +1,9 @@ + +plugins { + id 'com.android.application' + id 'kotlin-android' +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -21,8 +27,6 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" def keystoreProperties = new Properties() @@ -33,7 +37,7 @@ if (keystorePropertiesFile.exists()) { android { - compileSdkVersion 33 + compileSdkVersion 34 lintOptions { checkReleaseBuilds false @@ -56,7 +60,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.dvox.gifunavi" minSdkVersion 23 - targetSdkVersion 33 + targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName multiDexEnabled true @@ -64,10 +68,16 @@ android { signingConfigs { release { - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPassword'] - storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null - storePassword keystoreProperties['storePassword'] + if (project.hasProperty("RELEASE_STORE_FILE")) { + storeFile file(RELEASE_STORE_FILE) + storePassword RELEASE_STORE_PASSWORD + keyAlias RELEASE_KEY_ALIAS + keyPassword RELEASE_KEY_PASSWORD + } + //keyAlias keystoreProperties['keyAlias'] + //keyPassword keystoreProperties['keyPassword'] + //storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + //storePassword keystoreProperties['SachikoMiyata123'] } } buildTypes { diff --git a/android/gradle.properties b/android/gradle.properties index 46c1f16..efc7201 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,7 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true +RELEASE_STORE_FILE=release-key.keystore +RELEASE_STORE_PASSWORD=SachikoMiyata123 +RELEASE_KEY_ALIAS=release_key +RELEASE_KEY_PASSWORD=SachikoMiyata123 diff --git a/lib/model/destination.dart b/lib/model/destination.dart index fcf4f46..39b7eeb 100644 --- a/lib/model/destination.dart +++ b/lib/model/destination.dart @@ -59,8 +59,8 @@ class Destination { this.buypoint_image, this.forced_checkin = false, this.recipt_times = 0, - this.tags, - this.use_qr_code = false}); + this.tags}); //, ... use_qr_code をDBに追加したらオープン +// this.use_qr_code = false}); factory Destination.fromMap(Map json) { bool selec = json['selected'] == 0 ? false : true; @@ -96,8 +96,8 @@ class Destination { buypoint_image: json["buypoint_image"], forced_checkin: forcedCheckin, recipt_times: json["recipt_times"], - tags: json["tags"], - use_qr_code: useQrCode); + tags: json["tags"] ); //, +// use_qr_code: useQrCode); } Map toMap() { @@ -131,8 +131,8 @@ class Destination { 'buypoint_image': buypoint_image, 'forced_checkin': forcedCheckin, 'recipt_times': recipt_times, - 'tags': tags, - 'use_qr_code': use_qr_code + 'tags': tags //, + //'use_qr_code': use_qr_code }; } } diff --git a/lib/pages/camera/camera_page.dart b/lib/pages/camera/camera_page.dart index b40314d..6ad4edc 100644 --- a/lib/pages/camera/camera_page.dart +++ b/lib/pages/camera/camera_page.dart @@ -450,8 +450,8 @@ class CameraPage extends StatelessWidget { // buyPointPhotoがtrueの場合は、BuyPointCameraウィジェットを返します。 //print("--- buy point camera ${destination.toString()}"); return BuyPointCamera(destination: destination); - }else if(destination.use_qr_code){ - return QRCodeScannerPage(destination: destination); + //}else if(destination.use_qr_code){ + // return QRCodeScannerPage(destination: destination); } else if (destinationController.isInRog.value) { // isInRogがtrueの場合は、カメラページのUIを構築します。 // AppBarには、目的地の情報を表示します。 diff --git a/lib/pages/destination/destination_controller.dart b/lib/pages/destination/destination_controller.dart index 577ee28..2ec1086 100644 --- a/lib/pages/destination/destination_controller.dart +++ b/lib/pages/destination/destination_controller.dart @@ -32,6 +32,7 @@ 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'; // 目的地に関連する状態管理とロジックを担当するクラスです。 // @@ -405,30 +406,35 @@ class DestinationController extends GetxController { // print("~~~~ calling start ~~~~"); print("---- in start -----"); - chekcs = 1; // スタート地点で前のゴールから24時間経過 + chekcs = 1; // スタート地点で前のゴールから24時間経過 isInCheckin.value = true; isAtStart.value = true; if (shouldShowBottomSheet) { - shouldShowBottomSheet = false; // bottom_sheet を起動させない。 + shouldShowBottomSheet = false; // bottom_sheet を起動させない。 Widget bottomSheet = BottomSheetNew(destination: d); await showModalBottomSheet( constraints: - BoxConstraints.loose(Size(Get.width, Get.height * 0.85)), + 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, + 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 && @@ -597,7 +603,7 @@ class DestinationController extends GetxController { // print( // "==== date diff is ${DateTime.now().difference(last_goal_at).inHours} ===="); if (isuserLoggedIn && - (d.cp == -2 || d.cp == 0 ) && // Goal CP + (d.cp == -2 || d.cp == 0 || d.cp == -1 ) && // Goal CP locationAlreadyCheckedIn && skip_10s == false) { //check for rogaining @@ -763,7 +769,7 @@ class DestinationController extends GetxController { if (autoCheckin) { if (!checkingIn) { makeCheckin(d, true, ""); - if (d.cp != -1) { + if (d.cp != -1 && d.cp != 0 && d.cp != -2) { rogainingCounted.value = true; } } @@ -774,7 +780,7 @@ class DestinationController extends GetxController { if (d.hidden_location != null && d.hidden_location == 0 && isInRog.value == true && - d.cp != -1) { + d.cp != -1 && d.cp != 0 && d.cp != -2) { chekcs = 3; isInCheckin.value = true; photos.clear(); @@ -887,7 +893,10 @@ class DestinationController extends GetxController { LatLng(currentLat, currentLon)); Destination des = festuretoDestination(fs); - if (distFs <= des.checkin_radious! && skipGps == false) { + 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. } @@ -1015,7 +1024,15 @@ class DestinationController extends GetxController { // print("~~~~ inserted into db ~~~~"); } - await _saveImageFromPath(imageurl); + 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!); + } populateDestinations(); diff --git a/lib/pages/index/index_controller.dart b/lib/pages/index/index_controller.dart index d011fce..ceb1567 100644 --- a/lib/pages/index/index_controller.dart +++ b/lib/pages/index/index_controller.dart @@ -131,6 +131,30 @@ class IndexController extends GetxController { super.onClose(); } + /* + @override + void onReady() async { + await readUserToken(); + final token = userToken; + if (token != null && token.isNotEmpty) { + await loadUserDetailsForToken(token); + fixMapBound(token); + } else { + // ユーザートークンが存在しない場合はログイン画面にリダイレクト + Get.offAllNamed(AppPages.LOGIN); + } + + // 地図のイベントリスナーを設定 + indexController.mapController.mapEventStream.listen((MapEvent mapEvent) { + if (mapEvent is MapEventMoveEnd) { + indexController.loadLocationsBound(); + } + }); + + super.onReady(); + } + */ + Future _updateConnectionStatus(ConnectivityResult result) async { connectionStatus = result; connectionStatusName.value = result.name; @@ -232,6 +256,10 @@ class IndexController extends GetxController { }); currentUser.clear(); cats.clear(); + + // ユーザートークンをデバイスから削除 + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.remove("user_token"); } // 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。 @@ -361,6 +389,9 @@ class IndexController extends GetxController { return; } + // MapControllerの初期化が完了するまで待機 + await waitForMapControllerReady(); + // Akira 追加:2024-4-6 #2800 //await waitForMapControllerReady(); // MapControllerの初期化が完了するまで待機 // Akira 追加:2024-4-6 #2800 @@ -451,7 +482,7 @@ class IndexController extends GetxController { Future waitForMapControllerReady() async { if (!isMapControllerReady.value) { await Future.doWhile(() async { - await Future.delayed(const Duration(milliseconds: 500)); + await Future.delayed(const Duration(milliseconds: 100)); return !isMapControllerReady.value; }); } diff --git a/lib/utils/location_controller.dart b/lib/utils/location_controller.dart index 402f766..f43850f 100644 --- a/lib/utils/location_controller.dart +++ b/lib/utils/location_controller.dart @@ -275,25 +275,27 @@ class LocationController extends GetxController { // ロゲ開始前、終了後、GPS=low の場合は更新しない。 // - if (destinationController.isInRog.value && isGpsSignalWeak == false) { - final adjustedLocation = adjustCurrentLocation(position); - if (adjustedLocation != null) { - final locationMarkerPosition = LocationMarkerPosition( - latitude: adjustedLocation.latitude, - longitude: adjustedLocation.longitude, - accuracy: position?.accuracy ?? 0, - ); - handleLocationUpdate(locationMarkerPosition); - //locationMarkerPositionStreamController.add(locationMarkerPosition); // 位置データ送信 - } else { - // 位置情報が取得できなかった場合、 - // locationMarkerPositionStreamControllerにnullを追加します。 - locationMarkerPositionStreamController.add(null); // null 送信? - //forceUpdateLocation(Position? position); + if (isGpsSignalWeak == false) { + //if (destinationController.isInRog.value && isGpsSignalWeak == false) { + final adjustedLocation = adjustCurrentLocation(position); + if (adjustedLocation != null) { + final locationMarkerPosition = LocationMarkerPosition( + latitude: adjustedLocation.latitude, + longitude: adjustedLocation.longitude, + accuracy: position?.accuracy ?? 0, + ); + handleLocationUpdate(locationMarkerPosition); + //locationMarkerPositionStreamController.add(locationMarkerPosition); // 位置データ送信 + } else { + // 位置情報が取得できなかった場合、 + // locationMarkerPositionStreamControllerにnullを追加します。 + locationMarkerPositionStreamController.add(null); // null 送信? + //forceUpdateLocation(Position? position); + + } + //}else{ + // debugPrint("GPS処理対象外"); - } - //}else{ - // debugPrint("GPS処理対象外"); } }, diff --git a/lib/widgets/bottom_sheet_new.dart b/lib/widgets/bottom_sheet_new.dart index b228b8d..8a66c43 100644 --- a/lib/widgets/bottom_sheet_new.dart +++ b/lib/widgets/bottom_sheet_new.dart @@ -1,4 +1,7 @@ import 'dart:ui' as ui; +import 'dart:io'; +import 'package:http/http.dart' as http; +import 'package:path_provider/path_provider.dart'; import 'package:flutter/material.dart'; import 'package:geojson_vi/geojson_vi.dart'; import 'package:geolocator/geolocator.dart'; @@ -123,6 +126,20 @@ class BottomSheetNew extends GetView { } } + Future saveTemporaryImage(Destination destination) async { + final serverUrl = ConstValues.currentServer(); + final imagePath = '${serverUrl}/media/compressed/${destination.photos}'; + + final tempDir = await getTemporaryDirectory(); + final tempFile = await File('${tempDir.path}/temp_image.jpg').create(recursive: true); + final response = await http.get(Uri.parse(imagePath)); + await tempFile.writeAsBytes(response.bodyBytes); + + destinationController.photos.clear(); + destinationController.photos.add(tempFile); + } + + // アクションボタン(チェックイン、ゴールなど)を表示するためのメソッドです。 // 現在の状態に基づいて、適切なボタンを返します。 // ボタンがタップされたときの処理も含まれています。 @@ -145,17 +162,83 @@ class BottomSheetNew extends GetView { destinationController.currentLat, destinationController.currentLon), LatLng(cdest.lat!, cdest.lon!)); - if (destinationController.rogainingCounted.value == true && + // 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) { + // ゲームが始まってなければ + return ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.secondary, + ), + onPressed: destinationController.isInRog.value == false && + (destinationController.distanceToStart() <= 100 || destinationController.isGpsSignalWeak() ) && //追加 Akira 2024-4-5 + (destination.cp == -1 || destination.cp == 0 ) && + destinationController.rogainingCounted.value == false ? () async { + // 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 { + + await saveTemporaryImage(destination); + + // Clear data and start game logic here + destinationController.isInRog.value = true; + destinationController.resetRogaining(); + destinationController.addToRogaining( + destinationController.currentLat, + destinationController.currentLon, + destination.location_id!, + ); + + saveGameState(); + await ExternalService().startRogaining(); + Get.back(); // Close the dialog and potentially navigate away + }, + ), + ], + ), + barrierDismissible: + false, // User must tap a button to close the dialog + ); + } + : null, + child: Obx( + ()=> Text( + destinationController.isInRog.value ? '競技中' : 'ロゲ開始', + style: TextStyle(color: Colors.white), + ), + ), + ); + + //print("counted ${destinationController.rogainingCounted.value}"); + + + }else if (destinationController.rogainingCounted.value == true && // destinationController.distanceToStart() <= 500 && ... GPS信号が弱い時でもOKとする。 (destinationController.distanceToStart() <= 500 || destinationController.isGpsSignalWeak() ) && - destination.cp == -1 && + (destination.cp == 0 || destination.cp == -2 || destination.cp == -1) && +// (destination.cp == 0 || destination.cp == -2 ) && DestinationController.ready_for_goal == true) { //goal return ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: destinationController.rogainingCounted.value == true && destinationController.distanceToStart() <= 500 && - destination.cp == -1 && + (destination.cp == 0 || destination.cp == -2|| destination.cp == -1) && DestinationController.ready_for_goal == true ? () async { destinationController.isAtGoal.value = true; @@ -204,7 +287,7 @@ class BottomSheetNew extends GetView { print('Error processing check-in: $e'); } }, - /* +/* // Check conditions to show confirmation dialog if (destinationController.isInRog.value == false && (destinationController.distanceToStart() <= 500 || destinationController.isGpsSignalWeak() ) && //追加 Akira 2024-4-5 @@ -257,7 +340,9 @@ class BottomSheetNew extends GetView { return; } }, - */ + + */ + child: Text( destination.cp == -1 && destinationController.isInRog.value == false && diff --git a/lib/widgets/map_widget.dart b/lib/widgets/map_widget.dart index 873adfb..8bbfc8c 100644 --- a/lib/widgets/map_widget.dart +++ b/lib/widgets/map_widget.dart @@ -37,6 +37,7 @@ class _MapWidgetState extends State with WidgetsBindingObserver { StreamSubscription? subscription; Timer? _timer; + bool curr_marker_display = false; Map _markerCache = {}; List _markers = []; @@ -52,7 +53,12 @@ class _MapWidgetState extends State with WidgetsBindingObserver { WidgetsBinding.instance.addObserver(this); _startIdleTimer(); - //_initMarkers(); + // MapControllerの初期化が完了するまで待機 + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + indexController.isMapControllerReady.value = true; + }); + }); // indexController.mapController = MapController(initCompleter: mapControllerCompleter); @@ -171,7 +177,9 @@ class _MapWidgetState extends State with WidgetsBindingObserver { //}else{ // labelText=i.properties!['cp']; } - //debugPrint("Text=${labelText}"); + if( i.properties!['cp'] <= 0 ) { + debugPrint("Text=${labelText}"); + } final double maxWidth = labelText.length * 16.0; GeoJSONMultiPoint p = i.geometry as GeoJSONMultiPoint; return InkWell( @@ -398,7 +406,11 @@ class _MapWidgetState extends State with WidgetsBindingObserver { stream: locationController.locationMarkerPositionStream, builder: (context, snapshot) { if (!snapshot.hasData) { - debugPrint("====== Not display current marker"); + //debugPrint("====== Not display current marker"); + curr_marker_display = true; + }else if(curr_marker_display){ + debugPrint("====== Displayed current marker"); + curr_marker_display = false; } return Container(); },