Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7976f4c75b | |||
| 8ed6a4e8bf | |||
| a22c2ea730 | |||
| e6328f84b1 |
16
TODO.txt
Normal file
16
TODO.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
不参加の過去エントリーは削除できるようにする。
|
||||||
|
バグ:
|
||||||
|
履歴の写真:アクセスエラー
|
||||||
|
バックアップをイベントごとに保存・レストア
|
||||||
|
|
||||||
|
ログインした際に、イベントが選択されていなければ、イベントを選択するように促す。
|
||||||
|
事前チェックインした写真が履歴に表示されない。
|
||||||
|
|
||||||
|
ユーザー名間違えたらログインできなくなる。
|
||||||
|
|
||||||
|
起動時に最後の参加イベントが過去日だったら、
|
||||||
|
チェックポイントをクリアする。
|
||||||
|
当日なら、参加処理?をしてタイトルを変える。
|
||||||
|
|
||||||
|
チーム構成とエントリーの相関が難しいのでは??
|
||||||
|
|
||||||
8
android/app/proguard-rules.pro
vendored
Normal file
8
android/app/proguard-rules.pro
vendored
Normal file
@ -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(...);
|
||||||
|
}
|
||||||
@ -13,7 +13,7 @@ import CoreMotion
|
|||||||
//GeneratedPluginRegistrant.register(with: self)
|
//GeneratedPluginRegistrant.register(with: self)
|
||||||
//return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
//return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
|
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
|
||||||
let motionChannel = FlutterMethodChannel(name: "com.yourcompany.app/motion",
|
let motionChannel = FlutterMethodChannel(name: "net.sumasen.gifunavi/motion",
|
||||||
binaryMessenger: controller.binaryMessenger)
|
binaryMessenger: controller.binaryMessenger)
|
||||||
motionChannel.setMethodCallHandler({
|
motionChannel.setMethodCallHandler({
|
||||||
[weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
|
[weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
|
||||||
|
|||||||
@ -19,26 +19,34 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="岐阜ナビ" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WPs-nj-CIV">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="岐阜ナビ" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WPs-nj-CIV">
|
||||||
<rect key="frame" x="46" y="123" width="314" height="59"/>
|
<rect key="frame" x="46" y="122.99999999999999" width="301" height="38.333333333333329"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="32"/>
|
<fontDescription key="fontDescription" type="system" pointSize="32"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="NPO 岐阜aiネットワーク" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TM1-SD-6RA">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="NPO 岐阜aiネットワーク" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TM1-SD-6RA">
|
||||||
<rect key="frame" x="46" y="722" width="314" height="41"/>
|
<rect key="frame" x="46" y="708" width="301" height="40"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="hCH-Iu-4S2">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="hCH-Iu-4S2">
|
||||||
<rect key="frame" x="83" y="314" width="240" height="224"/>
|
<rect key="frame" x="46" y="241.33333333333334" width="301" height="341.33333333333326"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="xbc-2k-c8Z" firstAttribute="top" secondItem="TM1-SD-6RA" secondAttribute="bottom" constant="70" id="DbA-tx-JAk"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="hCH-Iu-4S2" secondAttribute="trailing" constant="30" id="DsC-fI-z7h"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="TM1-SD-6RA" secondAttribute="trailing" constant="30" id="Spt-qL-bHo"/>
|
||||||
|
<constraint firstItem="WPs-nj-CIV" firstAttribute="top" secondItem="Ydg-fD-yQy" secondAttribute="bottom" constant="64" id="Wf4-J5-CP7"/>
|
||||||
|
<constraint firstItem="hCH-Iu-4S2" firstAttribute="top" secondItem="WPs-nj-CIV" secondAttribute="bottom" constant="80" id="eIO-ZH-rp5"/>
|
||||||
|
<constraint firstItem="hCH-Iu-4S2" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" constant="30" id="oZr-ky-01l"/>
|
||||||
|
<constraint firstItem="WPs-nj-CIV" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" constant="30" id="pEC-jM-hHf"/>
|
||||||
|
<constraint firstItem="TM1-SD-6RA" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" constant="30" id="pRM-dy-ucg"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="WPs-nj-CIV" secondAttribute="trailing" constant="30" id="ugk-x4-Wgc"/>
|
||||||
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
|||||||
@ -88,6 +88,9 @@ Future<void> saveGameState() async {
|
|||||||
pref.setBool(
|
pref.setBool(
|
||||||
"rogaining_counted", destinationController.rogainingCounted.value);
|
"rogaining_counted", destinationController.rogainingCounted.value);
|
||||||
pref.setBool("ready_for_goal", DestinationController.ready_for_goal);
|
pref.setBool("ready_for_goal", DestinationController.ready_for_goal);
|
||||||
|
|
||||||
|
// 最後のゲーム日時を保存
|
||||||
|
pref.setString('lastGameDate', DateTime.now().toIso8601String());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -108,6 +111,20 @@ Future<void> restoreGame() async {
|
|||||||
if (indexController.currentUser.isNotEmpty &&
|
if (indexController.currentUser.isNotEmpty &&
|
||||||
indexController.currentUser[0]["user"]["id"] == savedUserId) {
|
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');
|
final dateString = pref.getString('eventDate');
|
||||||
if (dateString != null) {
|
if (dateString != null) {
|
||||||
final parsedDate = DateTime.parse(dateString);
|
final parsedDate = DateTime.parse(dateString);
|
||||||
@ -143,6 +160,24 @@ Future<void> restoreGame() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ゲームの状態をリセットする関数
|
||||||
|
Future<void> 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>();
|
||||||
|
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 {
|
void restoreGame_new() async {
|
||||||
SharedPreferences pref = await SharedPreferences.getInstance();
|
SharedPreferences pref = await SharedPreferences.getInstance();
|
||||||
@ -216,10 +251,7 @@ void main() async {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
|
||||||
await initServices();
|
await initServices();
|
||||||
|
|
||||||
runApp(const ProviderScope(child: MyApp()));
|
runApp(const ProviderScope(child: MyApp()));
|
||||||
}catch(e, stackTrace){
|
}catch(e, stackTrace){
|
||||||
print('Error during initialization: $e');
|
print('Error during initialization: $e');
|
||||||
@ -244,19 +276,6 @@ Future<void> initServices() async {
|
|||||||
Get.put(LocationController(), permanent: true);
|
Get.put(LocationController(), permanent: true);
|
||||||
|
|
||||||
debugPrint("2: Controllers initialized");
|
debugPrint("2: Controllers initialized");
|
||||||
/*
|
|
||||||
// すべてのコントローラーとサービスを非同期で初期化
|
|
||||||
Get.lazyPut(() => IndexController(apiService: Get.find<ApiService>()));
|
|
||||||
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([
|
await Future.wait([
|
||||||
@ -273,6 +292,7 @@ Future<void> initServices() async {
|
|||||||
|
|
||||||
}catch(e){
|
}catch(e){
|
||||||
print('Error initializing : $e');
|
print('Error initializing : $e');
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
print('All services started...');
|
print('All services started...');
|
||||||
|
|
||||||
@ -667,11 +687,14 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _checkMemoryUsage() async {
|
void _checkMemoryUsage() async {
|
||||||
|
/*
|
||||||
final memoryInfo = await _getMemoryInfo();
|
final memoryInfo = await _getMemoryInfo();
|
||||||
//debugPrint('Current memory usage: ${memoryInfo['used']} MB');
|
//debugPrint('Current memory usage: ${memoryInfo['used']} MB');
|
||||||
if (memoryInfo['used']! > 100) { // 100MB以上使用している場合
|
if (memoryInfo['used']! > 100) { // 100MB以上使用している場合
|
||||||
_performMemoryCleanup();
|
_performMemoryCleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, int>> _getMemoryInfo() async {
|
Future<Map<String, int>> _getMemoryInfo() async {
|
||||||
|
|||||||
@ -11,6 +11,8 @@ class Entry {
|
|||||||
final DateTime? date;
|
final DateTime? date;
|
||||||
final int zekkenNumber; // 新しく追加
|
final int zekkenNumber; // 新しく追加
|
||||||
final String owner;
|
final String owner;
|
||||||
|
bool hasParticipated;
|
||||||
|
bool hasGoaled;
|
||||||
|
|
||||||
Entry({
|
Entry({
|
||||||
required this.id,
|
required this.id,
|
||||||
@ -20,6 +22,8 @@ class Entry {
|
|||||||
required this.date,
|
required this.date,
|
||||||
required this.zekkenNumber,
|
required this.zekkenNumber,
|
||||||
required this.owner,
|
required this.owner,
|
||||||
|
this.hasParticipated = false,
|
||||||
|
this.hasGoaled = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Entry.fromJson(Map<String, dynamic> json) {
|
factory Entry.fromJson(Map<String, dynamic> json) {
|
||||||
|
|||||||
@ -44,6 +44,9 @@ import 'package:path_provider/path_provider.dart';
|
|||||||
// 目的地に関連する状態管理とロジックを担当するクラスです。
|
// 目的地に関連する状態管理とロジックを担当するクラスです。
|
||||||
//
|
//
|
||||||
class DestinationController extends GetxController {
|
class DestinationController extends GetxController {
|
||||||
|
Timer? _checkForCheckinTimer;
|
||||||
|
final int _checkInterval = 3000; // ミリ秒単位でチェック間隔を設定
|
||||||
|
|
||||||
late LocationSettings locationSettings; // 位置情報の設定を保持する変数です。
|
late LocationSettings locationSettings; // 位置情報の設定を保持する変数です。
|
||||||
|
|
||||||
//late TeamController teamController = TeamController();
|
//late TeamController teamController = TeamController();
|
||||||
@ -1019,15 +1022,16 @@ class DestinationController extends GetxController {
|
|||||||
// 2024-8-24 ... 佐伯呼び出しが必要なのか?
|
// 2024-8-24 ... 佐伯呼び出しが必要なのか?
|
||||||
//
|
//
|
||||||
Future<void> checkForCheckin() async {
|
Future<void> checkForCheckin() async {
|
||||||
//print("--- Start of checkForCheckin function ---");
|
if (!game_started) {
|
||||||
dbService.updateDatabase();
|
|
||||||
await Future.delayed(const Duration(milliseconds: 3000));
|
|
||||||
game_started = true;
|
game_started = true;
|
||||||
|
dbService.updateDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// ここで、エラー
|
// ここで、エラー
|
||||||
if( indexController.locations.isNotEmpty ) {
|
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;
|
GeoJSONMultiPoint mp = fs!.geometry as GeoJSONMultiPoint;
|
||||||
LatLng pt = LatLng(mp.coordinates[0][1], mp.coordinates[0][0]);
|
LatLng pt = LatLng(mp.coordinates[0][1], mp.coordinates[0][0]);
|
||||||
|
|
||||||
@ -1045,10 +1049,11 @@ class DestinationController extends GetxController {
|
|||||||
await startTimerLocation(fs, distFs);
|
await startTimerLocation(fs, distFs);
|
||||||
// Note: You cannot break out of forEach. If you need to stop processing, you might have to reconsider using forEach.
|
// 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) {
|
if (gps_push_started == false) {
|
||||||
unawaited(pushGPStoServer());
|
pushGPStoServer();
|
||||||
|
//unawaited(pushGPStoServer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//print("--- 123 ---- $skip_gps----");
|
//print("--- 123 ---- $skip_gps----");
|
||||||
@ -1071,14 +1076,19 @@ class DestinationController extends GetxController {
|
|||||||
// "^^^^^^^^ ${DateFormat('kk:mm:ss \n EEE d MMM').format(DateTime.now())}");
|
// "^^^^^^^^ ${DateFormat('kk:mm:ss \n EEE d MMM').format(DateTime.now())}");
|
||||||
try {
|
try {
|
||||||
gps_push_started = true;
|
gps_push_started = true;
|
||||||
ExternalService().pushGPS();
|
await ExternalService().pushGPS();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
print("An error occurred in pushGPStoServer: $e");
|
||||||
//print("An error occurred: $e");
|
//print("An error occurred: $e");
|
||||||
//await pushGPStoServer();
|
//await pushGPStoServer();
|
||||||
} finally {
|
} finally {
|
||||||
|
if (gps_push_started) {
|
||||||
|
Future.delayed(Duration(minutes: 5), pushGPStoServer);
|
||||||
|
}
|
||||||
|
|
||||||
//print("--- End of pushGPStoServer function, calling recursively ---");
|
//print("--- End of pushGPStoServer function, calling recursively ---");
|
||||||
await Future.delayed(const Duration(seconds: 5 * 60));
|
//await Future.delayed(const Duration(seconds: 5 * 60));
|
||||||
await pushGPStoServer();
|
//await pushGPStoServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1332,12 +1342,13 @@ class DestinationController extends GetxController {
|
|||||||
@override
|
@override
|
||||||
void onInit() async {
|
void onInit() async {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
startCheckForCheckinTimer();
|
||||||
|
|
||||||
|
/*
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
await PermissionController.checkAndRequestPermissions();
|
await PermissionController.checkAndRequestPermissions();
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
startGPSCheckTimer();
|
startGPSCheckTimer();
|
||||||
|
|
||||||
@ -1415,6 +1426,18 @@ class DestinationController extends GetxController {
|
|||||||
//checkGPSDataReceived();
|
//checkGPSDataReceived();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void startCheckForCheckinTimer() {
|
||||||
|
_checkForCheckinTimer = Timer.periodic(Duration(milliseconds: _checkInterval), (_) {
|
||||||
|
checkForCheckin();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopCheckForCheckinTimer() {
|
||||||
|
_checkForCheckinTimer?.cancel();
|
||||||
|
_checkForCheckinTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void restartGPS(){
|
void restartGPS(){
|
||||||
// GPSデータのListenを再開する処理を追加
|
// GPSデータのListenを再開する処理を追加
|
||||||
Future.delayed(const Duration(seconds: 5), () {
|
Future.delayed(const Duration(seconds: 5), () {
|
||||||
@ -1427,8 +1450,9 @@ class DestinationController extends GetxController {
|
|||||||
//
|
//
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
gpsCheckTimer?.cancel();
|
stopCheckForCheckinTimer();
|
||||||
locationController.stopPositionStream();
|
//gpsCheckTimer?.cancel();
|
||||||
|
//locationController.stopPositionStream();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,9 +8,11 @@ import 'package:gifunavi/model/team.dart';
|
|||||||
import 'package:gifunavi/model/category.dart';
|
import 'package:gifunavi/model/category.dart';
|
||||||
import 'package:gifunavi/services/api_service.dart';
|
import 'package:gifunavi/services/api_service.dart';
|
||||||
|
|
||||||
import '../index/index_controller.dart';
|
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
import 'package:timezone/timezone.dart' as tz;
|
||||||
|
|
||||||
|
import '../../model/user.dart';
|
||||||
|
|
||||||
class EntryController extends GetxController {
|
class EntryController extends GetxController {
|
||||||
late ApiService _apiService;
|
late ApiService _apiService;
|
||||||
|
|
||||||
@ -27,6 +29,12 @@ class EntryController extends GetxController {
|
|||||||
final currentEntry = Rx<Entry?>(null);
|
final currentEntry = Rx<Entry?>(null);
|
||||||
final isLoading = true.obs;
|
final isLoading = true.obs;
|
||||||
|
|
||||||
|
final activeEvents = <Event>[].obs; //有効なイベントリスト
|
||||||
|
|
||||||
|
final teamMembers = <User>[].obs;
|
||||||
|
final hasError = false.obs;
|
||||||
|
final errorMessage = "".obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() async {
|
void onInit() async {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
@ -39,7 +47,7 @@ class EntryController extends GetxController {
|
|||||||
_apiService = await Get.putAsync(() => ApiService().init());
|
_apiService = await Get.putAsync(() => ApiService().init());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error initializing ApiService: $e');
|
print('Error initializing ApiService: $e');
|
||||||
Get.snackbar('Error', 'Failed to initialize API service');
|
Get.snackbar('Error', 'APIサービスの初期化に失敗しました');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,24 +60,31 @@ class EntryController extends GetxController {
|
|||||||
fetchTeams(),
|
fetchTeams(),
|
||||||
fetchCategories(),
|
fetchCategories(),
|
||||||
]);
|
]);
|
||||||
|
updateActiveEvents(); // イベント取得後にアクティブなイベントを更新
|
||||||
if (Get.arguments != null && Get.arguments['entry'] != null) {
|
if (Get.arguments != null && Get.arguments['entry'] != null) {
|
||||||
currentEntry.value = Get.arguments['entry'];
|
currentEntry.value = Get.arguments['entry'];
|
||||||
initializeEditMode(currentEntry.value!);
|
initializeEditMode(currentEntry.value!);
|
||||||
} else {
|
} else {
|
||||||
// 新規作成モードの場合、最初のイベントを選択
|
// 新規作成モードの場合、最初のイベントを選択
|
||||||
if (events.isNotEmpty) {
|
if (activeEvents.isNotEmpty) {
|
||||||
selectedEvent.value = events.first;
|
selectedEvent.value = activeEvents.first;
|
||||||
selectedDate.value = events.first.startDatetime;
|
selectedDate.value = activeEvents.first.startDatetime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
print('Error initializing data: $e');
|
print('Error initializing data: $e');
|
||||||
Get.snackbar('Error', 'Failed to load initial data');
|
// エラー状態を設定
|
||||||
|
hasError.value = true;
|
||||||
|
Get.snackbar('Error', '初期データの読み込みに失敗しました');
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateActiveEvents() {
|
||||||
|
final now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo'));
|
||||||
|
activeEvents.assignAll(events.where((event) => event.deadlineDateTime.isAfter(now)));
|
||||||
|
}
|
||||||
|
|
||||||
void initializeEditMode(Entry entry) {
|
void initializeEditMode(Entry entry) {
|
||||||
currentEntry.value = entry;
|
currentEntry.value = entry;
|
||||||
@ -91,12 +106,64 @@ class EntryController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateTeam(Team? value) {
|
Future<void> 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<NewCategory> 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;
|
selectedTeam.value = value;
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
selectedCategory.value = value.category;
|
selectedCategory.value = value.category;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//void updateTeam(Team? value) => selectedTeam.value = value;
|
//void updateTeam(Team? value) => selectedTeam.value = value;
|
||||||
void updateCategory(NewCategory? value) => selectedCategory.value = value;
|
void updateCategory(NewCategory? value) => selectedCategory.value = value;
|
||||||
//void updateDate(DateTime value) => selectedDate.value = value;
|
//void updateDate(DateTime value) => selectedDate.value = value;
|
||||||
@ -143,6 +210,7 @@ class EntryController extends GetxController {
|
|||||||
deadlineDateTime: deadlineDateTime,
|
deadlineDateTime: deadlineDateTime,
|
||||||
);
|
);
|
||||||
}).toList());
|
}).toList());
|
||||||
|
updateActiveEvents();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error fetching events: $e');
|
print('Error fetching events: $e');
|
||||||
Get.snackbar('Error', 'Failed to fetch events');
|
Get.snackbar('Error', 'Failed to fetch events');
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import 'package:gifunavi/model/event.dart';
|
|||||||
import 'package:gifunavi/model/category.dart';
|
import 'package:gifunavi/model/category.dart';
|
||||||
import 'package:gifunavi/model/team.dart';
|
import 'package:gifunavi/model/team.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:gifunavi/widgets/error_widget.dart';
|
||||||
|
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
import 'package:timezone/timezone.dart' as tz;
|
||||||
|
|
||||||
@ -31,6 +32,17 @@ class EntryDetailPage extends GetView<EntryController> {
|
|||||||
if (controller.isLoading.value) {
|
if (controller.isLoading.value) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (controller.hasError.value) {
|
||||||
|
return CustomErrorWidget(
|
||||||
|
errorMessage: controller.errorMessage.value,
|
||||||
|
onRetry: () => controller.loadInitialData(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@ -39,11 +51,24 @@ class EntryDetailPage extends GetView<EntryController> {
|
|||||||
children: [
|
children: [
|
||||||
_buildDropdown<Event>(
|
_buildDropdown<Event>(
|
||||||
label: 'イベント',
|
label: 'イベント',
|
||||||
items: controller.events,
|
items: controller.activeEvents,
|
||||||
selectedId: controller.selectedEvent.value?.id,
|
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(
|
onChanged: (eventId) => controller.updateEvent(
|
||||||
controller.events.firstWhere((e) => e.id == eventId)
|
controller.activeEvents.firstWhere((e) => e.id == eventId)
|
||||||
),
|
),
|
||||||
|
*/
|
||||||
getDisplayName: (event) => event.eventName,
|
getDisplayName: (event) => event.eventName,
|
||||||
getId: (event) => event.id,
|
getId: (event) => event.id,
|
||||||
),
|
),
|
||||||
@ -52,9 +77,21 @@ class EntryDetailPage extends GetView<EntryController> {
|
|||||||
label: 'チーム',
|
label: 'チーム',
|
||||||
items: controller.teams,
|
items: controller.teams,
|
||||||
selectedId: controller.selectedTeam.value?.id,
|
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(
|
onChanged: (teamId) => controller.updateTeam(
|
||||||
controller.teams.firstWhere((t) => t.id == teamId)
|
controller.teams.firstWhere((t) => t.id == teamId)
|
||||||
),
|
),
|
||||||
|
*/
|
||||||
getDisplayName: (team) => team.teamName,
|
getDisplayName: (team) => team.teamName,
|
||||||
getId: (team) => team.id,
|
getId: (team) => team.id,
|
||||||
),
|
),
|
||||||
@ -78,7 +115,9 @@ class EntryDetailPage extends GetView<EntryController> {
|
|||||||
title: const Text('日付'),
|
title: const Text('日付'),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
controller.selectedDate.value != null
|
controller.selectedDate.value != null
|
||||||
? DateFormat('yyyy-MM-dd').format(tz.TZDateTime.from(controller.selectedDate.value!, tz.getLocation('Asia/Tokyo')))
|
? DateFormat('yyyy-MM-dd').format(tz.TZDateTime.from(
|
||||||
|
controller.selectedDate.value!,
|
||||||
|
tz.getLocation('Asia/Tokyo')))
|
||||||
: '日付を選択してください',
|
: '日付を選択してください',
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
@ -86,25 +125,37 @@ class EntryDetailPage extends GetView<EntryController> {
|
|||||||
Get.snackbar('Error', 'Please select an event first');
|
Get.snackbar('Error', 'Please select an event first');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final tz.TZDateTime now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo'));
|
final tz.TZDateTime now = tz.TZDateTime.now(tz
|
||||||
final tz.TZDateTime eventStart = tz.TZDateTime.from(controller.selectedEvent.value!.startDatetime, tz.getLocation('Asia/Tokyo'));
|
.getLocation('Asia/Tokyo'));
|
||||||
final tz.TZDateTime eventEnd = tz.TZDateTime.from(controller.selectedEvent.value!.endDatetime, 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
|
final tz.TZDateTime initialDate = controller.selectedDate
|
||||||
? tz.TZDateTime.from(controller.selectedDate.value!, tz.getLocation('Asia/Tokyo'))
|
.value != null
|
||||||
|
? tz.TZDateTime.from(controller.selectedDate.value!,
|
||||||
|
tz.getLocation('Asia/Tokyo'))
|
||||||
: (now.isAfter(eventStart) ? now : eventStart);
|
: (now.isAfter(eventStart) ? now : eventStart);
|
||||||
|
|
||||||
// 選択可能な最初の日付を設定(今日かイベント開始日のうち、より後の日付)
|
// 選択可能な最初の日付を設定(今日かイベント開始日のうち、より後の日付)
|
||||||
final tz.TZDateTime firstDate = now.isAfter(eventStart) ? now : eventStart;
|
final tz.TZDateTime firstDate = now.isAfter(eventStart)
|
||||||
|
? now
|
||||||
|
: eventStart;
|
||||||
|
|
||||||
final DateTime? picked = await showDatePicker(
|
final DateTime? picked = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialDate: initialDate.isAfter(firstDate) ? initialDate : firstDate,
|
initialDate: initialDate.isAfter(firstDate)
|
||||||
|
? initialDate
|
||||||
|
: firstDate,
|
||||||
firstDate: firstDate,
|
firstDate: firstDate,
|
||||||
lastDate: eventEnd,
|
lastDate: eventEnd,
|
||||||
);
|
);
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
controller.updateDate(tz.TZDateTime.from(picked, tz.getLocation('Asia/Tokyo')));
|
controller.updateDate(tz.TZDateTime.from(
|
||||||
|
picked, tz.getLocation('Asia/Tokyo')));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -151,6 +202,12 @@ class EntryDetailPage extends GetView<EntryController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error in EntryDetailPage: $e');
|
||||||
|
return const Center(
|
||||||
|
child: Text('エラーが発生しました。もう一度お試しください。'),
|
||||||
|
);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -166,15 +223,42 @@ class EntryDetailPage extends GetView<EntryController> {
|
|||||||
return DropdownButtonFormField<int>(
|
return DropdownButtonFormField<int>(
|
||||||
decoration: InputDecoration(labelText: label),
|
decoration: InputDecoration(labelText: label),
|
||||||
value: selectedId,
|
value: selectedId,
|
||||||
items: items.map((item) => DropdownMenuItem<int>(
|
items: items.isNotEmpty ? items.map((item) => DropdownMenuItem<int>(
|
||||||
|
//items: items.map((item) => DropdownMenuItem<int>(
|
||||||
value: getId(item),
|
value: getId(item),
|
||||||
child: Text(getDisplayName(item)),
|
child: Text(getDisplayName(item)),
|
||||||
)).toList(),
|
)).toList() : null,
|
||||||
onChanged: onChanged,
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
onChanged(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//onChanged: onChanged,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCategoryDropdown() {
|
Widget _buildCategoryDropdown() {
|
||||||
|
final eligibleCategories = controller.getFilteredCategories();
|
||||||
|
|
||||||
|
return DropdownButtonFormField<NewCategory>(
|
||||||
|
decoration: InputDecoration(labelText: 'カテゴリ'),
|
||||||
|
value: controller.selectedCategory.value,
|
||||||
|
items: eligibleCategories.isNotEmpty ? eligibleCategories.map((category) => DropdownMenuItem<NewCategory>(
|
||||||
|
|
||||||
|
//items: eligibleCategories.map((category) => DropdownMenuItem<NewCategory>(
|
||||||
|
value: category,
|
||||||
|
child: Text(category.categoryName),
|
||||||
|
)).toList() : null,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
controller.updateCategory(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//onChanged: (value) => controller.updateCategory(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCategoryDropdown_old() {
|
||||||
final eligibleCategories = controller.categories.where((c) =>
|
final eligibleCategories = controller.categories.where((c) =>
|
||||||
c.baseCategory == controller.selectedCategory.value?.baseCategory
|
c.baseCategory == controller.selectedCategory.value?.baseCategory
|
||||||
).toList();
|
).toList();
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import 'package:gifunavi/pages/entry/entry_controller.dart';
|
|||||||
import 'package:gifunavi/routes/app_pages.dart';
|
import 'package:gifunavi/routes/app_pages.dart';
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
import 'package:timezone/timezone.dart' as tz;
|
||||||
|
|
||||||
|
import 'package:gifunavi/model/entry.dart';
|
||||||
|
|
||||||
class EntryListPage extends GetView<EntryController> {
|
class EntryListPage extends GetView<EntryController> {
|
||||||
const EntryListPage({super.key});
|
const EntryListPage({super.key});
|
||||||
|
|
||||||
@ -28,11 +30,41 @@ class EntryListPage extends GetView<EntryController> {
|
|||||||
child: Text('表示するエントリーがありません。'),
|
child: Text('表示するエントリーがありません。'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final sortedEntries = controller.entries.toList()
|
||||||
|
..sort((b, a) => a.date!.compareTo(b.date!));
|
||||||
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: controller.entries.length,
|
itemCount: sortedEntries.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final entry = controller.entries[index];
|
final entry = sortedEntries[index];
|
||||||
|
//final now = DateTime.now();
|
||||||
|
//print("now=$now");
|
||||||
|
//final isEntryInFuture = _compareDatesOnly(entry.date!, now) >= 0;
|
||||||
|
final now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo'));
|
||||||
|
final entryDate = tz.TZDateTime.from(entry.date!, tz.getLocation('Asia/Tokyo'));
|
||||||
|
|
||||||
|
// 日付のみを比較(時間を無視)
|
||||||
|
final isEntryInFuture = _compareDatesOnly(entryDate, now) >= 0;
|
||||||
|
|
||||||
|
//final isEntryInFuture = entry.date!.isAfter(now) || entry.date!.isAtSameMomentAs(now);
|
||||||
|
|
||||||
|
Widget? leadingIcon;
|
||||||
|
if (!isEntryInFuture) {
|
||||||
|
if (entry.hasParticipated) {
|
||||||
|
if (entry.hasGoaled) {
|
||||||
|
leadingIcon =
|
||||||
|
const Icon(Icons.check_circle, color: Colors.green);
|
||||||
|
} else {
|
||||||
|
leadingIcon = const Icon(Icons.warning, color: Colors.yellow);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
leadingIcon = const Icon(Icons.cancel, color: Colors.red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
leading: leadingIcon,
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -49,9 +81,16 @@ class EntryListPage extends GetView<EntryController> {
|
|||||||
Text('ゼッケン: ${entry.zekkenNumber ?? "未設定"}'),
|
Text('ゼッケン: ${entry.zekkenNumber ?? "未設定"}'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () =>
|
onTap: () {
|
||||||
|
if (isEntryInFuture) {
|
||||||
Get.toNamed(AppPages.ENTRY_DETAIL,
|
Get.toNamed(AppPages.ENTRY_DETAIL,
|
||||||
arguments: {'mode': 'edit', 'entry': entry}),
|
arguments: {'mode': 'edit', 'entry': entry});
|
||||||
|
} else if (entry.hasParticipated) {
|
||||||
|
Get.toNamed(AppPages.EVENT_RESULT, arguments: {'entry': entry});
|
||||||
|
} else {
|
||||||
|
_showNonParticipationDialog(context, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -59,6 +98,11 @@ class EntryListPage extends GetView<EntryController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新しく追加するメソッド
|
||||||
|
int _compareDatesOnly(DateTime a, DateTime b) {
|
||||||
|
return DateTime(a.year, a.month, a.day).compareTo(DateTime(b.year, b.month, b.day));
|
||||||
|
}
|
||||||
|
|
||||||
String _formatDate(DateTime? date) {
|
String _formatDate(DateTime? date) {
|
||||||
if (date == null) {
|
if (date == null) {
|
||||||
return '日時未設定';
|
return '日時未設定';
|
||||||
@ -66,6 +110,26 @@ class EntryListPage extends GetView<EntryController> {
|
|||||||
final jstDate = tz.TZDateTime.from(date, tz.getLocation('Asia/Tokyo'));
|
final jstDate = tz.TZDateTime.from(date, tz.getLocation('Asia/Tokyo'));
|
||||||
return DateFormat('yyyy-MM-dd').format(jstDate);
|
return DateFormat('yyyy-MM-dd').format(jstDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showNonParticipationDialog(BuildContext context, Entry entry) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(entry.event.eventName),
|
||||||
|
content: Text('${_formatDate(entry.date)}\n\n不参加でした'),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: const Text('閉じる'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EntryListPage_old extends GetView<EntryController> {
|
class EntryListPage_old extends GetView<EntryController> {
|
||||||
|
|||||||
153
lib/pages/entry/event_result_page.dart
Normal file
153
lib/pages/entry/event_result_page.dart
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:gifunavi/model/entry.dart';
|
||||||
|
import 'package:gifunavi/pages/gps/gps_controller.dart';
|
||||||
|
import 'package:gifunavi/pages/history/history_controller.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class EventResultPage extends StatefulWidget {
|
||||||
|
final Entry entry;
|
||||||
|
|
||||||
|
const EventResultPage({super.key, required this.entry});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EventResultPage> createState() => _EventResultPageState();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EventResultPageState extends State<EventResultPage> {
|
||||||
|
late GpsController gpsController;
|
||||||
|
late HistoryController historyController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
gpsController = Get.put(GpsController());
|
||||||
|
historyController = Get.put(HistoryController());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DefaultTabController(
|
||||||
|
length: 3,
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('${widget.entry.event.eventName} 結果'),
|
||||||
|
bottom: const TabBar(
|
||||||
|
tabs: [
|
||||||
|
Tab(text: 'ランキング'),
|
||||||
|
Tab(text: '走行経路'),
|
||||||
|
Tab(text: 'チェックポイント'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: TabBarView(
|
||||||
|
children: [
|
||||||
|
_buildFutureTab(), //_buildRankingTab(),
|
||||||
|
_buildFutureTab(), //_buildRouteTab(),
|
||||||
|
_buildFutureTab(), //_buildCheckpointTab(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFutureTab() {
|
||||||
|
// ランキングの表示ロジックを実装
|
||||||
|
return const Center(child: Text('近日公開予定(当面、HPを参照ください。)'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Widget _buildRankingTab() {
|
||||||
|
// ランキングの表示ロジックを実装
|
||||||
|
return const Center(child: Text('ランキング表示(実装が必要)'));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRouteTab() {
|
||||||
|
return Obx(() {
|
||||||
|
if (gpsController.gpsData.isEmpty) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
return FlutterMap(
|
||||||
|
options: MapOptions(
|
||||||
|
center: LatLng(gpsController.gpsData[0].lat, gpsController.gpsData[0].lon),
|
||||||
|
zoom: 13.0,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TileLayer(
|
||||||
|
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||||
|
subdomains: ['a', 'b', 'c'],
|
||||||
|
),
|
||||||
|
PolylineLayer(
|
||||||
|
polylines: [
|
||||||
|
Polyline(
|
||||||
|
points: gpsController.gpsData
|
||||||
|
.map((data) => LatLng(data.lat, data.lon))
|
||||||
|
.toList(),
|
||||||
|
color: Colors.red,
|
||||||
|
strokeWidth: 3.0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
MarkerLayer(
|
||||||
|
markers: gpsController.gpsData
|
||||||
|
.map((data) => Marker(
|
||||||
|
width: 80.0,
|
||||||
|
height: 80.0,
|
||||||
|
point: LatLng(data.lat, data.lon),
|
||||||
|
child: const Icon(Icons.location_on, color: Colors.red),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCheckpointTab() {
|
||||||
|
return Obx(() {
|
||||||
|
if (historyController.checkpoints.isEmpty) {
|
||||||
|
return const Center(child: Text('チェックポイント履歴がありません'));
|
||||||
|
}
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: historyController.checkpoints.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final checkpoint = historyController.checkpoints[index];
|
||||||
|
return ListTile(
|
||||||
|
title: Text('CP ${checkpoint.cp_number ?? 'Unknown'}'),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('通過時刻: ${_formatDateTime(checkpoint.checkintime)}'),
|
||||||
|
Text('チーム: ${checkpoint.team_name ?? 'Unknown'}'),
|
||||||
|
Text('イベント: ${checkpoint.event_code ?? 'Unknown'}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
leading: checkpoint.image != null
|
||||||
|
? Image.file(
|
||||||
|
File(checkpoint.image!),
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
print('Error loading image: $error');
|
||||||
|
return const Icon(Icons.error);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const Icon(Icons.image_not_supported),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDateTime(int? microsecondsSinceEpoch) {
|
||||||
|
if (microsecondsSinceEpoch == null) return 'Unknown';
|
||||||
|
final dateTime = DateTime.fromMicrosecondsSinceEpoch(microsecondsSinceEpoch);
|
||||||
|
return DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime);
|
||||||
|
}
|
||||||
22
lib/pages/gps/gps_controller.dart
Normal file
22
lib/pages/gps/gps_controller.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:gifunavi/model/gps_data.dart';
|
||||||
|
import 'package:gifunavi/utils/database_gps.dart';
|
||||||
|
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||||
|
|
||||||
|
class GpsController extends GetxController {
|
||||||
|
final gpsData = <GpsData>[].obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
loadGpsData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadGpsData() async {
|
||||||
|
final teamName = Get.find<IndexController>().currentUser[0]["user"]['team_name'];
|
||||||
|
final eventCode = Get.find<IndexController>().currentUser[0]["user"]["event_code"];
|
||||||
|
GpsDatabaseHelper db = GpsDatabaseHelper.instance;
|
||||||
|
var data = await db.getGPSData(teamName, eventCode);
|
||||||
|
gpsData.value = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
lib/pages/history/history_controller.dart
Normal file
22
lib/pages/history/history_controller.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:gifunavi/model/rog.dart';
|
||||||
|
import 'package:gifunavi/utils/database_helper.dart';
|
||||||
|
|
||||||
|
class HistoryController extends GetxController {
|
||||||
|
final checkpoints = <Rog>[].obs; // Rog オブジェクトのリストに変更
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
loadCheckpoints();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadCheckpoints() async {
|
||||||
|
DatabaseHelper db = DatabaseHelper.instance;
|
||||||
|
var data = await db.allRogianing();
|
||||||
|
checkpoints.value = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -240,14 +240,14 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
try {
|
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
try {
|
||||||
initConnectivity();
|
initConnectivity();
|
||||||
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
|
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
//WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
await PermissionController.checkAndRequestPermissions();
|
// await PermissionController.checkAndRequestPermissions();
|
||||||
});
|
//});
|
||||||
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_startLocationService(); // アプリ起動時にLocationServiceを開始する
|
_startLocationService(); // アプリ起動時にLocationServiceを開始する
|
||||||
|
|||||||
@ -40,7 +40,7 @@ class _IndexPageState extends State<IndexPage> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
await _ensureControllersAreInitialized();
|
await _ensureControllersAreInitialized();
|
||||||
await PermissionController.checkAndRequestPermissions();
|
//await PermissionController.checkAndRequestPermissions();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -167,6 +167,17 @@ class PermissionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<bool> showLocationDisclosure() async {
|
static Future<bool> showLocationDisclosure() async {
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
return await _showLocationDisclosureIOS();
|
||||||
|
} else if (Platform.isAndroid) {
|
||||||
|
return await _showLocationDisclosureAndroid();
|
||||||
|
} else {
|
||||||
|
// その他のプラットフォームの場合はデフォルトの処理を行う
|
||||||
|
return await _showLocationDisclosureIOS();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> _showLocationDisclosureIOS() async {
|
||||||
if (Get.context == null) {
|
if (Get.context == null) {
|
||||||
print('Context is null, cannot show dialog');
|
print('Context is null, cannot show dialog');
|
||||||
return false;
|
return false;
|
||||||
@ -214,6 +225,41 @@ class PermissionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<bool> _showLocationDisclosureAndroid() async {
|
||||||
|
return await showDialog<bool>(
|
||||||
|
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: <Widget>[
|
||||||
|
Text('このアプリでは、以下の目的で位置情報を使用します:'),
|
||||||
|
Text('• チェックポイントの自動チェックイン(アプリが閉じているときも含む)'),
|
||||||
|
Text('• 移動履歴の記録(バックグラウンドでも継続)'),
|
||||||
|
Text('• 現在地周辺の情報表示'),
|
||||||
|
Text('\nバックグラウンドでも位置情報を継続的に取得します。'),
|
||||||
|
Text('これにより、バッテリーの消費が増加する可能性があります。'),
|
||||||
|
Text('同意しない場合には、アプリは終了します。'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
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) {
|
static void showPermissionDeniedDialog(String title,String message) {
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
|
|||||||
@ -329,13 +329,6 @@ class TeamController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<NewCategory> getFilteredCategories_old() {
|
List<NewCategory> getFilteredCategories_old() {
|
||||||
//List<User> teamMembers = getCurrentTeamMembers();
|
|
||||||
return categories.where((category) {
|
|
||||||
return isCategoryValid(category, teamMembers);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<NewCategory> getFilteredCategories() {
|
|
||||||
if (teamMembers.isEmpty && currentUser.value != null) {
|
if (teamMembers.isEmpty && currentUser.value != null) {
|
||||||
// ソロの場合
|
// ソロの場合
|
||||||
String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子';
|
String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子';
|
||||||
@ -347,6 +340,24 @@ class TeamController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<NewCategory> 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) {
|
bool isElementarySchoolOrYounger(User user) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final age = now.year - user.dateOfBirth!.year;
|
final age = now.year - user.dateOfBirth!.year;
|
||||||
@ -379,17 +390,7 @@ class TeamController extends GetxController {
|
|||||||
// メンバーリストの最新状態を取得
|
// メンバーリストの最新状態を取得
|
||||||
await fetchTeamMembers(selectedTeam.value!.id);
|
await fetchTeamMembers(selectedTeam.value!.id);
|
||||||
|
|
||||||
List<NewCategory> eligibleCategories = [];
|
List<NewCategory> eligibleCategories = getFilteredCategories();
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同じ時間のカテゴリを優先的に選択
|
// 同じ時間のカテゴリを優先的に選択
|
||||||
NewCategory? newCategory = eligibleCategories.firstWhereOrNull((c) => c.time == oldTime);
|
NewCategory? newCategory = eligibleCategories.firstWhereOrNull((c) => c.time == oldTime);
|
||||||
|
|||||||
@ -120,15 +120,19 @@ class _TeamDetailPageState extends State<TeamDetailPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final filteredCategories = controller.getFilteredCategories();
|
final filteredCategories = controller.getFilteredCategories();
|
||||||
final categoriesToDisplay = filteredCategories.isEmpty
|
// 選択されているカテゴリが表示リストに含まれていない場合、最初の項目を選択する
|
||||||
? controller.categories
|
if (controller.selectedCategory.value == null ||
|
||||||
: filteredCategories;
|
!filteredCategories.contains(controller.selectedCategory.value)) {
|
||||||
|
controller.updateCategory(filteredCategories.isNotEmpty
|
||||||
|
? filteredCategories.first
|
||||||
|
: null);
|
||||||
|
}
|
||||||
|
|
||||||
// 選択されているカテゴリが表示リストに含まれていない場合、最初の項目を選択する
|
// 選択されているカテゴリが表示リストに含まれていない場合、最初の項目を選択する
|
||||||
if (controller.selectedCategory.value == null ||
|
if (controller.selectedCategory.value == null ||
|
||||||
!categoriesToDisplay.contains(controller.selectedCategory.value)) {
|
!filteredCategories.contains(controller.selectedCategory.value)) {
|
||||||
controller.updateCategory(categoriesToDisplay.isNotEmpty
|
controller.updateCategory(filteredCategories.isNotEmpty
|
||||||
? categoriesToDisplay.first
|
? filteredCategories.first
|
||||||
: null);
|
: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +151,7 @@ class _TeamDetailPageState extends State<TeamDetailPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (categoriesToDisplay.isEmpty)
|
if (filteredCategories.isEmpty)
|
||||||
const Text('カテゴリデータを読み込めませんでした。',
|
const Text('カテゴリデータを読み込めませんでした。',
|
||||||
style: TextStyle(color: Colors.red))
|
style: TextStyle(color: Colors.red))
|
||||||
else
|
else
|
||||||
@ -156,7 +160,7 @@ class _TeamDetailPageState extends State<TeamDetailPage> {
|
|||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'カテゴリ'),
|
labelText: 'カテゴリ'),
|
||||||
value: controller.selectedCategory.value,
|
value: controller.selectedCategory.value,
|
||||||
items: categoriesToDisplay.map((category) =>
|
items: filteredCategories.map((category) =>
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
value: category,
|
value: category,
|
||||||
child: Text(category.categoryName),
|
child: Text(category.categoryName),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get/get_navigation/src/routes/get_route.dart';
|
import 'package:get/get_navigation/src/routes/get_route.dart';
|
||||||
import 'package:gifunavi/pages/changepassword/change_password_page.dart';
|
import 'package:gifunavi/pages/changepassword/change_password_page.dart';
|
||||||
@ -34,6 +35,9 @@ import 'package:gifunavi/pages/entry/event_entries_page.dart';
|
|||||||
import 'package:gifunavi/pages/entry/event_entries_binding.dart';
|
import 'package:gifunavi/pages/entry/event_entries_binding.dart';
|
||||||
import 'package:gifunavi/pages/register/user_detail_page.dart';
|
import 'package:gifunavi/pages/register/user_detail_page.dart';
|
||||||
|
|
||||||
|
import 'package:gifunavi/pages/entry/event_result_page.dart';
|
||||||
|
import 'package:gifunavi/model/entry.dart';
|
||||||
|
|
||||||
part 'app_routes.dart';
|
part 'app_routes.dart';
|
||||||
|
|
||||||
class AppPages {
|
class AppPages {
|
||||||
@ -70,6 +74,7 @@ class AppPages {
|
|||||||
static const EVENT_ENTRY = Routes.EVENT_ENTRIES;
|
static const EVENT_ENTRY = Routes.EVENT_ENTRIES;
|
||||||
static const USER_DETAILS_EDIT = Routes.USER_DETAILS_EDIT;
|
static const USER_DETAILS_EDIT = Routes.USER_DETAILS_EDIT;
|
||||||
|
|
||||||
|
static const EVENT_RESULT = Routes.EVENT_RESULT;
|
||||||
|
|
||||||
static final routes = [
|
static final routes = [
|
||||||
GetPage(
|
GetPage(
|
||||||
@ -175,6 +180,23 @@ class AppPages {
|
|||||||
name: Routes.USER_DETAILS_EDIT,
|
name: Routes.USER_DETAILS_EDIT,
|
||||||
page: () => const UserDetailsEditPage(),
|
page: () => const UserDetailsEditPage(),
|
||||||
),
|
),
|
||||||
|
GetPage(
|
||||||
|
name: Routes.EVENT_RESULT,
|
||||||
|
page: () {
|
||||||
|
final args = Get.arguments;
|
||||||
|
if (args is Map<String, dynamic> && args.containsKey('entry')) {
|
||||||
|
return EventResultPage(entry: args['entry'] as Entry);
|
||||||
|
} else {
|
||||||
|
// エントリーが提供されていない場合のフォールバック
|
||||||
|
// 例: エラーページを表示するか、ホームページにリダイレクトする
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Text('エラー: イベント結果を表示できません。'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,4 +38,5 @@ abstract class Routes {
|
|||||||
static const EVENT_ENTRIES = '/event-entries';
|
static const EVENT_ENTRIES = '/event-entries';
|
||||||
static const USER_DETAILS_EDIT = '/user-details-edit';
|
static const USER_DETAILS_EDIT = '/user-details-edit';
|
||||||
|
|
||||||
|
static const EVENT_RESULT = '/event-result';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,7 +87,7 @@ class ApiService extends GetxService{
|
|||||||
Future<dynamic> _handleRequest(Future<http.Response> Function() request) async {
|
Future<dynamic> _handleRequest(Future<http.Response> Function() request) async {
|
||||||
try {
|
try {
|
||||||
final response = await request();
|
final response = await request();
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
return json.decode(utf8.decode(response.bodyBytes));
|
return json.decode(utf8.decode(response.bodyBytes));
|
||||||
} else if (response.statusCode == 401) {
|
} else if (response.statusCode == 401) {
|
||||||
await _handleUnauthorized();
|
await _handleUnauthorized();
|
||||||
|
|||||||
@ -370,6 +370,15 @@ class ExternalService {
|
|||||||
|
|
||||||
//int userId = indexController.currentUser[0]["user"]["id"];
|
//int userId = indexController.currentUser[0]["user"]["id"];
|
||||||
//print("--- Pressed -----");
|
//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'];
|
String team = indexController.currentUser[0]["user"]['team_name'];
|
||||||
//print("--- _team : ${_team}-----");
|
//print("--- _team : ${_team}-----");
|
||||||
String eventCode = indexController.currentUser[0]["user"]["event_code"];
|
String eventCode = indexController.currentUser[0]["user"]["event_code"];
|
||||||
|
|||||||
@ -248,7 +248,7 @@ class LocationController extends GetxController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await PermissionController.checkAndRequestPermissions();
|
//await PermissionController.checkAndRequestPermissions();
|
||||||
|
|
||||||
// 位置情報の設定を行います。z11
|
// 位置情報の設定を行います。z11
|
||||||
// Set up the location options
|
// Set up the location options
|
||||||
|
|||||||
35
lib/widgets/error_widget.dart
Normal file
35
lib/widgets/error_widget.dart
Normal file
@ -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('再試行'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -54,9 +54,11 @@ class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
|
|||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
// 追加
|
// 追加
|
||||||
|
/*
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
PermissionController.checkAndRequestPermissions();
|
PermissionController.checkAndRequestPermissions();
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
debugPrint('MapWidget: initState called');
|
debugPrint('MapWidget: initState called');
|
||||||
SettingsBinding().dependencies(); // これを追加
|
SettingsBinding().dependencies(); // これを追加
|
||||||
@ -83,7 +85,7 @@ class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
|
|||||||
});
|
});
|
||||||
// MapControllerの初期化が完了したら、IndexControllerのonInitを呼び出す
|
// MapControllerの初期化が完了したら、IndexControllerのonInitを呼び出す
|
||||||
//indexController.checkPermission();
|
//indexController.checkPermission();
|
||||||
PermissionController.checkAndRequestPermissions();
|
//PermissionController.checkAndRequestPermissions();
|
||||||
});
|
});
|
||||||
|
|
||||||
late MapResetController mapResetController = MapResetController();
|
late MapResetController mapResetController = MapResetController();
|
||||||
|
|||||||
@ -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
|
# 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
|
# 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.
|
# 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:
|
environment:
|
||||||
sdk: ^3.5.0
|
sdk: ^3.5.0
|
||||||
|
|||||||
Reference in New Issue
Block a user