diff --git a/assets/images/money.png b/assets/images/money.png new file mode 100644 index 0000000..9f2ff56 Binary files /dev/null and b/assets/images/money.png differ diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index c4f831e..a9c4d95 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -27,17 +27,17 @@ LSRequiresIPhoneOS NSCameraUsageDescription - 写真撮影のためカメラにアクセスします + 「岐阜ナビは旅のさまざまなチェックポイントで写真を撮影して共有するためにカメラへのアクセスが必要です。これにより、訪問を確認し、体験を向上させることができます。」 NSLocationAlwaysAndWhenInUseUsageDescription - このアプリでは、位置情報の収集を行います。 + 「岐阜ナビはアプリが閉じられているときでも位置情報へのアクセスが必要です。これにより、チェックポイントでの自動チェックインを提供し、旅の間のナビゲーション体験を向上させます。」 NSLocationAlwaysUsageDescription - このアプリでは、バックグラウンドで位置情報を収集します。 + 「自動チェックインを有効にし、ナビゲーションを改善するために、岐阜ナビはアプリが使用されていないときでも継続的に位置情報へのアクセスを必要とします。」 NSLocationWhenInUseUsageDescription - このアプリでは、開始時点で位置情報を収集します。 + 「岐阜ナビにアプリ使用中の位置情報へのアクセスを許可することで、チェックポイントでのチェックインを有効にし、位置に基づいた情報とガイダンスを提供します。」 NSMicrophoneUsageDescription - プロフィールに動画を投稿してください。 + 「岐阜ナビは、ナビゲーション体験を向上させるために、音声コマンドやオーディオフィードバックなどの機能にマイクへのアクセスが必要です。」 NSPhotoLibraryUsageDescription - 写真ライブラリへのアクセス警告 + 「岐阜ナビはチェックポイントで撮影した写真を保存・管理し、他人と旅の様子を共有するために、写真ライブラリへのアクセスが必要です。」 NSPhotoLibraryUsageDescription Need to save image in phone Album UILaunchStoryboardName @@ -45,9 +45,7 @@ UIMainStoryboardFile Main NSCameraUsageDescription - ロゲイニングのゴールでは写真撮影が必要です。 - NSMicrophoneUsageDescription - 収録音声の保存が必要です。 + 「岐阜ナビはチェックポイントで撮影した写真を写真ライブラリに保存し、あなたの旅行の記録を保持し、友人と共有することができます。」 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/lib/model/gps_data.dart b/lib/model/gps_data.dart index f6a3aa8..404a900 100644 --- a/lib/model/gps_data.dart +++ b/lib/model/gps_data.dart @@ -6,25 +6,30 @@ class GpsData { double lon; int is_checkin; int created_at; + int is_synced; - GpsData( - {required this.id, - required this.team_name, - required this.event_code, - required this.lat, - required this.lon, - required this.created_at, - this.is_checkin = 0}); + GpsData({ + required this.id, + required this.team_name, + required this.event_code, + required this.lat, + required this.lon, + required this.created_at, + this.is_checkin = 0, + this.is_synced = 0, + }); factory GpsData.fromMap(Map json) { return GpsData( - id: json["id"], - team_name: json["team_name"], - event_code: json["event_code"], - lat: json["lat"], - lon: json["lon"], - is_checkin: json["is_checkin"], - created_at: json["created_at"]); + id: json["id"], + team_name: json["team_name"], + event_code: json["event_code"], + lat: json["lat"], + lon: json["lon"], + is_checkin: json["is_checkin"], + created_at: json["created_at"], + is_synced: json["is_synced"] ?? 0, + ); } Map toMap() { @@ -35,7 +40,8 @@ class GpsData { 'lat': lat, 'lon': lon, 'is_checkin': is_checkin, - 'created_at': created_at + 'created_at': created_at, + 'is_synced': is_synced, }; } } diff --git a/lib/pages/camera/camera_page.dart b/lib/pages/camera/camera_page.dart index df9df01..9569c52 100644 --- a/lib/pages/camera/camera_page.dart +++ b/lib/pages/camera/camera_page.dart @@ -72,6 +72,16 @@ ImageProvider getFinishImage() { } } +ImageProvider getReceiptImage() { + DestinationController destinationController = + Get.find(); + if (destinationController.photos.isNotEmpty) { + return FileImage(destinationController.photos[0]); + } else { + return const AssetImage('assets/images/money.png'); + } +} + class CameraPage extends StatelessWidget { bool? manulaCheckin = false; bool? buyPointPhoto = false; @@ -454,7 +464,7 @@ class BuyPointCamera extends StatelessWidget { height: 370, decoration: BoxDecoration( image: DecorationImage( - image: getFinishImage(), fit: BoxFit.cover)), + image: getReceiptImage(), fit: BoxFit.cover)), ), ), ), diff --git a/lib/pages/destination/destination_controller.dart b/lib/pages/destination/destination_controller.dart index b23ebc4..e00fdc2 100644 --- a/lib/pages/destination/destination_controller.dart +++ b/lib/pages/destination/destination_controller.dart @@ -510,7 +510,7 @@ class DestinationController extends GetxController { lat: la, lon: ln, is_checkin: isCheckin, - created_at: DateTime.now().microsecondsSinceEpoch); + created_at: DateTime.now().millisecondsSinceEpoch); var res = await db.insertGps(gps_data); print("==gps res == ${res}"); } @@ -559,10 +559,10 @@ class DestinationController extends GetxController { LengthUnit.Meter, LatLng(latFs, lonFs), LatLng(la, ln)); Destination des = festuretoDestination(fs); - print( - "--- position is ---- ${position.longitude}, --- ${position.longitude}----"); + //print( + // "--- position is ---- ${position.longitude}, --- ${position.longitude}----"); - print("--- distFs ---- $distFs, --- ${des.checkin_radious}----"); + //print("--- distFs ---- $distFs, --- ${des.checkin_radious}----"); if (distFs <= des.checkin_radious! && skipGps == false) { //print("--- 789 ---- $skip_gps----"); //near a location @@ -571,6 +571,7 @@ class DestinationController extends GetxController { break; } } + pushGPStoServer(); //print("--- 123 ---- $skip_gps----"); } catch (e) { //print("An error occurred: $e"); @@ -581,6 +582,19 @@ class DestinationController extends GetxController { } } + Future pushGPStoServer() async { + try { + await Future.delayed(const Duration(minutes: 5)); + ExternalService().pushGPS(); + } catch (e) { + //print("An error occurred: $e"); + await pushGPStoServer(); + } finally { + //print("--- End of pushGPStoServer function, calling recursively ---"); + await pushGPStoServer(); + } + } + void addToRogaining(double lat, double lon, int destinationId) async { DatabaseHelper db = DatabaseHelper.instance; List d = await db.getDestinationById(destinationId); @@ -665,7 +679,7 @@ class DestinationController extends GetxController { DateTime now = DateTime.now(); String formattedDate = DateFormat('yyyy-MM-dd HH:mm:ss').format(now); - await addGPStoDB(destination.lat!, destination.lon!, isCheckin: 1); + await addGPStoDB(currentLat, currentLon, isCheckin: 1); // print("------ checkin event $eventCode ------"); ExternalService() @@ -869,9 +883,6 @@ class DestinationController extends GetxController { // ---------- database ------------------/// void addDestinations(Destination dest) { - // print( - // '------ destination controller in add destination ${dest.checkin_radious} ---- :::::'); - DatabaseHelper db = DatabaseHelper.instance; db.getDestinationByLatLon(dest.lat!, dest.lon!).then((value) { if (value.isNotEmpty) { @@ -947,40 +958,16 @@ class DestinationController extends GetxController { } void populateDestinations() { - //print( - // "--------- destination controller populsting destinations ----------- ::::::"); - DatabaseHelper db = DatabaseHelper.instance; destinations.clear(); db.getDestinations().then((value) { destinationCount.value = 0; for (Destination d in value) { - // print( - // "------ destination controller populating destination-------- ${d.checkedin}-------- :::::"); - //print("-----populated----- ${d.toMap()}"); destinations.add(d); } if (destinations.isEmpty) { rogainingCounted.value = false; } - // destinationCount.value = 0; - //print( - // "------ destination controller destinationcount-------- $destinationCount-------- :::::"); - - // MatrixService.getDestinations(value).then((mat){ - // print(" matrix is ------- ${mat}"); - // matrix = mat; - - // try{ - // getRoutePoints(); - // destinationCount.value = destinations.length; - // } - // catch(_){ - // skip_gps = false; - // return; - // } - - // }); }); } diff --git a/lib/pages/drawer/drawer_page.dart b/lib/pages/drawer/drawer_page.dart index d7e97dc..d771fd0 100644 --- a/lib/pages/drawer/drawer_page.dart +++ b/lib/pages/drawer/drawer_page.dart @@ -130,6 +130,26 @@ class DrawerPage extends StatelessWidget { width: 0, height: 0, ), + indexController.currentUser.isNotEmpty + ? ListTile( + leading: const Icon(Icons.delete_forever), + title: Text("ユーザーデータを削除する".tr), + onTap: () { + String token = indexController.currentUser[0]['token']; + AuthService.deleteUser(token).then((value) { + if (value.isNotEmpty) { + indexController.logout(); + Get.toNamed(AppPages.TRAVEL); + Get.snackbar("ユーザーデータを削除する", + "データを削除するためにユーザーの同意が設定されています アプリとサーバーでユーザーデータが削除されました"); + } + }); + }, + ) + : const SizedBox( + width: 0, + height: 0, + ), // ListTile( // leading: const Icon(Icons.person), // title: Text("profile".tr), diff --git a/lib/pages/permission/permission.dart b/lib/pages/permission/permission.dart index f374b45..fd692a6 100644 --- a/lib/pages/permission/permission.dart +++ b/lib/pages/permission/permission.dart @@ -45,9 +45,16 @@ class _PermissionHandlerScreenState extends State { content: const SingleChildScrollView( child: ListBody( children: [ - Text('このアプリでは、位置情報の収集を行います。'), + Text('ぎふナビ "アプリへようこそ!\n'), + Text('収集データ:\n'), Text( - '岐阜ナビアプリではチェックポイントの自動チェックインの機能を可能にするために、現在地のデータが収集されます。アプリを閉じている時や、使用していないときにも収集されます。位置情報は、個人を特定できない統計的な情報として、ユーザーの個人情報とは一切結びつかない形で送信されます。お知らせの配信、位置情報の利用を許可しない場合は、この後表示されるダイアログで「許可しない」を選択してください。'), + '岐阜ナビアプリをより快適にご利用いただくために、チェックポイントでの自動チェックインのための位置情報を収集しています。また、撮影した画像やチェックインした時間を収集し、チャレンジの勝者を決定します。\n'), + Text('お客様のデータの使用方法:\n'), + Text( + 'お客様のデータは、アプリを改善し、競争力のある魅力的な体験を提供するのに役立ちます。お客様の位置情報と画像は、個人を特定できない統計データとして処理され、いかなる個人情報にもリンクされないようにしています\n'), + Text('お客様の管理:\n'), + Text( + 'お客様はご自身のデータを完全に管理することができます。アプリの設定でいつでも同意を撤回することができます。位置情報の収集に同意しなくても、ぎふナビを楽しむことはできますが、自動チェックインや競技への参加など、一部の機能が制限されることを覚えておいてください。位置情報、画像、時刻情報の利用に同意する場合は「許可」を選択してください。許可しない場合は、「許可しない」を選択してください。あなたの選択は、アプリの他の機能を使用することに影響しません。\n'), ], ), ), diff --git a/lib/services/external_service.dart b/lib/services/external_service.dart index 9e80516..20e8524 100644 --- a/lib/services/external_service.dart +++ b/lib/services/external_service.dart @@ -5,9 +5,11 @@ import 'package:intl/intl.dart'; import 'package:rogapp/model/rog.dart'; import 'package:rogapp/pages/destination/destination_controller.dart'; import 'package:rogapp/pages/index/index_controller.dart'; +import 'package:rogapp/utils/database_gps.dart'; import 'package:rogapp/utils/database_helper.dart'; import 'dart:convert'; +import '../model/gps_data.dart'; import '../utils/const.dart'; // @@ -340,6 +342,73 @@ class ExternalService { return Future.value(false); } + String timestampToTimeString(int timestamp) { + // Convert timestamp to DateTime and format it as needed + var dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); + // Format dateTime to a time string (e.g., '12:00:00') + // Adjust the format as needed + return "${dateTime.hour}:${dateTime.minute}:${dateTime.second}"; + } + + Future pushGPS() async { + final IndexController indexController = Get.find(); + + //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"]; + + List gpsDataList = []; + + if (indexController.connectionStatusName.value != "wifi" && + indexController.connectionStatusName.value != "mobile") { + return Future.value(false); + } else { + // Step 1: Fetch data from the local database + gpsDataList = + await GpsDatabaseHelper.instance.getUnsyncedGPSData(team, eventCode); + + // Step 2: Transform data into the required format + var payload = { + 'team_name': team, + 'event_code': eventCode, + 'waypoints': gpsDataList.map((gpsData) { + return { + 'latitude': gpsData.lat.toString(), + 'longitude': gpsData.lon.toString(), + // Convert the timestamp to a formatted time string + 'time': timestampToTimeString(gpsData.created_at), + }; + }).toList(), + }; + + print("calling push gps step 2 ${payload}"); + + String urlS = + 'https://rogaining.sumasen.net/gifuroge/get_waypoint_datas_from_rogapp'; + //print('++++++++$url'); + var url = Uri.parse(urlS); // Replace with your server URL + var response = await http.post( + url, + headers: {"Content-Type": "application/json"}, + body: json.encode(payload), + ); + + print("GPS Data res ${response.statusCode}"); + if (response.statusCode == 200) { + // Handle success + // make local data as synced + await GpsDatabaseHelper.instance.setSyncData(gpsDataList); + print("GPS Data sent successfully"); + } else { + // Handle error + print("Failed to send data"); + } + } + return Future.value(false); + } + static Future> usersEventCode( String teamcode, String password) async { Map res = {}; diff --git a/lib/utils/database_gps.dart b/lib/utils/database_gps.dart index 96a922a..d19e059 100644 --- a/lib/utils/database_gps.dart +++ b/lib/utils/database_gps.dart @@ -37,7 +37,8 @@ class GpsDatabaseHelper { lat REAL, lon REAL, is_checkin int, - created_at INTEGER + created_at INTEGER, + is_synced INTEGER DEFAULT 0 ) '''); } @@ -70,4 +71,26 @@ class GpsDatabaseHelper { print("--------- db list $gpsDatas"); return gpsDatas; } + + Future> getUnsyncedGPSData( + String team_name, String event_code) async { + Database db = await instance.database; + var gpss = await db.query('gps', + where: 'team_name = ? and event_code = ? and is_synced = 0', + whereArgs: [team_name, event_code], + orderBy: 'created_at'); + return gpss.isNotEmpty ? gpss.map((e) => GpsData.fromMap(e)).toList() : []; + } + + Future setSyncData(List data) async { + Database db = await instance.database; + for (var record in data) { + await db.update( + 'gps', + {'is_synced': 1}, + where: 'id = ?', + whereArgs: [record.id], + ); + } + } } diff --git a/lib/widgets/bottom_sheet_new.dart b/lib/widgets/bottom_sheet_new.dart index 091dfa4..b6a13df 100644 --- a/lib/widgets/bottom_sheet_new.dart +++ b/lib/widgets/bottom_sheet_new.dart @@ -65,6 +65,7 @@ class BottomSheetNew extends GetView { } } else { GeoJsonFeature gf = indexController.currentFeature[0]; + print("=== photo sss ${gf.properties!["photos"]}"); if (gf.properties!["photos"] == null || gf.properties!["photos"] == "") { return const Image(image: AssetImage('assets/images/empty_image.png')); } else { @@ -80,9 +81,11 @@ class BottomSheetNew extends GetView { }, ); } else { + String imageUrl = Uri.encodeFull( + '$serverUrl/media/compressed/${gf.properties!["photos"]}'); return Image( image: NetworkImage( - '$serverUrl/media/compressed/${gf.properties!["photos"]}', + imageUrl, ), errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { @@ -495,8 +498,7 @@ class BottomSheetNew extends GetView { // ), destinationController.rogainingCounted.value == true && - destination.cp == -1 && - destinationController.distanceToStart() <= 500 + destination.cp == -1 ? ElevatedButton( onPressed: () async { destinationController.isAtGoal.value = true; diff --git a/pubspec.yaml b/pubspec.yaml index 3f48580..6d83729 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -113,6 +113,7 @@ flutter: - assets/images/japanese_fun.jpeg - assets/images/appicon.png - assets/images/login_image.jpg + - assets/images/money.png # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see