diff --git a/TODO.txt b/TODO.txt index 141bc06..5204dbe 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,4 +4,10 @@ バックアップをイベントごとに保存・レストア ログインした際に、イベントが選択されていなければ、イベントを選択するように促す。 -事前チェックインした写真が履歴に表示されない。 \ No newline at end of file +事前チェックインした写真が履歴に表示されない。 + +ユーザー名間違えたらログインできなくなる。 + +起動時に最後の参加イベントが過去日だったら、 +チェックポイントをクリアする。 +当日なら、参加処理?をしてタイトルを変える。 diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..831db07 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,8 @@ +-assumenosideeffects class android.util.Log { + public static boolean isLoggable(java.lang.String, int); + public static int v(...); + public static int i(...); + public static int w(...); + public static int d(...); + public static int e(...); +} \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard index 2662bd6..c8fa47b 100644 --- a/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -19,26 +19,34 @@ - + + + + + + + + + + + diff --git a/lib/main.dart b/lib/main.dart index 154e1ef..58ab8c6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -216,10 +216,7 @@ void main() async { }; try { - - await initServices(); - runApp(const ProviderScope(child: MyApp())); }catch(e, stackTrace){ print('Error during initialization: $e'); @@ -244,19 +241,6 @@ Future initServices() async { Get.put(LocationController(), permanent: true); debugPrint("2: Controllers initialized"); - /* - // すべてのコントローラーとサービスを非同期で初期化 - Get.lazyPut(() => IndexController(apiService: Get.find())); - debugPrint("2: start IndexController"); - - // その他のコントローラーを遅延初期化 - Get.lazyPut(() => SettingsController()); - debugPrint("2: start SettingsController"); - Get.lazyPut(() => DestinationController()); - debugPrint("3: start DestinationController"); - Get.lazyPut(() => LocationController()); - debugPrint("4: start LocationController"); - */ // 非同期処理を並列実行 await Future.wait([ @@ -273,6 +257,7 @@ Future initServices() async { }catch(e){ print('Error initializing : $e'); + rethrow; } print('All services started...'); @@ -667,11 +652,14 @@ class _MyAppState extends State with WidgetsBindingObserver { } void _checkMemoryUsage() async { + /* final memoryInfo = await _getMemoryInfo(); //debugPrint('Current memory usage: ${memoryInfo['used']} MB'); if (memoryInfo['used']! > 100) { // 100MB以上使用している場合 _performMemoryCleanup(); } + + */ } Future> _getMemoryInfo() async { diff --git a/lib/pages/destination/destination_controller.dart b/lib/pages/destination/destination_controller.dart index 6b8b184..2bfec90 100644 --- a/lib/pages/destination/destination_controller.dart +++ b/lib/pages/destination/destination_controller.dart @@ -44,6 +44,9 @@ import 'package:path_provider/path_provider.dart'; // 目的地に関連する状態管理とロジックを担当するクラスです。 // class DestinationController extends GetxController { + Timer? _checkForCheckinTimer; + final int _checkInterval = 3000; // ミリ秒単位でチェック間隔を設定 + late LocationSettings locationSettings; // 位置情報の設定を保持する変数です。 //late TeamController teamController = TeamController(); @@ -1019,15 +1022,16 @@ class DestinationController extends GetxController { // 2024-8-24 ... 佐伯呼び出しが必要なのか? // Future checkForCheckin() async { - //print("--- Start of checkForCheckin function ---"); - dbService.updateDatabase(); - await Future.delayed(const Duration(milliseconds: 3000)); - game_started = true; + if (!game_started) { + game_started = true; + dbService.updateDatabase(); + } try { // ここで、エラー if( indexController.locations.isNotEmpty ) { - indexController.locations[0].features.forEach((fs) async { + for (var fs in indexController.locations[0].features) { + //indexController.locations[0].features.forEach((fs) async { GeoJSONMultiPoint mp = fs!.geometry as GeoJSONMultiPoint; LatLng pt = LatLng(mp.coordinates[0][1], mp.coordinates[0][0]); @@ -1045,10 +1049,11 @@ class DestinationController extends GetxController { await startTimerLocation(fs, distFs); // Note: You cannot break out of forEach. If you need to stop processing, you might have to reconsider using forEach. } - }); + } if (gps_push_started == false) { - unawaited(pushGPStoServer()); + pushGPStoServer(); + //unawaited(pushGPStoServer()); } } //print("--- 123 ---- $skip_gps----"); @@ -1071,14 +1076,19 @@ class DestinationController extends GetxController { // "^^^^^^^^ ${DateFormat('kk:mm:ss \n EEE d MMM').format(DateTime.now())}"); try { gps_push_started = true; - ExternalService().pushGPS(); + await ExternalService().pushGPS(); } catch (e) { + print("An error occurred in pushGPStoServer: $e"); //print("An error occurred: $e"); //await pushGPStoServer(); } finally { - //print("--- End of pushGPStoServer function, calling recursively ---"); - await Future.delayed(const Duration(seconds: 5 * 60)); - await pushGPStoServer(); + if (gps_push_started) { + Future.delayed(Duration(minutes: 5), pushGPStoServer); + } + + //print("--- End of pushGPStoServer function, calling recursively ---"); + //await Future.delayed(const Duration(seconds: 5 * 60)); + //await pushGPStoServer(); } } @@ -1332,12 +1342,13 @@ class DestinationController extends GetxController { @override void onInit() async { super.onInit(); + startCheckForCheckinTimer(); - + /* WidgetsBinding.instance.addPostFrameCallback((_) async { await PermissionController.checkAndRequestPermissions(); }); - + */ startGPSCheckTimer(); @@ -1415,6 +1426,18 @@ class DestinationController extends GetxController { //checkGPSDataReceived(); } + void startCheckForCheckinTimer() { + _checkForCheckinTimer = Timer.periodic(Duration(milliseconds: _checkInterval), (_) { + checkForCheckin(); + }); + } + + void stopCheckForCheckinTimer() { + _checkForCheckinTimer?.cancel(); + _checkForCheckinTimer = null; + } + + void restartGPS(){ // GPSデータのListenを再開する処理を追加 Future.delayed(const Duration(seconds: 5), () { @@ -1427,8 +1450,9 @@ class DestinationController extends GetxController { // @override void onClose() { - gpsCheckTimer?.cancel(); - locationController.stopPositionStream(); + stopCheckForCheckinTimer(); + //gpsCheckTimer?.cancel(); + //locationController.stopPositionStream(); super.onClose(); } diff --git a/lib/pages/entry/entry_controller.dart b/lib/pages/entry/entry_controller.dart index 08f10e0..787fa75 100644 --- a/lib/pages/entry/entry_controller.dart +++ b/lib/pages/entry/entry_controller.dart @@ -11,6 +11,8 @@ import 'package:gifunavi/services/api_service.dart'; import 'package:gifunavi/pages/index/index_controller.dart'; import 'package:timezone/timezone.dart' as tz; +import '../../model/user.dart'; + class EntryController extends GetxController { late ApiService _apiService; @@ -29,6 +31,8 @@ class EntryController extends GetxController { final activeEvents = [].obs; //有効なイベントリスト + final teamMembers = [].obs; + @override void onInit() async { super.onInit(); @@ -98,12 +102,64 @@ class EntryController extends GetxController { } } - void updateTeam(Team? value) { + Future fetchTeamMembers(int teamId) async { + try { + final members = await _apiService.getTeamMembers(teamId); + teamMembers.assignAll(members); + } catch (e) { + print('Error fetching team members: $e'); + Get.snackbar('Error', 'Failed to fetch team members'); + } + } + + List getFilteredCategories() { + if (selectedTeam.value == null) return []; + + if (teamMembers.isEmpty) { + // ソロの場合 + String baseCategory = selectedTeam.value!.members.first.female ? 'ソロ女子' : 'ソロ男子'; + return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList(); + } else if (teamMembers.length == 1) { + // チームメンバーが1人の場合(ソロ) + String baseCategory = teamMembers.first.female ? 'ソロ女子' : 'ソロ男子'; + return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList(); + } else { + // 複数人の場合 + bool hasElementaryOrYounger = teamMembers.any(isElementarySchoolOrYounger); + String baseCategory = hasElementaryOrYounger ? 'ファミリー' : '一般'; + return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList(); + } + } + + bool isElementarySchoolOrYounger(User user) { + final now = DateTime.now(); + final age = now.year - user.dateOfBirth!.year; + return age <= 12; + } + + void updateTeam(Team? value) async { + selectedTeam.value = value; + if (value != null) { + await fetchTeamMembers(value.id); + final filteredCategories = getFilteredCategories(); + if (filteredCategories.isNotEmpty) { + selectedCategory.value = filteredCategories.first; + } else { + selectedCategory.value = null; + } + } else { + teamMembers.clear(); + selectedCategory.value = null; + } + } + + void updateTeam_old(Team? value) { selectedTeam.value = value; if (value != null) { selectedCategory.value = value.category; } } + //void updateTeam(Team? value) => selectedTeam.value = value; void updateCategory(NewCategory? value) => selectedCategory.value = value; //void updateDate(DateTime value) => selectedDate.value = value; diff --git a/lib/pages/entry/entry_detail_page.dart b/lib/pages/entry/entry_detail_page.dart index cf4c984..d9430be 100644 --- a/lib/pages/entry/entry_detail_page.dart +++ b/lib/pages/entry/entry_detail_page.dart @@ -175,6 +175,20 @@ class EntryDetailPage extends GetView { } Widget _buildCategoryDropdown() { + final eligibleCategories = controller.getFilteredCategories(); + + return DropdownButtonFormField( + decoration: InputDecoration(labelText: 'カテゴリ'), + value: controller.selectedCategory.value, + items: eligibleCategories.map((category) => DropdownMenuItem( + value: category, + child: Text(category.categoryName), + )).toList(), + onChanged: (value) => controller.updateCategory(value), + ); + } + + Widget _buildCategoryDropdown_old() { final eligibleCategories = controller.categories.where((c) => c.baseCategory == controller.selectedCategory.value?.baseCategory ).toList(); diff --git a/lib/pages/index/index_controller.dart b/lib/pages/index/index_controller.dart index 2241676..0f526ed 100644 --- a/lib/pages/index/index_controller.dart +++ b/lib/pages/index/index_controller.dart @@ -240,14 +240,14 @@ class IndexController extends GetxController with WidgetsBindingObserver { @override void onInit() { + super.onInit(); try { - super.onInit(); initConnectivity(); _connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus); - WidgetsBinding.instance.addPostFrameCallback((_) async { - await PermissionController.checkAndRequestPermissions(); - }); + //WidgetsBinding.instance.addPostFrameCallback((_) async { + // await PermissionController.checkAndRequestPermissions(); + //}); WidgetsBinding.instance.addObserver(this); _startLocationService(); // アプリ起動時にLocationServiceを開始する diff --git a/lib/pages/index/index_page.dart b/lib/pages/index/index_page.dart index fb2f737..c02af9c 100644 --- a/lib/pages/index/index_page.dart +++ b/lib/pages/index/index_page.dart @@ -40,7 +40,7 @@ class _IndexPageState extends State { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) async { await _ensureControllersAreInitialized(); - await PermissionController.checkAndRequestPermissions(); + //await PermissionController.checkAndRequestPermissions(); }); } diff --git a/lib/pages/permission/permission.dart b/lib/pages/permission/permission.dart index cba56a6..cea466f 100644 --- a/lib/pages/permission/permission.dart +++ b/lib/pages/permission/permission.dart @@ -167,6 +167,17 @@ class PermissionController { } static Future showLocationDisclosure() async { + if (Platform.isIOS) { + return await _showLocationDisclosureIOS(); + } else if (Platform.isAndroid) { + return await _showLocationDisclosureAndroid(); + } else { + // その他のプラットフォームの場合はデフォルトの処理を行う + return await _showLocationDisclosureIOS(); + } + } + + static Future _showLocationDisclosureIOS() async { if (Get.context == null) { print('Context is null, cannot show dialog'); return false; @@ -214,6 +225,41 @@ class PermissionController { } } + static Future _showLocationDisclosureAndroid() async { + return await showDialog( + context: Get.overlayContext ?? Get.context ?? (throw Exception('No valid context found')), + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('位置情報の使用について'), + content: const SingleChildScrollView( + child: ListBody( + children: [ + Text('このアプリでは、以下の目的で位置情報を使用します:'), + Text('• チェックポイントの自動チェックイン(アプリが閉じているときも含む)'), + Text('• 移動履歴の記録(バックグラウンドでも継続)'), + Text('• 現在地周辺の情報表示'), + Text('\nバックグラウンドでも位置情報を継続的に取得します。'), + Text('これにより、バッテリーの消費が増加する可能性があります。'), + Text('同意しない場合には、アプリは終了します。'), + ], + ), + ), + actions: [ + TextButton( + child: const Text('同意しない'), + onPressed: () => Navigator.of(context).pop(false), + ), + TextButton( + child: const Text('同意する'), + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ); + }, + ) ?? false; + } + static void showPermissionDeniedDialog(String title,String message) { Get.dialog( AlertDialog( diff --git a/lib/pages/team/team_controller.dart b/lib/pages/team/team_controller.dart index 9417baf..98141e4 100644 --- a/lib/pages/team/team_controller.dart +++ b/lib/pages/team/team_controller.dart @@ -329,13 +329,6 @@ class TeamController extends GetxController { } List getFilteredCategories_old() { - //List teamMembers = getCurrentTeamMembers(); - return categories.where((category) { - return isCategoryValid(category, teamMembers); - }).toList(); - } - - List getFilteredCategories() { if (teamMembers.isEmpty && currentUser.value != null) { // ソロの場合 String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子'; @@ -347,6 +340,24 @@ class TeamController extends GetxController { } } + List getFilteredCategories() { + if (teamMembers.isEmpty && currentUser.value != null) { + // ソロの場合 + String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子'; + return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList(); + } else if (teamMembers.length == 1) { + // チームメンバーが1人の場合(ソロ) + String baseCategory = teamMembers.first.female ? 'ソロ女子' : 'ソロ男子'; + return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList(); + } else { + // 複数人の場合 + bool hasElementaryOrYounger = teamMembers.any(isElementarySchoolOrYounger); + String baseCategory = hasElementaryOrYounger ? 'ファミリー' : '一般'; + return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList(); + } + } + + bool isElementarySchoolOrYounger(User user) { final now = DateTime.now(); final age = now.year - user.dateOfBirth!.year; @@ -379,17 +390,7 @@ class TeamController extends GetxController { // メンバーリストの最新状態を取得 await fetchTeamMembers(selectedTeam.value!.id); - List eligibleCategories = []; - if (teamMembers.isEmpty || teamMembers.length == 1) { - if (currentUser.value != null) { - String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子'; - eligibleCategories = categories.where((c) => c.baseCategory == baseCategory).toList(); - } - } else { - bool hasElementaryOrYounger = teamMembers.any(isElementarySchoolOrYounger); - String baseCategory = hasElementaryOrYounger ? 'ファミリー' : '一般'; - eligibleCategories = categories.where((c) => c.baseCategory == baseCategory).toList(); - } + List eligibleCategories = getFilteredCategories(); // 同じ時間のカテゴリを優先的に選択 NewCategory? newCategory = eligibleCategories.firstWhereOrNull((c) => c.time == oldTime); diff --git a/lib/pages/team/team_detail_page.dart b/lib/pages/team/team_detail_page.dart index 5e63022..50df5c1 100644 --- a/lib/pages/team/team_detail_page.dart +++ b/lib/pages/team/team_detail_page.dart @@ -120,15 +120,19 @@ class _TeamDetailPageState extends State { } final filteredCategories = controller.getFilteredCategories(); - final categoriesToDisplay = filteredCategories.isEmpty - ? controller.categories - : filteredCategories; + // 選択されているカテゴリが表示リストに含まれていない場合、最初の項目を選択する + if (controller.selectedCategory.value == null || + !filteredCategories.contains(controller.selectedCategory.value)) { + controller.updateCategory(filteredCategories.isNotEmpty + ? filteredCategories.first + : null); + } // 選択されているカテゴリが表示リストに含まれていない場合、最初の項目を選択する if (controller.selectedCategory.value == null || - !categoriesToDisplay.contains(controller.selectedCategory.value)) { - controller.updateCategory(categoriesToDisplay.isNotEmpty - ? categoriesToDisplay.first + !filteredCategories.contains(controller.selectedCategory.value)) { + controller.updateCategory(filteredCategories.isNotEmpty + ? filteredCategories.first : null); } @@ -147,7 +151,7 @@ class _TeamDetailPageState extends State { ), ), const SizedBox(height: 16), - if (categoriesToDisplay.isEmpty) + if (filteredCategories.isEmpty) const Text('カテゴリデータを読み込めませんでした。', style: TextStyle(color: Colors.red)) else @@ -156,7 +160,7 @@ class _TeamDetailPageState extends State { decoration: const InputDecoration( labelText: 'カテゴリ'), value: controller.selectedCategory.value, - items: categoriesToDisplay.map((category) => + items: filteredCategories.map((category) => DropdownMenuItem( value: category, child: Text(category.categoryName), diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 94be685..d898832 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -87,7 +87,7 @@ class ApiService extends GetxService{ Future _handleRequest(Future Function() request) async { try { final response = await request(); - if (response.statusCode == 200) { + if (response.statusCode == 200 || response.statusCode == 201) { return json.decode(utf8.decode(response.bodyBytes)); } else if (response.statusCode == 401) { await _handleUnauthorized(); diff --git a/lib/services/external_service.dart b/lib/services/external_service.dart index a518d96..987edc2 100644 --- a/lib/services/external_service.dart +++ b/lib/services/external_service.dart @@ -370,6 +370,15 @@ class ExternalService { //int userId = indexController.currentUser[0]["user"]["id"]; //print("--- Pressed -----"); + if( indexController.currentUser[0]["user"]==null ){ + return Future.value(false); + } + if( indexController.currentUser[0]["user"]['team_name']==null ){ + return Future.value(false); + } + if( indexController.currentUser[0]["user"]["event_code"]==null ){ + return Future.value(false); + } String team = indexController.currentUser[0]["user"]['team_name']; //print("--- _team : ${_team}-----"); String eventCode = indexController.currentUser[0]["user"]["event_code"]; diff --git a/lib/utils/location_controller.dart b/lib/utils/location_controller.dart index 724b7a6..33b06da 100644 --- a/lib/utils/location_controller.dart +++ b/lib/utils/location_controller.dart @@ -248,7 +248,7 @@ class LocationController extends GetxController { return; } - await PermissionController.checkAndRequestPermissions(); + //await PermissionController.checkAndRequestPermissions(); // 位置情報の設定を行います。z11 // Set up the location options diff --git a/lib/widgets/map_widget.dart b/lib/widgets/map_widget.dart index c21033f..84ad8d0 100644 --- a/lib/widgets/map_widget.dart +++ b/lib/widgets/map_widget.dart @@ -54,9 +54,11 @@ class _MapWidgetState extends State with WidgetsBindingObserver { super.initState(); // 追加 + /* WidgetsBinding.instance.addPostFrameCallback((_) { PermissionController.checkAndRequestPermissions(); }); + */ debugPrint('MapWidget: initState called'); SettingsBinding().dependencies(); // これを追加 @@ -83,7 +85,7 @@ class _MapWidgetState extends State with WidgetsBindingObserver { }); // MapControllerの初期化が完了したら、IndexControllerのonInitを呼び出す //indexController.checkPermission(); - PermissionController.checkAndRequestPermissions(); + //PermissionController.checkAndRequestPermissions(); }); late MapResetController mapResetController = MapResetController(); diff --git a/pubspec.yaml b/pubspec.yaml index 888b7bf..dc86316 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 4.8.19+499 +version: 4.8.20+500 environment: sdk: ^3.5.0