From 7976f4c75b8471e60861b3a4adeedab6bbb146b9 Mon Sep 17 00:00:00 2001 From: Akira Date: Tue, 10 Sep 2024 16:16:04 +0900 Subject: [PATCH] Fixed Android issues --- TODO.txt | 3 + lib/main.dart | 35 ++++ lib/pages/entry/entry_controller.dart | 10 +- lib/pages/entry/entry_detail_page.dart | 280 +++++++++++++++---------- lib/widgets/error_widget.dart | 35 ++++ 5 files changed, 255 insertions(+), 108 deletions(-) create mode 100644 lib/widgets/error_widget.dart diff --git a/TODO.txt b/TODO.txt index 5204dbe..99879ab 100644 --- a/TODO.txt +++ b/TODO.txt @@ -11,3 +11,6 @@ 起動時に最後の参加イベントが過去日だったら、 チェックポイントをクリアする。 当日なら、参加処理?をしてタイトルを変える。 + +チーム構成とエントリーの相関が難しいのでは?? + diff --git a/lib/main.dart b/lib/main.dart index 58ab8c6..95a6960 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -88,6 +88,9 @@ Future saveGameState() async { pref.setBool( "rogaining_counted", destinationController.rogainingCounted.value); pref.setBool("ready_for_goal", DestinationController.ready_for_goal); + + // 最後のゲーム日時を保存 + pref.setString('lastGameDate', DateTime.now().toIso8601String()); } @@ -108,6 +111,20 @@ Future restoreGame() async { if (indexController.currentUser.isNotEmpty && indexController.currentUser[0]["user"]["id"] == savedUserId) { + // 最後のゲーム日時を取得 + final lastGameDateString = pref.getString('lastGameDate'); + if (lastGameDateString != null) { + final lastGameDate = DateTime.parse(lastGameDateString); + final now = DateTime.now(); + + // 最後のゲームが昨日以前の場合 + if (lastGameDate.isBefore(DateTime(now.year, now.month, now.day))) { + // ゲームの状態をクリア + await resetGameState(); + return; // ここで関数を終了 + } + } + final dateString = pref.getString('eventDate'); if (dateString != null) { final parsedDate = DateTime.parse(dateString); @@ -143,6 +160,24 @@ Future restoreGame() async { } } +// ゲームの状態をリセットする関数 +Future resetGameState() async { + SharedPreferences pref = await SharedPreferences.getInstance(); + await pref.remove("is_in_rog"); + await pref.remove("rogaining_counted"); + await pref.remove("ready_for_goal"); + + DestinationController destinationController = Get.find(); + destinationController.isInRog.value = false; + destinationController.rogainingCounted.value = false; + DestinationController.ready_for_goal = false; + + // チェックポイントをクリア + destinationController.deleteDBDestinations(); + + debugPrint("Game state has been reset due to outdated last game date"); +} + /* void restoreGame_new() async { SharedPreferences pref = await SharedPreferences.getInstance(); diff --git a/lib/pages/entry/entry_controller.dart b/lib/pages/entry/entry_controller.dart index 787fa75..4faa019 100644 --- a/lib/pages/entry/entry_controller.dart +++ b/lib/pages/entry/entry_controller.dart @@ -32,6 +32,8 @@ class EntryController extends GetxController { final activeEvents = [].obs; //有効なイベントリスト final teamMembers = [].obs; + final hasError = false.obs; + final errorMessage = "".obs; @override void onInit() async { @@ -45,7 +47,7 @@ class EntryController extends GetxController { _apiService = await Get.putAsync(() => ApiService().init()); } catch (e) { print('Error initializing ApiService: $e'); - Get.snackbar('Error', 'Failed to initialize API service'); + Get.snackbar('Error', 'APIサービスの初期化に失敗しました'); } } @@ -64,14 +66,16 @@ class EntryController extends GetxController { initializeEditMode(currentEntry.value!); } else { // 新規作成モードの場合、最初のイベントを選択 - if (events.isNotEmpty) { + if (activeEvents.isNotEmpty) { selectedEvent.value = activeEvents.first; selectedDate.value = activeEvents.first.startDatetime; } } } catch(e) { print('Error initializing data: $e'); - Get.snackbar('Error', 'Failed to load initial data'); + // エラー状態を設定 + hasError.value = true; + Get.snackbar('Error', '初期データの読み込みに失敗しました'); } finally { isLoading.value = false; } diff --git a/lib/pages/entry/entry_detail_page.dart b/lib/pages/entry/entry_detail_page.dart index d9430be..d42e696 100644 --- a/lib/pages/entry/entry_detail_page.dart +++ b/lib/pages/entry/entry_detail_page.dart @@ -7,6 +7,7 @@ import 'package:gifunavi/model/event.dart'; import 'package:gifunavi/model/category.dart'; import 'package:gifunavi/model/team.dart'; import 'package:intl/intl.dart'; +import 'package:gifunavi/widgets/error_widget.dart'; import 'package:timezone/timezone.dart' as tz; @@ -31,36 +32,72 @@ class EntryDetailPage extends GetView { if (controller.isLoading.value) { return const Center(child: CircularProgressIndicator()); } - return Padding( - padding: const EdgeInsets.all(16.0), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildDropdown( - label: 'イベント', - items: controller.activeEvents, - selectedId: controller.selectedEvent.value?.id, + + + if (controller.hasError.value) { + return CustomErrorWidget( + errorMessage: controller.errorMessage.value, + onRetry: () => controller.loadInitialData(), + ); + } + + + try { + return Padding( + padding: const EdgeInsets.all(16.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDropdown( + label: 'イベント', + items: controller.activeEvents, + selectedId: controller.selectedEvent.value?.id, + + onChanged: (eventId) { + final event = controller.activeEvents.firstWhereOrNull(( + e) => e.id == eventId); + if (event != null) { + controller.updateEvent(event); + } else { + print('Event with id $eventId not found'); + // 必要に応じてエラー処理を追加 + } + }, + /* onChanged: (eventId) => controller.updateEvent( controller.activeEvents.firstWhere((e) => e.id == eventId) ), - getDisplayName: (event) => event.eventName, - getId: (event) => event.id, - ), - const SizedBox(height: 16), - _buildDropdown( - label: 'チーム', - items: controller.teams, - selectedId: controller.selectedTeam.value?.id, + */ + getDisplayName: (event) => event.eventName, + getId: (event) => event.id, + ), + const SizedBox(height: 16), + _buildDropdown( + label: 'チーム', + items: controller.teams, + selectedId: controller.selectedTeam.value?.id, + onChanged: (teamId) { + final team = controller.teams.firstWhereOrNull((t) => + t.id == teamId); + if (team != null) { + controller.updateTeam(team); + } else { + print('Team with id $teamId not found'); + // 必要に応じてエラー処理を追加 + } + }, + /* onChanged: (teamId) => controller.updateTeam( controller.teams.firstWhere((t) => t.id == teamId) ), - getDisplayName: (team) => team.teamName, - getId: (team) => team.id, - ), - const SizedBox(height: 16), - _buildCategoryDropdown(), - /* + */ + getDisplayName: (team) => team.teamName, + getId: (team) => team.id, + ), + const SizedBox(height: 16), + _buildCategoryDropdown(), + /* _buildDropdown() label: 'カテゴリ', items: controller.categories, @@ -73,84 +110,104 @@ class EntryDetailPage extends GetView { ), */ - const SizedBox(height: 16), - ListTile( - title: const Text('日付'), - subtitle: Text( - controller.selectedDate.value != null - ? DateFormat('yyyy-MM-dd').format(tz.TZDateTime.from(controller.selectedDate.value!, tz.getLocation('Asia/Tokyo'))) - : '日付を選択してください', - ), - onTap: () async { - if (controller.selectedEvent.value == null) { - Get.snackbar('Error', 'Please select an event first'); - return; - } - final tz.TZDateTime now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo')); - final tz.TZDateTime eventStart = tz.TZDateTime.from(controller.selectedEvent.value!.startDatetime, tz.getLocation('Asia/Tokyo')); - final tz.TZDateTime eventEnd = tz.TZDateTime.from(controller.selectedEvent.value!.endDatetime, tz.getLocation('Asia/Tokyo')); - - final tz.TZDateTime initialDate = controller.selectedDate.value != null - ? tz.TZDateTime.from(controller.selectedDate.value!, tz.getLocation('Asia/Tokyo')) - : (now.isAfter(eventStart) ? now : eventStart); - - // 選択可能な最初の日付を設定(今日かイベント開始日のうち、より後の日付) - final tz.TZDateTime firstDate = now.isAfter(eventStart) ? now : eventStart; - - final DateTime? picked = await showDatePicker( - context: context, - initialDate: initialDate.isAfter(firstDate) ? initialDate : firstDate, - firstDate: firstDate, - lastDate: eventEnd, - ); - if (picked != null) { - controller.updateDate(tz.TZDateTime.from(picked, tz.getLocation('Asia/Tokyo'))); - } - }, - ), - const SizedBox(height: 32), - if (mode == 'new') - ElevatedButton( - onPressed: () => controller.createEntry(), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - minimumSize: const Size(double.infinity, 50), + const SizedBox(height: 16), + ListTile( + title: const Text('日付'), + subtitle: Text( + controller.selectedDate.value != null + ? DateFormat('yyyy-MM-dd').format(tz.TZDateTime.from( + controller.selectedDate.value!, + tz.getLocation('Asia/Tokyo'))) + : '日付を選択してください', ), - child: const Text('エントリーを作成'), - ) - else - Row( - children: [ - Expanded( - child: ElevatedButton( - onPressed: () => controller.deleteEntry(), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, - minimumSize: const Size(0, 50), - ), - child: const Text('エントリーを削除'), - ), - ), - const SizedBox(width: 16), - Expanded( - child: ElevatedButton( - onPressed: () => controller.updateEntry(), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.lightBlue, - foregroundColor: Colors.white, - minimumSize: const Size(0, 50), - ), - child: const Text('エントリーを更新'), - ), - ), - ], + onTap: () async { + if (controller.selectedEvent.value == null) { + Get.snackbar('Error', 'Please select an event first'); + return; + } + final tz.TZDateTime now = tz.TZDateTime.now(tz + .getLocation('Asia/Tokyo')); + final tz.TZDateTime eventStart = tz.TZDateTime.from( + controller.selectedEvent.value!.startDatetime, tz + .getLocation('Asia/Tokyo')); + final tz.TZDateTime eventEnd = tz.TZDateTime.from( + controller.selectedEvent.value!.endDatetime, tz + .getLocation('Asia/Tokyo')); + + final tz.TZDateTime initialDate = controller.selectedDate + .value != null + ? tz.TZDateTime.from(controller.selectedDate.value!, + tz.getLocation('Asia/Tokyo')) + : (now.isAfter(eventStart) ? now : eventStart); + + // 選択可能な最初の日付を設定(今日かイベント開始日のうち、より後の日付) + final tz.TZDateTime firstDate = now.isAfter(eventStart) + ? now + : eventStart; + + final DateTime? picked = await showDatePicker( + context: context, + initialDate: initialDate.isAfter(firstDate) + ? initialDate + : firstDate, + firstDate: firstDate, + lastDate: eventEnd, + ); + if (picked != null) { + controller.updateDate(tz.TZDateTime.from( + picked, tz.getLocation('Asia/Tokyo'))); + } + }, ), - ], + const SizedBox(height: 32), + if (mode == 'new') + ElevatedButton( + onPressed: () => controller.createEntry(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + minimumSize: const Size(double.infinity, 50), + ), + child: const Text('エントリーを作成'), + ) + else + Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () => controller.deleteEntry(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + minimumSize: const Size(0, 50), + ), + child: const Text('エントリーを削除'), + ), + ), + const SizedBox(width: 16), + Expanded( + child: ElevatedButton( + onPressed: () => controller.updateEntry(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.lightBlue, + foregroundColor: Colors.white, + minimumSize: const Size(0, 50), + ), + child: const Text('エントリーを更新'), + ), + ), + ], + ), + ], + ), ), - ), - ); + ); + } catch (e) { + print('Error in EntryDetailPage: $e'); + return const Center( + child: Text('エラーが発生しました。もう一度お試しください。'), + ); + } }), ); } @@ -166,11 +223,17 @@ class EntryDetailPage extends GetView { return DropdownButtonFormField( decoration: InputDecoration(labelText: label), value: selectedId, - items: items.map((item) => DropdownMenuItem( + items: items.isNotEmpty ? items.map((item) => DropdownMenuItem( + //items: items.map((item) => DropdownMenuItem( value: getId(item), child: Text(getDisplayName(item)), - )).toList(), - onChanged: onChanged, + )).toList() : null, + onChanged: (value) { + if (value != null) { + onChanged(value); + } + }, + //onChanged: onChanged, ); } @@ -180,11 +243,18 @@ class EntryDetailPage extends GetView { return DropdownButtonFormField( decoration: InputDecoration(labelText: 'カテゴリ'), value: controller.selectedCategory.value, - items: eligibleCategories.map((category) => DropdownMenuItem( + items: eligibleCategories.isNotEmpty ? eligibleCategories.map((category) => DropdownMenuItem( + + //items: eligibleCategories.map((category) => DropdownMenuItem( value: category, child: Text(category.categoryName), - )).toList(), - onChanged: (value) => controller.updateCategory(value), + )).toList() : null, + onChanged: (value) { + if (value != null) { + controller.updateCategory(value); + } + }, + //onChanged: (value) => controller.updateCategory(value), ); } diff --git a/lib/widgets/error_widget.dart b/lib/widgets/error_widget.dart new file mode 100644 index 0000000..9246b27 --- /dev/null +++ b/lib/widgets/error_widget.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class CustomErrorWidget extends StatelessWidget { + final String errorMessage; + final VoidCallback onRetry; + + const CustomErrorWidget({ + Key? key, + required this.errorMessage, + required this.onRetry, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'エラーが発生しました', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Text(errorMessage), + const SizedBox(height: 16), + ElevatedButton( + onPressed: onRetry, + child: const Text('再試行'), + ), + ], + ), + ); + } +} \ No newline at end of file