1 Commits

Author SHA1 Message Date
e37c4ceebd temporary update 2024-09-08 18:16:51 +09:00
33 changed files with 1131 additions and 1701 deletions

View File

@ -1,16 +1 @@
不参加の過去エントリーは削除できるようにする 期限切れのイベントは表示しない。==> メッセージで期限切れであることを表示すること
バグ:
履歴の写真:アクセスエラー
バックアップをイベントごとに保存・レストア
ログインした際に、イベントが選択されていなければ、イベントを選択するように促す。
事前チェックインした写真が履歴に表示されない。
ユーザー名間違えたらログインできなくなる。
起動時に最後の参加イベントが過去日だったら、
チェックポイントをクリアする。
当日なら、参加処理?をしてタイトルを変える。
チーム構成とエントリーの相関が難しいのでは??

View File

@ -1,8 +0,0 @@
-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(...);
}

View File

@ -1,5 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools"
package="com.dvox.gifunavi">
<uses-feature <uses-feature
android:name="android.hardware.camera" android:name="android.hardware.camera"
android:required="false" /> android:required="false" />
@ -10,16 +11,16 @@
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
android:maxSdkVersion="34" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<application <application
android:label="岐阜ナビ" android:label="岐阜ナビ"
android:icon="@mipmap/launcher_icon"> android:icon="@mipmap/launcher_icon">
<activity <activity
android:name="com.dvox.gifunavi.MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:taskAffinity="" android:taskAffinity=""
@ -27,7 +28,7 @@
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
tools:ignore="Instantiatable"> >
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
@ -49,7 +50,8 @@
<meta-data android:name="com.google.android.geo.API_KEY" <meta-data android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE"/> android:value="AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE"/>
 <service  <service
android:name="com.dvox.gifunavi.LocationService" android:name=".LocationService"
android:enableOnBackInvokedCallback="true"
android:enabled="true" android:enabled="true"
android:exported="false" android:exported="false"
android:foregroundServiceType="location" /> android:foregroundServiceType="location" />

View File

@ -1,5 +0,0 @@
package com.dvox.gifunavi_git
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View File

@ -12,6 +12,7 @@ import CoreMotion
) -> Bool { ) -> Bool {
//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: "net.sumasen.gifunavi/motion", let motionChannel = FlutterMethodChannel(name: "net.sumasen.gifunavi/motion",
binaryMessenger: controller.binaryMessenger) binaryMessenger: controller.binaryMessenger)
@ -33,17 +34,58 @@ import CoreMotion
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
//
// 姿
//
// sendMotionData使Flutter
private func startMotionUpdates(result: @escaping FlutterResult) { private func startMotionUpdates(result: @escaping FlutterResult) {
if motionManager.isDeviceMotionAvailable { if motionManager.isDeviceMotionAvailable {
motionManager.deviceMotionUpdateInterval = 0.1 motionManager.deviceMotionUpdateInterval = 0.1
motionManager.startDeviceMotionUpdates(to: .main) { (motion, error) in motionManager.startDeviceMotionUpdates(to: .main) { [weak self] (motion, error) in
guard let self = self else { return }
if let error = error {
DispatchQueue.main.async { DispatchQueue.main.async {
// UI result(FlutterError(code: "MOTION_ERROR",
let appState = UIApplication.shared.applicationState message: error.localizedDescription,
// details: nil))
}
return
}
guard let motion = motion else {
DispatchQueue.main.async {
result(FlutterError(code: "NO_MOTION_DATA",
message: "No motion data available",
details: nil))
}
return
}
DispatchQueue.main.async {
let motionData: [String: Any] = [
"attitude": [
"roll": motion.attitude.roll,
"pitch": motion.attitude.pitch,
"yaw": motion.attitude.yaw
],
"gravity": [
"x": motion.gravity.x,
"y": motion.gravity.y,
"z": motion.gravity.z
],
"userAcceleration": [
"x": motion.userAcceleration.x,
"y": motion.userAcceleration.y,
"z": motion.userAcceleration.z
]
]
self.sendMotionData(motionData)
} }
} }
result(nil) result(nil) //
} else { } else {
result(FlutterError(code: "UNAVAILABLE", result(FlutterError(code: "UNAVAILABLE",
message: "Device motion is not available.", message: "Device motion is not available.",
@ -51,6 +93,12 @@ import CoreMotion
} }
} }
private func sendMotionData(_ data: [String: Any]) {
let motionChannel = FlutterMethodChannel(name: "net.sumasen.gifunavi/motion",
binaryMessenger: (window?.rootViewController as! FlutterViewController).binaryMessenger)
motionChannel.invokeMethod("onMotionData", arguments: data)
}
private func stopMotionUpdates(result: @escaping FlutterResult) { private func stopMotionUpdates(result: @escaping FlutterResult) {
motionManager.stopDeviceMotionUpdates() motionManager.stopDeviceMotionUpdates()
result(nil) result(nil)

View File

@ -20,32 +20,35 @@
<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" 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="122.99999999999999" width="301" height="38.333333333333329"/> <rect key="frame" x="36" y="123.99999999999999" width="321" height="38.333333333333329"/>
<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" misplaced="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" text="NPO 岐阜aiネットワーク" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TM1-SD-6RA">
<rect key="frame" x="46" y="708" width="301" height="40"/> <rect key="frame" x="36" y="717.66666666666663" width="321" height="20.333333333333371"/>
<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" 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="46" y="241.33333333333334" width="301" height="341.33333333333326"/> <rect key="frame" x="36" y="212.33333333333334" width="321" height="405.33333333333326"/>
<constraints>
<constraint firstAttribute="width" secondItem="hCH-Iu-4S2" secondAttribute="height" multiplier="270:341" id="jg6-gh-iGg"/>
</constraints>
</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> <constraints>
<constraint firstItem="xbc-2k-c8Z" firstAttribute="top" secondItem="TM1-SD-6RA" secondAttribute="bottom" constant="70" id="DbA-tx-JAk"/> <constraint firstAttribute="trailingMargin" secondItem="WPs-nj-CIV" secondAttribute="trailing" constant="20" id="1UA-eg-bma"/>
<constraint firstAttribute="trailingMargin" secondItem="hCH-Iu-4S2" secondAttribute="trailing" constant="30" id="DsC-fI-z7h"/> <constraint firstAttribute="trailingMargin" secondItem="hCH-Iu-4S2" secondAttribute="trailing" constant="20" id="3lP-Fl-Xzw"/>
<constraint firstAttribute="trailingMargin" secondItem="TM1-SD-6RA" secondAttribute="trailing" constant="30" id="Spt-qL-bHo"/> <constraint firstAttribute="trailingMargin" secondItem="TM1-SD-6RA" secondAttribute="trailing" constant="20" id="E8j-R8-JVy"/>
<constraint firstItem="WPs-nj-CIV" firstAttribute="top" secondItem="Ydg-fD-yQy" secondAttribute="bottom" constant="64" id="Wf4-J5-CP7"/> <constraint firstItem="xbc-2k-c8Z" firstAttribute="top" secondItem="TM1-SD-6RA" secondAttribute="bottom" constant="80" id="MKZ-EI-0XJ"/>
<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="20" id="N5f-Kk-hnZ"/>
<constraint firstItem="hCH-Iu-4S2" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" constant="30" id="oZr-ky-01l"/> <constraint firstItem="TM1-SD-6RA" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" constant="20" id="OUN-wm-60h"/>
<constraint firstItem="WPs-nj-CIV" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" constant="30" id="pEC-jM-hHf"/> <constraint firstItem="WPs-nj-CIV" firstAttribute="top" secondItem="Ydg-fD-yQy" secondAttribute="bottom" constant="65" id="aPG-JX-zf5"/>
<constraint firstItem="TM1-SD-6RA" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" constant="30" id="pRM-dy-ucg"/> <constraint firstItem="hCH-Iu-4S2" firstAttribute="top" secondItem="WPs-nj-CIV" secondAttribute="bottom" constant="50" id="jKu-Hc-2ln"/>
<constraint firstAttribute="trailingMargin" secondItem="WPs-nj-CIV" secondAttribute="trailing" constant="30" id="ugk-x4-Wgc"/> <constraint firstItem="WPs-nj-CIV" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" constant="20" id="u9d-2i-Jy1"/>
</constraints> </constraints>
</view> </view>
</viewController> </viewController>

View File

@ -1,8 +1,8 @@
import UIKit
import Flutter import Flutter
import UIKit
import CoreMotion import CoreMotion
@UIApplicationMain @main
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
private let motionManager = CMMotionManager() private let motionManager = CMMotionManager()
@ -10,8 +10,11 @@ import CoreMotion
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
//GeneratedPluginRegistrant.register(with: self)
//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
@ -31,17 +34,58 @@ import CoreMotion
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
//
// 姿
//
// sendMotionData使Flutter
private func startMotionUpdates(result: @escaping FlutterResult) { private func startMotionUpdates(result: @escaping FlutterResult) {
if motionManager.isDeviceMotionAvailable { if motionManager.isDeviceMotionAvailable {
motionManager.deviceMotionUpdateInterval = 0.1 motionManager.deviceMotionUpdateInterval = 0.1
motionManager.startDeviceMotionUpdates(to: .main) { (motion, error) in motionManager.startDeviceMotionUpdates(to: .main) { (motion, error) in
guard let self = self else { return }
if let error = error {
DispatchQueue.main.async { DispatchQueue.main.async {
// UI result(FlutterError(code: "MOTION_ERROR",
let appState = UIApplication.shared.applicationState message: error.localizedDescription,
// details: nil))
}
return
}
guard let motion = motion else {
DispatchQueue.main.async {
result(FlutterError(code: "NO_MOTION_DATA",
message: "No motion data available",
details: nil))
}
return
}
DispatchQueue.main.async {
let motionData: [String: Any] = [
"attitude": [
"roll": motion.attitude.roll,
"pitch": motion.attitude.pitch,
"yaw": motion.attitude.yaw
],
"gravity": [
"x": motion.gravity.x,
"y": motion.gravity.y,
"z": motion.gravity.z
],
"userAcceleration": [
"x": motion.userAcceleration.x,
"y": motion.userAcceleration.y,
"z": motion.userAcceleration.z
]
]
self.sendMotionData(motionData)
} }
} }
result(nil) result(nil) //
} else { } else {
result(FlutterError(code: "UNAVAILABLE", result(FlutterError(code: "UNAVAILABLE",
message: "Device motion is not available.", message: "Device motion is not available.",
@ -49,6 +93,12 @@ import CoreMotion
} }
} }
private func sendMotionData(_ data: [String: Any]) {
let motionChannel = FlutterMethodChannel(name: "net.sumasen.gifunavi/motion",
 binaryMessenger: (window?.rootViewController as! FlutterViewController).binaryMessenger)
motionChannel.invokeMethod("onMotionData", arguments: data)
}
private func stopMotionUpdates(result: @escaping FlutterResult) { private func stopMotionUpdates(result: @escaping FlutterResult) {
motionManager.stopDeviceMotionUpdates() motionManager.stopDeviceMotionUpdates()
result(nil) result(nil)

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
//import 'dart:convert'; //import 'dart:convert';
//import 'dart:developer'; //import 'dart:developer';
import 'package:flutter_map/flutter_map.dart';
import 'package:gifunavi/model/gps_data.dart'; import 'package:gifunavi/model/gps_data.dart';
//import 'package:gifunavi/pages/home/home_page.dart'; //import 'package:gifunavi/pages/home/home_page.dart';
import 'package:gifunavi/utils/database_gps.dart'; import 'package:gifunavi/utils/database_gps.dart';
@ -88,9 +89,6 @@ 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());
} }
@ -111,20 +109,6 @@ 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);
@ -160,24 +144,6 @@ 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();
@ -233,6 +199,16 @@ void _showEventSelectionWarning() {
); );
} }
// main.dart の上部に追加
const bool isDebugMode = true; // リリース時にfalseに変更
// 各ファイルで使用
void debugLog(String message) {
if (isDebugMode) {
debugPrint('DEBUG: $message');
}
}
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -262,20 +238,89 @@ void main() async {
} }
} }
Future <void> _initializeControllers() async {
final stopwatch = Stopwatch()..start();
const timeout = Duration(seconds: 30); // タイムアウト時間を10秒から30秒に延長
try {
while (!_areAllControllersRegistered()) {
if (stopwatch.elapsed > timeout) {
throw TimeoutException('Controller initialization timed out');
}
await Future.delayed(const Duration(milliseconds: 100));
}
final LocationController _locationController = Get.find<LocationController>();
final IndexController _indexController = Get.find<IndexController>();
final DestinationController _destinationController = Get.find<DestinationController>();
final PermissionController _permissionController = Get.find<PermissionController>();
print('All controllers initialized successfully');
} catch (e) {
print('Error initializing controllers: $e');
_handleInitializationError();
}
}
bool _areAllControllersRegistered() {
return Get.isRegistered<LocationController>() &&
Get.isRegistered<IndexController>() &&
Get.isRegistered<DestinationController>() &&
Get.isRegistered<PermissionController>();
}
void _handleInitializationError() {
// エラーハンドリングのロジックをここに実装
// 例: エラーダイアログの表示、アプリの再起動など
print("_handleInitializationError");
}
Future<void> initServices() async { Future<void> initServices() async {
print('Starting services ...'); print('Starting services ...');
try { try {
//await Get.putAsync(() => ApiService().init()); await Get.putAsync(() => ApiService().init());
await _initApiService(); //await _initApiService();
debugPrint("1: start ApiService"); debugPrint("1: start ApiService");
if (Platform.isIOS ) {
// コントローラーを初期化 // コントローラーを初期化
/*
Get.put(IndexController(apiService: Get.find<ApiService>()), permanent: true); Get.put(IndexController(apiService: Get.find<ApiService>()), permanent: true);
Get.put(SettingsController(), permanent: true); Get.put(SettingsController(), permanent: true);
Get.put(DestinationController(), permanent: true); Get.put(DestinationController(), permanent: true);
Get.put(LocationController(), permanent: true); Get.put(LocationController(), permanent: true);
*/
// すべてのコントローラーとサービスを非同期で初期化
Get.lazyPut(() => IndexController(apiService: Get.find<ApiService>()));
debugPrint("2: start IndexController");
Get.lazyPut(() => MapController());
debugPrint("2: start MapController");
// その他のコントローラーを遅延初期化
Get.lazyPut(() => SettingsController());
debugPrint("2: start SettingsController");
Get.lazyPut(() => DestinationController());
debugPrint("3: start DestinationController");
Get.lazyPut(() => LocationController());
debugPrint("4: start LocationController");
//await _initializeControllers();
debugPrint("2: Controllers initialized"); debugPrint("2: Controllers initialized");
}else {
// すべてのコントローラーとサービスを非同期で初期化
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");
Get.lazyPut(() => PermissionController());
}
// 非同期処理を並列実行 // 非同期処理を並列実行
await Future.wait([ await Future.wait([
@ -286,13 +331,13 @@ Future<void> initServices() async {
print('=== 5. Initialized TimeZone...'); print('=== 5. Initialized TimeZone...');
print('=== 6. CacheProvider started...'); print('=== 6. CacheProvider started...');
Get.put(PermissionController());
await _checkPermissions(); await _checkPermissions();
debugPrint("7: start PermissionController"); debugPrint("7: start PermissionController");
//await PermissionController.checkAndRequestPermissions();
}catch(e){ }catch(e){
print('Error initializing : $e'); print('Error initializing : $e');
rethrow;
} }
print('All services started...'); print('All services started...');
@ -314,7 +359,17 @@ Future<void> _initCacheProvider() async {
} }
Future<void> _checkPermissions() async { Future<void> _checkPermissions() async {
int attempts = 0;
while (Get.context == null && attempts < 10) {
await Future.delayed(const Duration(milliseconds: 100));
attempts++;
}
if (Get.context != null) {
await PermissionController.checkAndRequestPermissions(); await PermissionController.checkAndRequestPermissions();
} else {
print('Context is still null, cannot check permissions');
}
//await PermissionController.checkAndRequestPermissions(); // main._checkPermissions
} }
Future<void> _initApiService() async { Future<void> _initApiService() async {
@ -494,7 +549,9 @@ Future<void> startBackgroundTracking() async {
try { try {
// 位置情報の権限が許可されているかを確認 // 位置情報の権限が許可されているかを確認
await PermissionController.checkAndRequestPermissions(); WidgetsBinding.instance.addPostFrameCallback((_) {
PermissionController.checkAndRequestPermissions();
});
} catch (e) { } catch (e) {
print('Error starting background tracking: $e'); print('Error starting background tracking: $e');
} }
@ -589,10 +646,29 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
Future <void> _initializeControllers() async { Future <void> _initializeControllers() async {
while (!Get.isRegistered<LocationController>() ) {
print("LocationController is not up... ");
await Future.delayed(const Duration(milliseconds: 100));
}
while (!Get.isRegistered<IndexController>() ) {
print("IndexController is not up...");
await Future.delayed(const Duration(milliseconds: 100));
}
while (!Get.isRegistered<DestinationController>() ) {
print("DestinationController is not up...");
await Future.delayed(const Duration(milliseconds: 100));
}
/*
while (!Get.isRegistered<PermissionController>() ) {
print("PermissionController is not up... ");
await Future.delayed(const Duration(milliseconds: 100));
}
*/
while (!Get.isRegistered<LocationController>() || while (!Get.isRegistered<LocationController>() ||
!Get.isRegistered<IndexController>() || !Get.isRegistered<IndexController>() ||
!Get.isRegistered<DestinationController>() || !Get.isRegistered<DestinationController>() ) {
!Get.isRegistered<PermissionController>()) { print("LocationController status = Get.isRegistered<LocationController>() ");
await Future.delayed(const Duration(milliseconds: 100)); await Future.delayed(const Duration(milliseconds: 100));
} }
@ -600,7 +676,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
_locationController = Get.find<LocationController>(); _locationController = Get.find<LocationController>();
_indexController = Get.find<IndexController>(); _indexController = Get.find<IndexController>();
_destinationController = Get.find<DestinationController>(); _destinationController = Get.find<DestinationController>();
_permissionController = Get.find<PermissionController>(); //_permissionController = Get.find<PermissionController>();
_isControllerInitialized = true; _isControllerInitialized = true;
} }
@ -687,14 +763,11 @@ 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 {
@ -808,15 +881,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override @override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async { Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
try { try {
if (!Get.isRegistered<IndexController>()) { await _initializeControllers();
_indexController = Get.find<IndexController>();
}
if (!Get.isRegistered<LocationController>()) {
_locationController = Get.find<LocationController>();
}
if (!Get.isRegistered<DestinationController>()) {
_destinationController = Get.find<DestinationController>();
}
switch (state) { switch (state) {
case AppLifecycleState.resumed: case AppLifecycleState.resumed:
@ -858,6 +923,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
} }
Future<void> _onResumed() async { Future<void> _onResumed() async {
debugPrint("==(Status Changed)==> RESUMED"); debugPrint("==(Status Changed)==> RESUMED");
try { try {
if (!_isControllerInitialized) { if (!_isControllerInitialized) {
@ -888,43 +954,76 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
} }
Future<void> _onInactive() async { Future<void> _onInactive() async {
try {
debugPrint("==(Status Changed)==> INACTIVE"); debugPrint("==(Status Changed)==> INACTIVE");
if (!_isControllerInitialized) {
await _initializeControllers();
}
if (Platform.isIOS && !_destinationController.isRunningBackgroundGPS) { if (Platform.isIOS && !_destinationController.isRunningBackgroundGPS) {
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。"); debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
_locationController.stopPositionStream(); _locationController.stopPositionStream();
_destinationController.isRunningBackgroundGPS = true; _destinationController.isRunningBackgroundGPS = true;
await startBackgroundTracking(); await startBackgroundTracking();
} else if (Platform.isAndroid && !_destinationController.isRunningBackgroundGPS) { } else if (Platform.isAndroid &&
!_destinationController.isRunningBackgroundGPS) {
// Android特有の処理があれば追加 // Android特有の処理があれば追加
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。"); debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
} else { } else {
debugPrint("==(Status Changed)==> INACTIVE 不明状態"); debugPrint("==(Status Changed)==> INACTIVE 不明状態");
} }
await saveGameState(); await saveGameState();
} catch (e) {
print('Error in _onInactive: $e');
}
} }
Future<void> _onPaused() async { Future<void> _onPaused() async {
try {
debugPrint(" ==(Status Changed)==> PAUSED. バックグラウンド処理。"); debugPrint(" ==(Status Changed)==> PAUSED. バックグラウンド処理。");
if (Platform.isAndroid && !_destinationController.isRunningBackgroundGPS) { if (!_isControllerInitialized) {
debugPrint(" ==(Status Changed)==> PAUSED. Android バックグラウンド処理。"); await _initializeControllers();
}
if (Platform.isAndroid &&
!_destinationController.isRunningBackgroundGPS) {
debugPrint(
" ==(Status Changed)==> PAUSED. Android バックグラウンド処理。");
_locationController.stopPositionStream(); _locationController.stopPositionStream();
const platform = MethodChannel('location'); const platform = MethodChannel('location');
await platform.invokeMethod('startLocationService'); await platform.invokeMethod('startLocationService');
_destinationController.isRunningBackgroundGPS = true; _destinationController.isRunningBackgroundGPS = true;
} }
await saveGameState(); await saveGameState();
} catch (e) {
print('Error in _onPaused: $e');
}
} }
Future<void> _onDetached() async { Future<void> _onDetached() async {
try {
if (!_isControllerInitialized) {
await _initializeControllers();
}
debugPrint(" ==(Status Changed)==> DETACHED アプリは終了します。"); debugPrint(" ==(Status Changed)==> DETACHED アプリは終了します。");
await saveGameState(); await saveGameState();
// アプリ終了時の追加処理 // アプリ終了時の追加処理
} catch (e) {
print('Error in _onDetached: $e');
}
} }
Future<void> _onHidden() async { Future<void> _onHidden() async {
try {
if (!_isControllerInitialized) {
await _initializeControllers();
}
debugPrint(" ==(Status Changed)==> Hidden アプリが隠れた"); debugPrint(" ==(Status Changed)==> Hidden アプリが隠れた");
await saveGameState(); await saveGameState();
} catch (e) {
print('Error in _onHidden: $e');
}
} }
@override @override
@ -952,7 +1051,4 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
enableLog: true, enableLog: true,
); );
} }
} }

View File

@ -179,7 +179,7 @@ class _AuthPageState extends ConsumerState<AuthPage> {
Widget buildControlls() { Widget buildControlls() {
if (_isLoginProgress) { if (_isLoginProgress) {
return const Center( return const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(), // Auth_page
); );
} }

View File

@ -418,7 +418,7 @@ class CameraPage extends StatelessWidget {
}, },
child: Text("finish_goal".tr)) child: Text("finish_goal".tr))
: const Center( : const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(), // Camera page
) )
: Container()) : Container())
], ],

View File

@ -44,9 +44,6 @@ 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();
@ -1022,16 +1019,15 @@ class DestinationController extends GetxController {
// 2024-8-24 ... 佐伯呼び出しが必要なのか? // 2024-8-24 ... 佐伯呼び出しが必要なのか?
// //
Future<void> checkForCheckin() async { Future<void> checkForCheckin() async {
if (!game_started) { //print("--- Start of checkForCheckin function ---");
game_started = true;
dbService.updateDatabase(); dbService.updateDatabase();
} await Future.delayed(const Duration(milliseconds: 3000));
game_started = true;
try { try {
// ここで、エラー // ここで、エラー
if( indexController.locations.isNotEmpty ) { if( indexController.locations.isNotEmpty ) {
for (var fs in indexController.locations[0].features) { indexController.locations[0].features.forEach((fs) async {
//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]);
@ -1049,11 +1045,10 @@ 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) {
pushGPStoServer(); unawaited(pushGPStoServer());
//unawaited(pushGPStoServer());
} }
} }
//print("--- 123 ---- $skip_gps----"); //print("--- 123 ---- $skip_gps----");
@ -1076,19 +1071,14 @@ 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;
await ExternalService().pushGPS(); 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();
} }
} }
@ -1136,7 +1126,7 @@ class DestinationController extends GetxController {
} }
Future<String?> _saveImageToGallery(String imagePath) async { Future<String?> _saveImageToGallery(String imagePath) async {
final status = await PermissionController.checkStoragePermission(); final status = await PermissionController.checkStoragePermission(); // destinationController._saveImageToGallery
if(!status){ if(!status){
await PermissionController.requestStoragePermission(); await PermissionController.requestStoragePermission();
} }
@ -1342,13 +1332,6 @@ class DestinationController extends GetxController {
@override @override
void onInit() async { void onInit() async {
super.onInit(); super.onInit();
startCheckForCheckinTimer();
/*
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
*/
startGPSCheckTimer(); startGPSCheckTimer();
@ -1426,18 +1409,6 @@ 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), () {
@ -1450,9 +1421,8 @@ class DestinationController extends GetxController {
// //
@override @override
void onClose() { void onClose() {
stopCheckForCheckinTimer(); gpsCheckTimer?.cancel();
//gpsCheckTimer?.cancel(); locationController.stopPositionStream();
//locationController.stopPositionStream();
super.onClose(); super.onClose();
} }
@ -1669,6 +1639,7 @@ class DestinationController extends GetxController {
final token = indexController.userToken; final token = indexController.userToken;
if (token != null && token.isNotEmpty) { if (token != null && token.isNotEmpty) {
await indexController.loadUserDetailsForToken(token); await indexController.loadUserDetailsForToken(token);
await Future.delayed(Duration(milliseconds: 500)); // 短い遅延を追加
fixMapBound(token); fixMapBound(token);
}else { }else {
Get.toNamed(AppPages.LOGIN)!.then((value) { Get.toNamed(AppPages.LOGIN)!.then((value) {
@ -1689,6 +1660,9 @@ class DestinationController extends GetxController {
}); });
} }
// MapControllerの準備が整うまで待機
await indexController.waitForMapControllerReady();
// 地図のイベントリスナーを設定 // 地図のイベントリスナーを設定
indexController.mapController.mapEventStream.listen((MapEvent mapEvent) { indexController.mapController.mapEventStream.listen((MapEvent mapEvent) {
if (mapEvent is MapEventMoveEnd) { if (mapEvent is MapEventMoveEnd) {
@ -1701,11 +1675,12 @@ class DestinationController extends GetxController {
// 地図の境界を修正する関数です。 // 地図の境界を修正する関数です。
// //
void fixMapBound(String token) { Future<void> fixMapBound(String token) async {
await indexController.waitForMapControllerReady();
//String _token = indexController.currentUser[0]["token"]; //String _token = indexController.currentUser[0]["token"];
indexController.switchPage(AppPages.INDEX); indexController.switchPage(AppPages.INDEX);
if (isMapControllerReady) { if (indexController.isMapControllerReady.value) {
LocationService.getLocationsExt(token).then((value) { LocationService.getLocationsExt(token).then((value) {
if (value != null) { if (value != null) {
//print("--- loc ext is - $value ----"); //print("--- loc ext is - $value ----");
@ -1767,11 +1742,18 @@ class DestinationController extends GetxController {
// 地図を現在位置に中央揃えする関数です。 // 地図を現在位置に中央揃えする関数です。
// //
void centerMapToCurrentLocation() { void centerMapToCurrentLocation() {
final indexController = Get.find<IndexController>();
if (indexController.isMapControllerReady.value && indexController.isMapControllerReady.value) {
// ... 現在位置へのセンタリングのロジック ...
indexController.mapController.move(LatLng(currentLat, currentLon), 17.0);
} else {
debugPrint('Map controller is not ready for centering');
}
//print("center is ${currentLat}, ${currentLon}"); //print("center is ${currentLat}, ${currentLon}");
// Akira ... 状況によって呼ぶか呼ばないか // Akira ... 状況によって呼ぶか呼ばないか
if (currentLat != 0 || currentLon != 0) { //if (currentLat != 0 || currentLon != 0) {
indexController.mapController.move(LatLng(currentLat, currentLon), 17.0); //indexController.mapController.move(LatLng(currentLat, currentLon), 17.0);
} //}
} }
// 接続状態が変更されたときに呼び出される関数です。 // 接続状態が変更されたときに呼び出される関数です。
@ -1944,7 +1926,7 @@ class DestinationController extends GetxController {
barrierDismissible: false, barrierDismissible: false,
builder: (BuildContext context) { builder: (BuildContext context) {
return const Center( return const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(), // Destination COntroller
); );
}); });
} }

View File

@ -8,11 +8,9 @@ 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 'package:gifunavi/pages/index/index_controller.dart'; import '../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;
@ -31,10 +29,6 @@ class EntryController extends GetxController {
final activeEvents = <Event>[].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();
@ -47,7 +41,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', 'APIサービスの初期化に失敗しました'); Get.snackbar('Error', 'Failed to initialize API service');
} }
} }
@ -66,16 +60,14 @@ class EntryController extends GetxController {
initializeEditMode(currentEntry.value!); initializeEditMode(currentEntry.value!);
} else { } else {
// 新規作成モードの場合、最初のイベントを選択 // 新規作成モードの場合、最初のイベントを選択
if (activeEvents.isNotEmpty) { if (events.isNotEmpty) {
selectedEvent.value = activeEvents.first; selectedEvent.value = activeEvents.first;
selectedDate.value = activeEvents.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;
} }
@ -106,64 +98,12 @@ class EntryController extends GetxController {
} }
} }
Future<void> fetchTeamMembers(int teamId) async { void updateTeam(Team? value) {
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;

View File

@ -7,7 +7,6 @@ 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;
@ -32,17 +31,6 @@ 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(
@ -53,22 +41,9 @@ class EntryDetailPage extends GetView<EntryController> {
label: 'イベント', label: 'イベント',
items: controller.activeEvents, 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.activeEvents.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,
), ),
@ -77,21 +52,9 @@ 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,
), ),
@ -115,9 +78,7 @@ 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( ? DateFormat('yyyy-MM-dd').format(tz.TZDateTime.from(controller.selectedDate.value!, tz.getLocation('Asia/Tokyo')))
controller.selectedDate.value!,
tz.getLocation('Asia/Tokyo')))
: '日付を選択してください', : '日付を選択してください',
), ),
onTap: () async { onTap: () async {
@ -125,37 +86,25 @@ 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 final tz.TZDateTime now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo'));
.getLocation('Asia/Tokyo')); final tz.TZDateTime eventStart = tz.TZDateTime.from(controller.selectedEvent.value!.startDatetime, tz.getLocation('Asia/Tokyo'));
final tz.TZDateTime eventStart = tz.TZDateTime.from( final tz.TZDateTime eventEnd = tz.TZDateTime.from(controller.selectedEvent.value!.endDatetime, tz.getLocation('Asia/Tokyo'));
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 final tz.TZDateTime initialDate = controller.selectedDate.value != null
.value != null ? tz.TZDateTime.from(controller.selectedDate.value!, tz.getLocation('Asia/Tokyo'))
? 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) final tz.TZDateTime firstDate = now.isAfter(eventStart) ? now : eventStart;
? now
: eventStart;
final DateTime? picked = await showDatePicker( final DateTime? picked = await showDatePicker(
context: context, context: context,
initialDate: initialDate.isAfter(firstDate) initialDate: initialDate.isAfter(firstDate) ? initialDate : firstDate,
? initialDate
: firstDate,
firstDate: firstDate, firstDate: firstDate,
lastDate: eventEnd, lastDate: eventEnd,
); );
if (picked != null) { if (picked != null) {
controller.updateDate(tz.TZDateTime.from( controller.updateDate(tz.TZDateTime.from(picked, tz.getLocation('Asia/Tokyo')));
picked, tz.getLocation('Asia/Tokyo')));
} }
}, },
), ),
@ -202,12 +151,6 @@ class EntryDetailPage extends GetView<EntryController> {
), ),
), ),
); );
} catch (e) {
print('Error in EntryDetailPage: $e');
return const Center(
child: Text('エラーが発生しました。もう一度お試しください。'),
);
}
}), }),
); );
} }
@ -223,42 +166,15 @@ 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.isNotEmpty ? items.map((item) => DropdownMenuItem<int>( items: 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() : null, )).toList(),
onChanged: (value) { onChanged: onChanged,
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();

View File

@ -7,7 +7,7 @@ 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'; import '../../model/entry.dart';
class EntryListPage extends GetView<EntryController> { class EntryListPage extends GetView<EntryController> {
const EntryListPage({super.key}); const EntryListPage({super.key});
@ -38,14 +38,8 @@ class EntryListPage extends GetView<EntryController> {
itemCount: sortedEntries.length, itemCount: sortedEntries.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final entry = sortedEntries[index]; final entry = sortedEntries[index];
//final now = DateTime.now(); final now = DateTime.now();
//print("now=$now"); final isEntryInFuture = _compareDatesOnly(entry.date!, now) >= 0;
//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); //final isEntryInFuture = entry.date!.isAfter(now) || entry.date!.isAtSameMomentAs(now);
@ -149,7 +143,7 @@ class EntryListPage_old extends GetView<EntryController> {
), ),
body: Obx((){ body: Obx((){
if (controller.isLoading.value) { if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator()); // EntryList
} }
// エントリーを日付昇順にソート // エントリーを日付昇順にソート

View File

@ -6,6 +6,8 @@ import 'package:gifunavi/pages/gps/gps_controller.dart';
import 'package:gifunavi/pages/history/history_controller.dart'; import 'package:gifunavi/pages/history/history_controller.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:gifunavi/pages/gps/gps_controller.dart';
import 'package:gifunavi/pages/history/history_controller.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
class EventResultPage extends StatefulWidget { class EventResultPage extends StatefulWidget {

View File

@ -27,7 +27,7 @@ class _HistoryPageState extends State<HistoryPage> {
future: db.getDestinations(), future: db.getDestinations(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator()); //History page
} else if (snapshot.hasError) { } else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}')); return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) { } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
@ -167,7 +167,7 @@ class CustomWidget extends StatelessWidget {
print('Error loading image path: ${snapshot.error}'); print('Error loading image path: ${snapshot.error}');
return const Icon(Icons.error); return const Icon(Icons.error);
} else { } else {
return const CircularProgressIndicator(); return const CircularProgressIndicator(); // History page 2
} }
}, },
); );

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:get/get.dart';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -8,7 +9,6 @@ import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:geojson_vi/geojson_vi.dart'; import 'package:geojson_vi/geojson_vi.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:gifunavi/model/destination.dart'; import 'package:gifunavi/model/destination.dart';
import 'package:gifunavi/model/entry.dart'; import 'package:gifunavi/model/entry.dart';
@ -34,6 +34,8 @@ import 'package:timezone/data/latest.dart' as tz;
import '../permission/permission.dart'; import '../permission/permission.dart';
//enum MapState { initializing, ready, error }
class IndexController extends GetxController with WidgetsBindingObserver { class IndexController extends GetxController with WidgetsBindingObserver {
List<GeoJSONFeatureCollection> locations = <GeoJSONFeatureCollection>[].obs; List<GeoJSONFeatureCollection> locations = <GeoJSONFeatureCollection>[].obs;
List<GeoJSONFeature> currentFeature = <GeoJSONFeature>[].obs; List<GeoJSONFeature> currentFeature = <GeoJSONFeature>[].obs;
@ -60,10 +62,16 @@ class IndexController extends GetxController with WidgetsBindingObserver {
var isCustomAreaSelected = false.obs; var isCustomAreaSelected = false.obs;
RxBool isMapControllerReady = RxBool(false); // MapControllerの初期化状態を管理するフラグ
//final mapControllerReadyStream = Stream<bool>.value(false); // MapControllerの初期化状態を通知するためのストリーム //final mapControllerReadyStream = Stream<bool>.value(false); // MapControllerの初期化状態を通知するためのストリーム
MapController mapController = MapController(); // 複数の状態変数_isLoading, _isMapInitialized, indexController.isMapControllerReadyを一元管理します。
//final Rx<MapState> mapState = MapState.initializing.obs;
final Completer<void> mapControllerCompleter = Completer<void>();
late MapController mapController; // = Rx<MapController?>(null);
final RxBool isMapControllerReady = false.obs; // MapControllerの初期化状態を管理するフラグ
//MapController mapController = MapController();
MapController rogMapController = MapController(); MapController rogMapController = MapController();
LogManager logManager = LogManager(); LogManager logManager = LogManager();
@ -96,7 +104,43 @@ class IndexController extends GetxController with WidgetsBindingObserver {
final selectedEventName = 'add_location'.tr.obs; final selectedEventName = 'add_location'.tr.obs;
RxBool isLoadingLocations = true.obs; RxBool isLoadingLocations = false.obs;
// ユーザーの参加状況を追跡する
final RxBool isUserParticipating = false.obs;
// ユーザーのゴール到達状況を追跡する
final RxBool hasUserReachedGoal = false.obs;
void initMapController() {
mapController = MapController();
// MapEventMoveEndイベントを使用して初期化完了を検出
mapController.mapEventStream.listen((event) {
if (event is MapEventMoveEnd && !isMapControllerReady.value) {
// MapEventMoveEndイベントが発生したら、MapControllerの準備完了とみなす
if (!isMapControllerReady.value) {
print('MapController is ready');
isMapControllerReady.value = true;
if (!mapControllerCompleter.isCompleted) {
mapControllerCompleter.complete();
}
}
}
});
}
// ユーザーの参加状況を更新するメソッド
void updateUserParticipationStatus(bool isParticipating) {
isUserParticipating.value = isParticipating;
update(); // GetX の update() メソッドを呼び出してUIを更新
}
// ユーザーのゴール到達状況を更新するメソッド
void updateUserGoalStatus(bool hasReachedGoal) {
hasUserReachedGoal.value = hasReachedGoal;
update(); // GetX の update() メソッドを呼び出してUIを更新
}
void setSelectedEventName(String eventName) { void setSelectedEventName(String eventName) {
selectedEventName.value = eventName; selectedEventName.value = eventName;
@ -240,15 +284,16 @@ class IndexController extends GetxController with WidgetsBindingObserver {
@override @override
void onInit() { void onInit() {
super.onInit();
try { try {
super.onInit();
//mapController = MapController();
//ever(isMapControllerReady, (_) => _onMapControllerReady());
initMapController();
initConnectivity(); initConnectivity();
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus); _connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
//WidgetsBinding.instance.addPostFrameCallback((_) async {
// await PermissionController.checkAndRequestPermissions();
//});
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
_startLocationService(); // アプリ起動時にLocationServiceを開始する _startLocationService(); // アプリ起動時にLocationServiceを開始する
@ -259,7 +304,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
tz.initializeTimeZones(); tz.initializeTimeZones();
//teamController = Get.find<TeamController>(); //teamController = Get.find<TeamController>();
loadLocations(); //loadLocations();
}catch(e,stacktrace){ }catch(e,stacktrace){
print('Error in IndexController.onInit: $e'); print('Error in IndexController.onInit: $e');
@ -270,18 +315,33 @@ class IndexController extends GetxController with WidgetsBindingObserver {
} }
} }
void _onMapControllerReady() {
if (isMapControllerReady.value) {
loadLocations();
}
}
Future<void> loadLocations() async { Future<void> loadLocations() async {
if (isLoadingLocations.value) return;
isLoadingLocations.value = true; isLoadingLocations.value = true;
try { try {
await waitForMapControllerReady(); debugPrint('IndexController: Starting to load locations');
await waitForMapControllerReady(); // loadLocations
debugPrint('IndexController: Permission granted, loading locations');
String eventCode = currentUser.isNotEmpty ? currentUser[0]["user"]["event_code"] ?? "" : ""; String eventCode = currentUser.isNotEmpty ? currentUser[0]["user"]["event_code"] ?? "" : "";
await loadLocationsBound(eventCode); await loadLocationsBound(eventCode);
debugPrint('IndexController: Locations loaded successfully');
} catch (e) { } catch (e) {
print('Error loading locations: $e'); print('Error loading locations: $e');
// エラーハンドリングを追加(例:スナックバーでユーザーに通知) // エラーハンドリングを追加(例:スナックバーでユーザーに通知)
Get.snackbar('エラー', '位置情報の取得に失敗しました: ${e.toString()}');
} finally { } finally {
isLoadingLocations.value = false; isLoadingLocations.value = false;
} }
} }
void _updateConnectionStatus(List<ConnectivityResult> results) { void _updateConnectionStatus(List<ConnectivityResult> results) {
@ -497,17 +557,20 @@ class IndexController extends GetxController with WidgetsBindingObserver {
// //
Future<void> login(String email, String password) async { Future<void> login(String email, String password) async {
try { try {
isLoading.value = true;
final value = await AuthService.login(email, password); final value = await AuthService.login(email, password);
if (value.isNotEmpty && value['token'] != null) { if (value.isNotEmpty && value['token'] != null) {
await changeUser(value); await changeUser(value);
await _initializeUserData(); await _initializeUserData();
Get.offAllNamed(AppPages.INDEX); Get.offAllNamed(AppPages.INDEX);
} else { } else {
Get.snackbar('Login Failed', 'Invalid credentials'); Get.snackbar('ログイン失敗', 'メールアドレスまたはパスワードが間違っています');
} }
} catch (e) { } catch (e) {
print('Login error: $e'); print('Login error: $e');
Get.snackbar('Login Failed', 'An error occurred. Please try again.'); Get.snackbar('ログイン失敗', 'エラーが発生しました。もう一度お試しください。');
} finally {
isLoading.value = false;
} }
} }
@ -897,6 +960,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
return; return;
} }
try {
// MapControllerの初期化が完了するまで待機 // MapControllerの初期化が完了するまで待機
await waitForMapControllerReady(); await waitForMapControllerReady();
@ -979,7 +1043,8 @@ class IndexController extends GetxController with WidgetsBindingObserver {
"please zoom in", "please zoom in",
backgroundColor: Colors.yellow, backgroundColor: Colors.yellow,
colorText: Colors.white, colorText: Colors.white,
icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue), icon: const Icon(Icons.assistant_photo_outlined, size: 40.0,
color: Colors.blue),
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3), duration: const Duration(seconds: 3),
); );
@ -1016,7 +1081,10 @@ class IndexController extends GetxController with WidgetsBindingObserver {
// エラーが発生した場合のリトライ処理や適切なエラーメッセージの表示を行う // エラーが発生した場合のリトライ処理や適切なエラーメッセージの表示を行う
// 例えば、一定時間後に再度loadLocationsBound()を呼び出すなど // 例えば、一定時間後に再度loadLocationsBound()を呼び出すなど
} }
} catch (e) {
print('Error in loadLocationsBound: $e');
//Get.snackbar('エラー', '位置情報の読み込みに失敗しました。もう一度お試しください。');
}
} }
// バウンドが有効かどうかを確認するヘルパーメソッド // バウンドが有効かどうかを確認するヘルパーメソッド
@ -1029,20 +1097,44 @@ class IndexController extends GetxController with WidgetsBindingObserver {
bounds.southWest.latitude < bounds.northEast.latitude; bounds.southWest.latitude < bounds.northEast.latitude;
} }
static const int maxRetries = 5;
int retryCount = 0;
//===Akira 追加:2024-4-6 #2800 //===Akira 追加:2024-4-6 #2800
// 要検討MapControllerの初期化が完了するまで待機していますが、タイムアウトを設定することを検討してください。 // 要検討MapControllerの初期化が完了するまで待機していますが、タイムアウトを設定することを検討してください。
// 初期化に時間がかかりすぎる場合、ユーザーにわかりやすいメッセージを表示するようにしてください。 // 初期化に時間がかかりすぎる場合、ユーザーにわかりやすいメッセージを表示するようにしてください。
// //
Future<void> waitForMapControllerReady() async { Future<void> waitForMapControllerReady() async {
if (isMapControllerReady.value) return;
//await isMapControllerReady.firstWhere((value) => value == true);
const timeout = Duration(seconds: 30);
try {
await Future.any([
mapControllerCompleter.future,
Future.delayed(timeout).then((_) => throw TimeoutException('MapController initialization timed out')),
]);
isMapControllerReady.value = true;
} catch (e) {
print('Warning waiting for MapController: $e');
// タイムアウト後もマップが機能している場合は、強制的に準備完了とマーク
if (!isMapControllerReady.value) { if (!isMapControllerReady.value) {
await Future.doWhile(() async { isMapControllerReady.value = true;
await Future.delayed(const Duration(milliseconds: 100)); if (!mapControllerCompleter.isCompleted) {
return !isMapControllerReady.value; mapControllerCompleter.complete();
}); }
}
} }
} }
//===Akira 追加:2024-4-6 #2800 //===Akira 追加:2024-4-6 #2800
void retryMapInitialization() {
// マップの再初期化ロジック
initMapController();
// 他の必要な再初期化処理
}
void setBound(LatLngBounds bounds) { void setBound(LatLngBounds bounds) {
currentBound.clear(); currentBound.clear();
currentBound.add(bounds); currentBound.add(bounds);

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:gifunavi/pages/destination/destination_controller.dart'; import 'package:gifunavi/pages/destination/destination_controller.dart';
import 'package:gifunavi/pages/drawer/drawer_page.dart'; import 'package:gifunavi/pages/drawer/drawer_page.dart';
@ -34,22 +35,45 @@ class IndexPage extends StatefulWidget {
} }
class _IndexPageState extends State<IndexPage> { class _IndexPageState extends State<IndexPage> {
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController = Get.find<DestinationController>();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async { _checkPermissionAndInitialize();
await _ensureControllersAreInitialized();
//await PermissionController.checkAndRequestPermissions();
});
} }
Future<void> _ensureControllersAreInitialized() async { Future<void> _checkPermissionAndInitialize() async {
while (!Get.isRegistered<IndexController>() || // 位置情報の許可が得られた場合の処理
!Get.isRegistered<DestinationController>() || await _initializeMap();
!Get.isRegistered<LocationController>()) {
await Future.delayed(const Duration(milliseconds: 100));
} }
Future<void> _initializeMap() async {
await indexController.loadLocations();
setState(() {}); // 状態を更新してUIを再構築
}
void _showLocationServiceDisabledOrDeniedError() {
Get.snackbar(
'エラー',
'位置情報サービスが無効か、許可されていません。設定を確認してください。',
duration: const Duration(seconds: 5),
snackPosition: SnackPosition.BOTTOM,
mainButton: TextButton(
child: const Text('設定'),
onPressed: () => Geolocator.openLocationSettings(),
),
);
}
void _showPermissionDeniedError() {
Get.snackbar(
'エラー',
'位置情報の許可が必要です。設定から許可してください。',
duration: const Duration(seconds: 5),
snackPosition: SnackPosition.BOTTOM,
);
} }
void checkEventAndNavigate() async { void checkEventAndNavigate() async {
@ -112,11 +136,11 @@ class _IndexPageState extends State<IndexPage> {
void _showEventSelectionWarning() { void _showEventSelectionWarning() {
Get.dialog( Get.dialog(
AlertDialog( AlertDialog(
title: Text('警告'), title: const Text('警告'),
content: Text('イベントを選択してください。'), content: const Text('イベントを選択してください。'),
actions: [ actions: [
TextButton( TextButton(
child: Text('OK'), child: const Text('OK'),
onPressed: () => Get.back(), onPressed: () => Get.back(),
), ),
], ],
@ -124,24 +148,27 @@ class _IndexPageState extends State<IndexPage> {
); );
} }
// class IndexPage extends GetView<IndexController> { // class IndexPage extends GetView<IndexController> {
// IndexPage({Key? key}) : super(key: key); // IndexPage({Key? key}) : super(key: key);
// IndexControllerとDestinationControllerのインスタンスを取得しています。 // IndexControllerとDestinationControllerのインスタンスを取得しています。
// //
final LocationController locationController = Get.find<LocationController>(); final LocationController locationController = Get.find<LocationController>();
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController =
Get.find<DestinationController>();
// buildメソッドは、ウィジェットのUIを構築するメソッドです。 // buildメソッドは、ウィジェットのUIを構築するメソッドです。
// ここでは、WillPopScopeウィジェットを使用して、端末の戻るボタンが押された際の動作を制御しています。 // ここでは、WillPopScopeウィジェットを使用して、端末の戻るボタンが押された際の動作を制御しています。
// //
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
/*
return PopScope( return PopScope(
canPop: false, canPop: false,
child: Scaffold( child: Scaffold(
*/
return Scaffold(
// //
// Scaffoldウィジェットを使用して、アプリのメインページのレイアウトを構築しています。 // Scaffoldウィジェットを使用して、アプリのメインページのレイアウトを構築しています。
// //
@ -150,24 +177,8 @@ class _IndexPageState extends State<IndexPage> {
title: Obx(() => Text(indexController.selectedEventName.value)), title: Obx(() => Text(indexController.selectedEventName.value)),
//title: Text("add_location".tr), //title: Text("add_location".tr),
actions: [ actions: [
// IconButton(
// onPressed: () {
// DatabaseService ds = DatabaseService();
// ds.updateDatabase();
// },
// icon: const Icon(Icons.ten_k_sharp)),
//
// AppBarには、タイトルとアクションアイコンが含まれています。
// アクションアイコンには、GPSデータの表示、履歴の表示、マップの更新、検索などの機能が含まれています。
//
IconButton( IconButton(
onPressed: () async { onPressed: () async {
// GpsDatabaseHelper db = GpsDatabaseHelper.instance;
// List<GpsData> data = await db.getGPSData(
// indexController.currentUser[0]["user"]['team_name'],
// indexController.currentUser[0]["user"]["event_code"]);
// print("GPS data is ${data.length}");
Get.toNamed(AppPages.GPS); Get.toNamed(AppPages.GPS);
}, },
icon: const Icon(Icons.telegram)), icon: const Icon(Icons.telegram)),
@ -201,102 +212,22 @@ class _IndexPageState extends State<IndexPage> {
), ),
), ),
//CatWidget(indexController: indexController,), //CatWidget(indexController: indexController,),
//
// デバッグ時のみリロードボタンの横にGPS信号レベルの設定ボタンを設置し、
// タップすることでGPS信号の強弱をシミュレーションできるようにする
// Akira 2024-4-5
//
/*
Obx(() {
if (locationController.isSimulationMode) {
return DropdownButton<String>(
value: locationController.getSimulatedSignalStrength(),
onChanged: (value) {
//debugPrint("DropDown changed!");
locationController.setSimulatedSignalStrength(value!);
},
items: ['low', 'medium', 'high', 'real']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
} else {
return Container();
}
}),
*/
], ],
), ),
// bottomNavigationBar: BottomAppBar(
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: <Widget>[
// Obx(
// () => destinationController.isInRog.value == true
// ? IconButton(
// onPressed: () {},
// icon: const Icon(
// Icons.run_circle,
// size: 44,
// color: Colors.green,
// ))
// : IconButton(
// onPressed: () {},
// icon: const Icon(
// Icons.run_circle,
// size: 44,
// color: Colors.black12,
// )),
// ),
// Padding(
// padding:
// const EdgeInsets.only(right: 10.0, top: 4.0, bottom: 4.0),
// child: InkWell(
// child: Obx(() => destinationController
// .isGpsSelected.value ==
// true
// ? Padding(
// padding: const EdgeInsets.only(
// right: 10.0, top: 4.0, bottom: 4.0),
// child: InkWell(
// child: const Image(
// image:
// AssetImage('assets/images/route3_off.png'),
// width: 35,
// height: 35,
// ),
// onTap: () {
// //indexController.switchPage(AppPages.TRAVEL);
// },
// ),
// )
// : Padding(
// padding: const EdgeInsets.only(
// right: 10.0, top: 4.0, bottom: 4.0),
// child: InkWell(
// child: const Image(
// image:
// AssetImage('assets/images/route2_on.png'),
// width: 35,
// height: 35,
// ),
// onTap: () {
// //indexController.switchPage(AppPages.TRAVEL);
// },
// ),
// ))),
// ),
// ],
// ),
// ),
// body: SafeArea(
child:Obx(() {
if (indexController.isLoadingLocations.value) {
return const Center(child: CircularProgressIndicator()); // Index Controller
} else {
return indexController.mode.value == 0
? const MapWidget()
: const ListWidget();
}
}),
),
// マップモードとリストモードを切り替えるためのボタンです。 // マップモードとリストモードを切り替えるためのボタンです。
//
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () { onPressed: () {
indexController.toggleMode(); indexController.toggleMode();
@ -312,22 +243,6 @@ class _IndexPageState extends State<IndexPage> {
), ),
), ),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
//
// bodyには、SafeAreaウィジェットを使用して、画面の安全な領域内にUIを構築しています。
//
body: SafeArea(
child: Column(
children: [
Expanded(
child: Obx(
() => indexController.mode.value == 0
? const MapWidget()
: const ListWidget(),
))
],
),
),
),
); );
} }
} }

View File

@ -182,7 +182,7 @@ class _LoginPageState extends State<LoginPage> {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: borderRadius:
BorderRadius.circular(40)), BorderRadius.circular(40)),
child: const CircularProgressIndicator(), child: const CircularProgressIndicator(), // Login page
) )
: Column( : Column(
children: [ children: [
@ -199,24 +199,19 @@ class _LoginPageState extends State<LoginPage> {
.tr, .tr,
backgroundColor: Colors.red, backgroundColor: Colors.red,
colorText: Colors.white, colorText: Colors.white,
icon: const Icon( icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
Icons snackPosition: SnackPosition.TOP,
.assistant_photo_outlined, duration: const Duration(seconds: 3),
size: 40.0,
color: Colors.blue),
snackPosition:
SnackPosition.TOP,
duration: const Duration(
seconds: 3),
); );
return; return;
} }
indexController.isLoading.value = try {
true; //indexController.isLoading.value = true;
indexController.login( indexController.login(emailController.text, passwordController.text);
emailController.text, } catch (e) {
passwordController.text print('Error during login: $e');
); // エラーハンドリングは IndexController 内で行われるため、ここでは何もしない
}
}, },
color: Colors.indigoAccent[400], color: Colors.indigoAccent[400],
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(

View File

@ -2,23 +2,87 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'dart:async'; import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';
class PermissionController { class PermissionController {
static bool? _locationPermissionGranted;
static bool _isRequestingPermission = false; /*
static Completer<bool>? _permissionCompleter; bool? _isRequestingPermission=false;
final Completer<PermissionController> _permissionCompleter = Completer<PermissionController>();
*/
static Future<bool> checkAndRequestPermissions() async { static Future<bool> checkAndRequestPermissions_new() async {
if (_isRequestingPermission) { try {
return _permissionCompleter!.future; if (_locationPermissionGranted != null) {
return _locationPermissionGranted!;
} }
_isRequestingPermission = true; var serviceEnabled = await Geolocator.isLocationServiceEnabled();
_permissionCompleter = Completer<bool>(); if (!serviceEnabled) {
Get.snackbar(
'エラー', '位置情報サービスが無効です。設定から有効にしてください。');
// 位置情報サービスが無効の場合、ユーザーに有効化を促す
return false;
}
debugPrint("====> Geolocator.checkPermission");
var permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
Get.snackbar('エラー', '位置情報の許可が拒否されました。');
return false;
}
}
if (permission == LocationPermission.deniedForever) {
Get.snackbar('エラー',
'位置情報の許可が永久に拒否されました。設定から許可してください。');
return false;
}
_locationPermissionGranted = true;
return true;
} catch (e) {
print('Error checking permissions: $e');
Get.snackbar('エラー', '権限の確認中にエラーが発生しました。');
return false;
}
}
static void resetPermissionCache() {
_locationPermissionGranted = null;
}
static Future<bool> _requestLocationPermission() async {
BuildContext? context;
int attempts = 0;
const maxAttempts = 10;
debugPrint("====> _requestLocationPermission");
// コンテキストが利用可能になるまで待機するロジック
while (Get.context == null && attempts < maxAttempts) {
context = Get.context;
if (context == null) {
await Future.delayed(const Duration(milliseconds: 500));
attempts++;
}
}
if (Get.context == null) {
print('Context is still null after waiting, cannot proceed with permission check');
return false;
}
try { try {
bool hasPermissions = await _checkLocationPermissions(); bool hasPermissions = await _checkLocationPermissions();
@ -36,18 +100,16 @@ class PermissionController {
SystemNavigator.pop(); SystemNavigator.pop();
} }
} }
return hasPermissions;
_isRequestingPermission = false;
_permissionCompleter!.complete(hasPermissions);
} catch (e) { } catch (e) {
print('Error in permission request: $e'); print('Error in permission request: $e');
_isRequestingPermission = false; return false;
_permissionCompleter!.complete(false);
} }
return _permissionCompleter!.future;
} }
static Future<bool> _checkLocationPermissions() async { static Future<bool> _checkLocationPermissions() async {
final locationPermission = await Permission.location.status; final locationPermission = await Permission.location.status;
final whenInUsePermission = await Permission.locationWhenInUse.status; final whenInUsePermission = await Permission.locationWhenInUse.status;
@ -118,20 +180,30 @@ class PermissionController {
(whenInUsePermission == PermissionStatus.granted || alwaysPermission == PermissionStatus.granted); (whenInUsePermission == PermissionStatus.granted || alwaysPermission == PermissionStatus.granted);
} }
static Future<bool> checkAndRequestPermissions_old() async { static Future<bool> checkAndRequestPermissions() async {
/*
if (_isRequestingPermission) { if (_isRequestingPermission) {
return _permissionCompleter!.future; return _permissionCompleter!.future;
} }
_isRequestingPermission = true; _isRequestingPermission = true;
_permissionCompleter = Completer<bool>(); _permissionCompleter = Completer<bool>();
*/
try { try {
if (_locationPermissionGranted != null) {
return _locationPermissionGranted!;
}
bool hasPermissions = await _checkLocationPermissions(); bool hasPermissions = await _checkLocationPermissions();
if (!hasPermissions) { if (!hasPermissions) {
bool userAgreed = await showLocationDisclosure(); bool userAgreed = await showLocationDisclosure();
if (userAgreed) { if (userAgreed) {
hasPermissions = await _requestAllLocationPermissions(); hasPermissions = await _requestAllLocationPermissions();
_locationPermissionGranted = true;
debugPrint("Finish checkAndRequestPermissions...");
return true;
} else { } else {
print('User did not agree to location usage'); print('User did not agree to location usage');
hasPermissions = false; hasPermissions = false;
@ -140,15 +212,18 @@ class PermissionController {
} }
} }
_isRequestingPermission = false; //_isRequestingPermission = false;
_permissionCompleter!.complete(hasPermissions); //_permissionCompleter!.complete(hasPermissions);
} catch( e ) { } catch( e ) {
print('Error in permission request: $e'); print('Error in permission request: $e');
_isRequestingPermission = false; //_isRequestingPermission = false;
_permissionCompleter!.complete(false); //_permissionCompleter!.complete(false);
} }
debugPrint("Finish checkAndRequestPermissions..."); debugPrint("Finish checkAndRequestPermissions...");
return _permissionCompleter!.future; //return _permissionCompleter!.future;
return false;
} }
static Future<void> requestAllLocationPermissions() async { static Future<void> requestAllLocationPermissions() async {
@ -167,25 +242,14 @@ 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;
} }
if (Get.isDialogOpen ?? false) { //if (Get.isDialogOpen ?? false) {
print('A dialog is already open'); // print('A dialog is already open');
return false; // return false;
} //}
try { try {
final result = await Get.dialog<bool>( final result = await Get.dialog<bool>(
@ -225,41 +289,6 @@ 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(

View File

@ -9,6 +9,12 @@ class SettingsController extends GetxController {
var autoReturnDisabled = false.obs; var autoReturnDisabled = false.obs;
final MapResetController mapResetController = Get.put(MapResetController()); final MapResetController mapResetController = Get.put(MapResetController());
@override
void onInit() {
super.onInit();
ever(timerDuration, (_) => resetIdleTimer());
}
void updateTimerDuration(int seconds) { void updateTimerDuration(int seconds) {
timerDuration.value = Duration(seconds: seconds); timerDuration.value = Duration(seconds: seconds);
} }

View File

@ -329,15 +329,10 @@ class TeamController extends GetxController {
} }
List<NewCategory> getFilteredCategories_old() { List<NewCategory> getFilteredCategories_old() {
if (teamMembers.isEmpty && currentUser.value != null) { //List<User> teamMembers = getCurrentTeamMembers();
// ソロの場合 return categories.where((category) {
String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子'; return isCategoryValid(category, teamMembers);
return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList(); }).toList();
} else {
bool hasElementaryOrYounger = teamMembers.any(isElementarySchoolOrYounger);
String baseCategory = hasElementaryOrYounger ? 'ファミリー' : '一般';
return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList();
}
} }
List<NewCategory> getFilteredCategories() { List<NewCategory> getFilteredCategories() {
@ -345,19 +340,13 @@ class TeamController extends GetxController {
// ソロの場合 // ソロの場合
String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子'; String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子';
return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList(); 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 { } else {
// 複数人の場合
bool hasElementaryOrYounger = teamMembers.any(isElementarySchoolOrYounger); bool hasElementaryOrYounger = teamMembers.any(isElementarySchoolOrYounger);
String baseCategory = hasElementaryOrYounger ? 'ファミリー' : '一般'; String baseCategory = hasElementaryOrYounger ? 'ファミリー' : '一般';
return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList(); 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;
@ -390,7 +379,17 @@ class TeamController extends GetxController {
// メンバーリストの最新状態を取得 // メンバーリストの最新状態を取得
await fetchTeamMembers(selectedTeam.value!.id); await fetchTeamMembers(selectedTeam.value!.id);
List<NewCategory> eligibleCategories = getFilteredCategories(); List<NewCategory> 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();
}
// 同じ時間のカテゴリを優先的に選択 // 同じ時間のカテゴリを優先的に選択
NewCategory? newCategory = eligibleCategories.firstWhereOrNull((c) => c.time == oldTime); NewCategory? newCategory = eligibleCategories.firstWhereOrNull((c) => c.time == oldTime);

View File

@ -120,19 +120,15 @@ class _TeamDetailPageState extends State<TeamDetailPage> {
} }
final filteredCategories = controller.getFilteredCategories(); final filteredCategories = controller.getFilteredCategories();
// 選択されているカテゴリが表示リストに含まれていない場合、最初の項目を選択する final categoriesToDisplay = filteredCategories.isEmpty
if (controller.selectedCategory.value == null || ? controller.categories
!filteredCategories.contains(controller.selectedCategory.value)) { : filteredCategories;
controller.updateCategory(filteredCategories.isNotEmpty
? filteredCategories.first
: null);
}
// 選択されているカテゴリが表示リストに含まれていない場合、最初の項目を選択する // 選択されているカテゴリが表示リストに含まれていない場合、最初の項目を選択する
if (controller.selectedCategory.value == null || if (controller.selectedCategory.value == null ||
!filteredCategories.contains(controller.selectedCategory.value)) { !categoriesToDisplay.contains(controller.selectedCategory.value)) {
controller.updateCategory(filteredCategories.isNotEmpty controller.updateCategory(categoriesToDisplay.isNotEmpty
? filteredCategories.first ? categoriesToDisplay.first
: null); : null);
} }
@ -151,7 +147,7 @@ class _TeamDetailPageState extends State<TeamDetailPage> {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
if (filteredCategories.isEmpty) if (categoriesToDisplay.isEmpty)
const Text('カテゴリデータを読み込めませんでした。', const Text('カテゴリデータを読み込めませんでした。',
style: TextStyle(color: Colors.red)) style: TextStyle(color: Colors.red))
else else
@ -160,7 +156,7 @@ class _TeamDetailPageState extends State<TeamDetailPage> {
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'カテゴリ'), labelText: 'カテゴリ'),
value: controller.selectedCategory.value, value: controller.selectedCategory.value,
items: filteredCategories.map((category) => items: categoriesToDisplay.map((category) =>
DropdownMenuItem( DropdownMenuItem(
value: category, value: category,
child: Text(category.categoryName), child: Text(category.categoryName),

View File

@ -36,7 +36,8 @@ 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/pages/entry/event_result_page.dart';
import 'package:gifunavi/model/entry.dart';
import '../model/entry.dart';
part 'app_routes.dart'; part 'app_routes.dart';

View File

@ -61,7 +61,7 @@ class ApiService extends GetxService{
final indexController = Get.find<IndexController>(); final indexController = Get.find<IndexController>();
if (indexController.currentUser.isNotEmpty) { if (indexController.currentUser.isNotEmpty) {
token = indexController.currentUser[0]['token'] ?? ''; token = indexController.currentUser[0]['token'] ?? '';
print("Get token = $token"); //print("Get token = $token");
}else{ }else{
token = ""; token = "";
} }
@ -77,7 +77,7 @@ class ApiService extends GetxService{
final indexController = Get.find<IndexController>(); final indexController = Get.find<IndexController>();
if (indexController.currentUser.isNotEmpty) { if (indexController.currentUser.isNotEmpty) {
token = indexController.currentUser[0]['token'] ?? ''; token = indexController.currentUser[0]['token'] ?? '';
print("Get token = $token"); //print("Get token = $token");
}else{ }else{
token = ""; token = "";
} }
@ -117,42 +117,6 @@ class ApiService extends GetxService{
} }
Future<List<Team>> getTeams_old() async {
init();
final token = await getToken2();
try {
final response = await http.get(
Uri.parse('$baseUrl/teams/'),
headers: {'Authorization': 'Token $token',"Content-Type": "application/json; charset=UTF-8"},
);
if (response.statusCode == 200) {
// UTF-8でデコード
final decodedResponse = utf8.decode(response.bodyBytes);
//print('User Response body: $decodedResponse');
List<dynamic> teamsJson = json.decode(decodedResponse);
List<Team> teams = [];
for (var teamJson in teamsJson) {
//print('\nTeam Data:');
//_printDataComparison(teamJson, Team);
teams.add(Team.fromJson(teamJson));
}
return teams;
} else {
throw Exception('Failed to load teams. Status code: ${response.statusCode}');
}
} catch (e, stackTrace) {
print('Error in getTeams: $e');
print('Stack trace: $stackTrace');
rethrow;
}
}
Future<List<NewCategory>> getCategories() async { Future<List<NewCategory>> getCategories() async {
init(); init();
getToken(); getToken();
@ -165,7 +129,7 @@ class ApiService extends GetxService{
if (response.statusCode == 200) { if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes); final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse'); //print('User Response body: $decodedResponse');
List<dynamic> categoriesJson = json.decode(decodedResponse); List<dynamic> categoriesJson = json.decode(decodedResponse);
List<NewCategory> categories = []; List<NewCategory> categories = [];
@ -200,26 +164,6 @@ class ApiService extends GetxService{
)).then((data) => NewCategory.fromJson(data)); )).then((data) => NewCategory.fromJson(data));
} }
Future<NewCategory> getZekkenNumber_old(int categoryId) async {
try {
final response = await http.post(
Uri.parse('$baseUrl/categories-viewset/$categoryId/get_zekken_number/'),
headers: {'Authorization': 'Token $token',"Content-Type": "application/json; charset=UTF-8"},
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse');
final categoriesJson = json.decode(decodedResponse);
return NewCategory.fromJson(categoriesJson);
} else {
throw Exception('Failed to increment category number');
}
} catch (e) {
throw Exception('Error incrementing category number: $e');
}
}
Future<User> getCurrentUser() async { Future<User> getCurrentUser() async {
init(); init();
final token = getToken(); final token = getToken();
@ -324,30 +268,6 @@ class ApiService extends GetxService{
)).then((data) => Team.fromJson(data)); )).then((data) => Team.fromJson(data));
} }
Future<Team> createTeam_old(String teamName, int categoryId) async {
init();
getToken();
final response = await http.post(
Uri.parse('$baseUrl/teams/'),
headers: {
'Authorization': 'Token $token',
"Content-Type": "application/json; charset=UTF-8",
},
body: json.encode({
'team_name': teamName,
'category': categoryId,
}),
);
if (response.statusCode == 201) {
final decodedResponse = utf8.decode(response.bodyBytes);
return Team.fromJson(json.decode(decodedResponse));
} else {
throw Exception('Failed to create team');
}
}
Future<Team> updateTeam(int teamId, String teamName, int categoryId) async { Future<Team> updateTeam(int teamId, String teamName, int categoryId) async {
final token = await getToken2(); final token = await getToken2();
return _handleRequest(() => http.put( return _handleRequest(() => http.put(
@ -357,31 +277,6 @@ class ApiService extends GetxService{
)).then((data) => Team.fromJson(data)); )).then((data) => Team.fromJson(data));
} }
Future<Team> updateTeam_old(int teamId, String teamName, int categoryId) async {
init();
getToken();
final response = await http.put(
Uri.parse('$baseUrl/teams/$teamId/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json; charset=UTF-8',
},
body: json.encode({
'team_name': teamName,
'category': categoryId,
}),
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
return Team.fromJson(json.decode(decodedResponse));
} else {
throw Exception('Failed to update team');
}
}
Future<void> deleteTeam(int teamId) async { Future<void> deleteTeam(int teamId) async {
final token = await getToken2(); final token = await getToken2();
await _handleRequest(() => http.delete( await _handleRequest(() => http.delete(
@ -390,24 +285,6 @@ class ApiService extends GetxService{
)); ));
} }
Future<void> deleteTeamold_(int teamId) async {
init();
getToken();
final response = await http.delete(
Uri.parse('$baseUrl/teams/$teamId/'),
headers: {'Authorization': 'Token $token','Content-Type': 'application/json; charset=UTF-8'},
);
if( response.statusCode == 400) {
final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse');
throw Exception('まだメンバーが残っているので、チームを削除できません。');
}else if (response.statusCode != 204) {
throw Exception('Failed to delete team');
}
}
Future<List<User>> getTeamMembers(int teamId) async { Future<List<User>> getTeamMembers(int teamId) async {
final token = await getToken2(); final token = await getToken2();
return _handleRequest(() => http.get( return _handleRequest(() => http.get(
@ -416,26 +293,6 @@ class ApiService extends GetxService{
)).then((data) => (data as List).map((json) => User.fromJson(json)).toList()); )).then((data) => (data as List).map((json) => User.fromJson(json)).toList());
} }
Future<List<User>> getTeamMembers_old(int teamId) async {
init();
getToken();
final response = await http.get(
Uri.parse('$baseUrl/teams/$teamId/members/'),
headers: {'Authorization': 'Token $token','Content-Type': 'application/json; charset=UTF-8'},
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse');
List<dynamic> membersJson = json.decode(decodedResponse);
return membersJson.map((json) => User.fromJson(json)).toList();
} else {
throw Exception('Failed to load team members');
}
}
Future<User> createTeamMember(int teamId, String? email, String? firstname, String? lastname, DateTime? dateOfBirth, bool? female) async { Future<User> createTeamMember(int teamId, String? email, String? firstname, String? lastname, DateTime? dateOfBirth, bool? female) async {
final token = await getToken2(); final token = await getToken2();
String? formattedDateOfBirth = dateOfBirth != null ? DateFormat('yyyy-MM-dd').format(dateOfBirth) : null; String? formattedDateOfBirth = dateOfBirth != null ? DateFormat('yyyy-MM-dd').format(dateOfBirth) : null;
@ -452,45 +309,6 @@ class ApiService extends GetxService{
)).then((data) => User.fromJson(data)); )).then((data) => User.fromJson(data));
} }
Future<User> createTeamMember_old(int teamId, String? email, String? firstname, String? lastname, DateTime? dateOfBirth,bool? female) async {
init();
getToken();
// emailが値を持っている場合の処理
if (email != null && email.isNotEmpty) {
firstname ??= "dummy";
lastname ??= "dummy";
dateOfBirth ??= DateTime.now();
female ??= false;
}
String? formattedDateOfBirth;
if (dateOfBirth != null) {
formattedDateOfBirth = DateFormat('yyyy-MM-dd').format(dateOfBirth);
}
final response = await http.post(
Uri.parse('$baseUrl/teams/$teamId/members/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json; charset=UTF-8',
},
body: json.encode({
'email': email,
'firstname': firstname,
'lastname': lastname,
'date_of_birth': formattedDateOfBirth,
'female': female,
}),
);
if (response.statusCode == 200 || response.statusCode == 201) {
final decodedResponse = utf8.decode(response.bodyBytes);
return User.fromJson(json.decode(decodedResponse));
} else {
throw Exception('Failed to create team member');
}
}
Future<User> updateTeamMember(int teamId, int? memberId, String firstname, String lastname, DateTime? dateOfBirth, bool? female) async { Future<User> updateTeamMember(int teamId, int? memberId, String firstname, String lastname, DateTime? dateOfBirth, bool? female) async {
final token = await getToken2(); final token = await getToken2();
@ -507,37 +325,6 @@ class ApiService extends GetxService{
)).then((data) => User.fromJson(data)); )).then((data) => User.fromJson(data));
} }
Future<User> updateTeamMember_old(int teamId,int? memberId, String firstname, String lastname, DateTime? dateOfBirth,bool? female) async {
init();
getToken();
String? formattedDateOfBirth;
if (dateOfBirth != null) {
formattedDateOfBirth = DateFormat('yyyy-MM-dd').format(dateOfBirth);
}
final response = await http.put(
Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json; charset=UTF-8',
},
body: json.encode({
'firstname': firstname,
'lastname': lastname,
'date_of_birth': formattedDateOfBirth,
'female': female,
}),
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
return User.fromJson(json.decode(decodedResponse));
} else {
throw Exception('Failed to update team member');
}
}
Future<void> deleteTeamMember(int teamId, int memberId) async { Future<void> deleteTeamMember(int teamId, int memberId) async {
final token = await getToken2(); final token = await getToken2();
await _handleRequest(() => http.delete( await _handleRequest(() => http.delete(
@ -546,20 +333,6 @@ class ApiService extends GetxService{
)); ));
} }
Future<void> deleteTeamMember_old(int teamId,int memberId) async {
init();
getToken();
final response = await http.delete(
Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode != 204) {
throw Exception('Failed to delete team member');
}
}
Future<void> deleteAllTeamMembers(int teamId) async { Future<void> deleteAllTeamMembers(int teamId) async {
final response = await http.delete( final response = await http.delete(
Uri.parse('$baseUrl/teams/$teamId/members/destroy_all/?confirm=true'), Uri.parse('$baseUrl/teams/$teamId/members/destroy_all/?confirm=true'),
@ -594,24 +367,6 @@ class ApiService extends GetxService{
)).then((data) => (data as List).map((json) => Entry.fromJson(json)).toList()); )).then((data) => (data as List).map((json) => Entry.fromJson(json)).toList());
} }
Future<List<Entry>> getEntries_old() async {
init();
getToken();
final response = await http.get(
Uri.parse('$baseUrl/entry/'),
headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
List<dynamic> entriesJson = json.decode(decodedResponse);
return entriesJson.map((json) => Entry.fromJson(json)).toList();
} else {
throw Exception('Failed to load entries');
}
}
Future<List<Entry>> getTeamEntries(int teamId) async { Future<List<Entry>> getTeamEntries(int teamId) async {
try { try {
@ -632,6 +387,21 @@ class ApiService extends GetxService{
} }
} }
Future<Entry> updateEntryStatus(int entryId, bool hasParticipated, bool hasGoaled) async {
final token = await getToken2();
return _handleRequest(() => http.patch(
Uri.parse('$baseUrl/entries/$entryId/update-status/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json; charset=UTF-8'
},
body: json.encode({
'hasParticipated': hasParticipated,
'hasGoaled': hasGoaled,
}),
)).then((data) => Entry.fromJson(data));
}
Future<List<Event>> getEvents() async { Future<List<Event>> getEvents() async {
init(); init();
getToken(); getToken();
@ -669,39 +439,6 @@ class ApiService extends GetxService{
)).then((data) => Entry.fromJson(data)); )).then((data) => Entry.fromJson(data));
} }
Future<Entry> createEntry_old(int teamId, int eventId, int categoryId, DateTime date,String zekkenNumber) async {
init();
getToken();
String? formattedDate;
formattedDate = DateFormat('yyyy-MM-dd').format(date);
final response = await http.post(
Uri.parse('$baseUrl/entry/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json; charset=UTF-8',
},
body: json.encode({
'team': teamId,
'event': eventId,
'category': categoryId,
'date': formattedDate,
'zekken_number':zekkenNumber,
}),
);
if (response.statusCode == 201) {
final decodedResponse = utf8.decode(response.bodyBytes);
return Entry.fromJson(json.decode(decodedResponse));
} else {
final decodedResponse = utf8.decode(response.bodyBytes);
print("decodedResponse = $decodedResponse");
final errorInfo = json.decode(decodedResponse);
throw Exception(errorInfo['error']);
}
}
Future<void> updateUserInfo(int userId, Entry entry) async { Future<void> updateUserInfo(int userId, Entry entry) async {
final token = await getToken2(); final token = await getToken2();
@ -719,40 +456,6 @@ class ApiService extends GetxService{
)); ));
} }
Future<void> updateUserInfo_old(int userId, Entry entry) async {
init();
getToken();
final entryId = entry.id;
DateTime? date = entry.date;
String? formattedDate;
formattedDate = DateFormat('yyyy-MM-dd').format(date!);
final response = await http.put(
Uri.parse('$baseUrl/userinfo/$userId/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json; charset=UTF-8',
},
body: json.encode({
'zekken_number': entry.zekkenNumber,
'event_code': entry.event.eventName,
'group': entry.team.category.categoryName,
'team_name': entry.team.teamName,
'date': formattedDate,
}),
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
final updatedUserInfo = json.decode(decodedResponse);
//Get.find<IndexController>().updateUserInfo(updatedUserInfo);
} else {
throw Exception('Failed to update entry');
}
}
Future<Entry> updateEntry(int entryId, int teamId, int eventId, int categoryId, DateTime date,int zekkenNumber) async { Future<Entry> updateEntry(int entryId, int teamId, int eventId, int categoryId, DateTime date,int zekkenNumber) async {
init(); init();

View File

@ -87,8 +87,7 @@ class AuthService {
return changePassword; return changePassword;
} }
static Future<Map<String, dynamic>> login( static Future<Map<String, dynamic>> login(String email, String password) async {
String email, String password) async {
//print("------- in logged email $email pwd $password ###### --------"); //print("------- in logged email $email pwd $password ###### --------");
Map<String, dynamic> cats = {}; Map<String, dynamic> cats = {};
String serverUrl = ConstValues.currentServer(); String serverUrl = ConstValues.currentServer();
@ -116,17 +115,12 @@ class AuthService {
var errorBody = json.decode(utf8.decode(response.bodyBytes)); var errorBody = json.decode(utf8.decode(response.bodyBytes));
errorMessage = errorBody['non_field_errors']?[0] ?? 'パスワードが正しくありません。'; errorMessage = errorBody['non_field_errors']?[0] ?? 'パスワードが正しくありません。';
} }
Get.snackbar( throw Exception(errorMessage);
"エラー",
errorMessage,
backgroundColor: Colors.red,
colorText: Colors.white,
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
);
cats = {};
} }
} catch( e ){ } catch( e ){
print('Error in login: $e');
throw e; // エラーを上位に伝播させる
/*
print('Error in login: $e'); print('Error in login: $e');
Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした", Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした",
backgroundColor: Colors.red, backgroundColor: Colors.red,
@ -142,7 +136,8 @@ class AuthService {
duration: const Duration(seconds: 3), duration: const Duration(seconds: 3),
//backgroundColor: Colors.yellow, //backgroundColor: Colors.yellow,
); );
cats = {}; */
//cats = {};
} }
return cats; return cats;
} }
@ -171,7 +166,7 @@ class AuthService {
Map<String, dynamic> cats = {}; Map<String, dynamic> cats = {};
String serverUrl = ConstValues.currentServer(); String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/register/'; String url = '$serverUrl/api/register/';
debugPrint('++++++++$url'); //debugPrint('++++++++$url');
final http.Response response = await http.post( final http.Response response = await http.post(
Uri.parse(url), Uri.parse(url),
headers: <String, String>{ headers: <String, String>{

View File

@ -1,6 +1,8 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:gifunavi/pages/entry/entry_controller.dart';
import 'package:gifunavi/services/api_service.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:gifunavi/model/rog.dart'; import 'package:gifunavi/model/rog.dart';
@ -33,6 +35,8 @@ class ExternalService {
Future<Map<String, dynamic>> startRogaining() async { Future<Map<String, dynamic>> startRogaining() async {
final IndexController indexController = Get.find<IndexController>(); final IndexController indexController = Get.find<IndexController>();
//final TeamController teamController = Get.find<TeamController>(); //final TeamController teamController = Get.find<TeamController>();
final ApiService apiService = Get.find<ApiService>();
final EntryController entryController = Get.find<EntryController>();
debugPrint("== startRogaining =="); debugPrint("== startRogaining ==");
@ -68,9 +72,21 @@ class ExternalService {
} else { } else {
debugPrint("== startRogaining processing=="); debugPrint("== startRogaining processing==");
try {
// 新しい API 呼び出しを使用
final updatedEntry = await apiService.updateEntryStatus(
entryController.currentEntry.value!.id!,
true, // hasParticipated を true に設定
false // hasGoaled は変更しない
);
// start_rogaining を本サーバーに送る。 TODO
// // 'has_participated': true ... Server 側の修正が必要 TODO
String serverUrl = ConstValues.currentServer(); String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/gifuroge/start_from_rogapp'; String url = '$serverUrl/gifuroge/start_from_rogapp';
print('++++++++$url'); //print('++++++++$url');
final http.Response response = await http.post( final http.Response response = await http.post(
Uri.parse(url), Uri.parse(url),
headers: <String, String>{ headers: <String, String>{
@ -78,6 +94,7 @@ class ExternalService {
}, },
body: jsonEncode( body: jsonEncode(
<String, String>{'team_name': team, 'event_code': eventCode}), <String, String>{'team_name': team, 'event_code': eventCode}),
// 'has_participated': true ... Server 側の修正が必要 TODO
); );
print("---- start rogianing api status ---- ${response.statusCode}"); print("---- start rogianing api status ---- ${response.statusCode}");
@ -85,6 +102,12 @@ class ExternalService {
if (response.statusCode == 200) { if (response.statusCode == 200) {
res = json.decode(utf8.decode(response.bodyBytes)); res = json.decode(utf8.decode(response.bodyBytes));
//print('----_res : $res ----'); //print('----_res : $res ----');
// ローカルのユーザーデータを更新
indexController.updateUserParticipationStatus(true);
}
}catch (e) {
print('Error in startRogaining: $e');
Get.snackbar('エラー', 'ロゲイニングの開始に失敗しました');
} }
} }
return res; return res;
@ -226,6 +249,8 @@ class ExternalService {
final IndexController indexController = Get.find<IndexController>(); final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController = final DestinationController destinationController =
Get.find<DestinationController>(); Get.find<DestinationController>();
final ApiService apiService = Get.find<ApiService>();
final EntryController entryController = Get.find<EntryController>();
// チームIDを取得 // チームIDを取得
@ -253,6 +278,14 @@ class ExternalService {
final im1Bytes = File(image).readAsBytesSync(); final im1Bytes = File(image).readAsBytesSync();
String im1_64 = base64Encode(im1Bytes); String im1_64 = base64Encode(im1Bytes);
try {
// 新しい API呼び出しを使用
final updatedEntry = await apiService.updateEntryStatus(
entryController.currentEntry.value!.id!,
true, // hasParticipated を true に設定
true // hasGoaled を true に設定
);
final http.Response response = await http.post( final http.Response response = await http.post(
Uri.parse(url1), Uri.parse(url1),
headers: <String, String>{ headers: <String, String>{
@ -275,7 +308,8 @@ class ExternalService {
//print('++++++++$url'); //print('++++++++$url');
if (response.statusCode == 201) { if (response.statusCode == 201) {
try { try {
Map<String, dynamic> res = json.decode(utf8.decode(response.bodyBytes)); Map<String, dynamic> res = json.decode(
utf8.decode(response.bodyBytes));
// print('----_res : $res ----'); // print('----_res : $res ----');
// print('---- image url ${res["goalimage"]} ----'); // print('---- image url ${res["goalimage"]} ----');
final http.Response response2 = await http.post( final http.Response response2 = await http.post(
@ -289,6 +323,7 @@ class ExternalService {
'goal_time': goalTime, 'goal_time': goalTime,
'image': res["goalimage"].toString().replaceAll( 'image': res["goalimage"].toString().replaceAll(
'http://localhost:8100', serverUrl) 'http://localhost:8100', serverUrl)
// 'has_goaled': true ... サーバー側の修正が必要 TODO
//'http://rogaining.sumasen.net') //'http://rogaining.sumasen.net')
}), }),
); );
@ -305,13 +340,21 @@ class ExternalService {
//print('----- response2 is $response2 --------'); //print('----- response2 is $response2 --------');
if (response2.statusCode == 200) { if (response2.statusCode == 200) {
res2 = json.decode(utf8.decode(response2.bodyBytes)); res2 = json.decode(utf8.decode(response2.bodyBytes));
// ローカルのユーザーデータを更新
indexController.updateUserGoalStatus(true);
} else { } else {
res2 = json.decode(utf8.decode(response2.bodyBytes)); res2 = json.decode(utf8.decode(response2.bodyBytes));
// ここはどうする? TODO
} }
} catch (e) { } catch (e) {
print("Error {$e}"); print("Error {$e}");
} }
} }
}catch(e) {
print("Error in makeGoal: $e");
Get.snackbar('エラー', 'ゴール処理に失敗しました');
}
//} //}
destinationController.resetRogaining(isgoal: true); destinationController.resetRogaining(isgoal: true);
return res2; return res2;
@ -370,15 +413,6 @@ 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"];

View File

@ -17,6 +17,19 @@ class MotionService {
} }
} }
Future<dynamic> _handleMotionData(MethodCall call) async {
switch (call.method) {
case 'onMotionData':
final Map<String, dynamic> motionData = call.arguments;
// ここでモーションデータを処理します
print('Received motion data: $motionData');
// 例: データを状態管理システムに渡す、UIを更新する等
break;
default:
print('Unknown method ${call.method}');
}
}
static Future<void> stopMotionUpdates() async { static Future<void> stopMotionUpdates() async {
if (Platform.isIOS) { if (Platform.isIOS) {
try { try {

View File

@ -32,6 +32,8 @@ class LocationController extends GetxController {
// Reactive variable to hold the current position // Reactive variable to hold the current position
Rx<Position?> currentPosition = Rx<Position?>(null); Rx<Position?> currentPosition = Rx<Position?>(null);
// 現在の位置情報を保持するReactive変数です。Rx<Position?>型で宣言されています。 // 現在の位置情報を保持するReactive変数です。Rx<Position?>型で宣言されています。
final locationMarkerPositionStreamController = StreamController<LocationMarkerPosition?>.broadcast();
// Subscription to the position stream // Subscription to the position stream
StreamSubscription<Position>? positionStream; StreamSubscription<Position>? positionStream;
@ -154,8 +156,8 @@ class LocationController extends GetxController {
//===== Akira Added 2024-4-9 end //===== Akira Added 2024-4-9 end
final locationMarkerPositionStreamController = //final locationMarkerPositionStreamController =
StreamController<LocationMarkerPosition?>.broadcast(); // StreamController<LocationMarkerPosition?>.broadcast();
// 位置マーカーの位置情報を送信するためのStreamControllerです。 // 位置マーカーの位置情報を送信するためのStreamControllerです。
// StreamController<LocationMarkerPosition?>型で宣言されています。 // StreamController<LocationMarkerPosition?>型で宣言されています。
@ -202,6 +204,7 @@ class LocationController extends GetxController {
} }
startPositionStream(); startPositionStream();
debugPrint("Started startPositionStream");
} catch( e ){ } catch( e ){
print('Error initializing location service: $e'); print('Error initializing location service: $e');
} }
@ -235,7 +238,12 @@ class LocationController extends GetxController {
accuracy: position.accuracy, accuracy: position.accuracy,
), ),
); );
}); },
onError: (error) {
debugPrint('Error in Geolocator stream: $error');
locationMarkerPositionStreamController.addError(error);
},
);
} }
void startPositionStream_old() async { void startPositionStream_old() async {

View File

@ -1,35 +0,0 @@
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('再試行'),
),
],
),
);
}
}

View File

@ -1,385 +1,87 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:gifunavi/pages/permission/permission.dart';
import 'package:gifunavi/pages/settings/settings_binding.dart';
import 'package:gifunavi/model/destination.dart';
import 'package:gifunavi/pages/destination/destination_controller.dart';
import 'package:gifunavi/pages/index/index_controller.dart'; import 'package:gifunavi/pages/index/index_controller.dart';
import 'package:gifunavi/utils/database_helper.dart';
import 'package:gifunavi/utils/location_controller.dart'; import 'package:gifunavi/utils/location_controller.dart';
import 'package:gifunavi/utils/text_util.dart'; import 'package:gifunavi/pages/destination/destination_controller.dart';
import 'package:gifunavi/widgets/base_layer_widget.dart';
import 'package:gifunavi/widgets/bottom_sheet_new.dart';
import 'package:gifunavi/widgets/current_position_widget.dart';
import 'package:gifunavi/widgets/game_state_view.dart';
import 'package:gifunavi/pages/settings/settings_controller.dart'; import 'package:gifunavi/pages/settings/settings_controller.dart';
import 'package:gifunavi/widgets/base_layer_widget.dart';
import 'package:gifunavi/widgets/game_state_view.dart';
import 'package:gifunavi/widgets/current_position_widget.dart';
class MapResetController { class MapWidget extends StatelessWidget {
void Function()? resetIdleTimer;
}
class MapWidget extends StatefulWidget {
const MapWidget({super.key});
@override
State<MapWidget> createState() => _MapWidgetState();
}
class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
//class _MapWidgetState extends State<MapWidget> {
final IndexController indexController = Get.find<IndexController>(); final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController = final DestinationController destinationController = Get.find<DestinationController>();
Get.find<DestinationController>();
final LocationController locationController = Get.find<LocationController>(); final LocationController locationController = Get.find<LocationController>();
final SettingsController settingsController = Get.find<SettingsController>(); final SettingsController settingsController = Get.find<SettingsController>();
late MapController mapController; MapWidget({Key? key}) : super(key: key) {
final Completer<MapController> mapControllerCompleter = Completer<MapController>(); _initializeControllers();
}
StreamSubscription? subscription; void _initializeControllers() {
Timer? _timer; indexController.initMapController();
bool curr_marker_display = false;
final Map<LatLng, Marker> _markerCache = {};
List<Marker> _markers = [];
@override
void initState() {
super.initState();
// 追加
/*
WidgetsBinding.instance.addPostFrameCallback((_) {
PermissionController.checkAndRequestPermissions();
});
*/
debugPrint('MapWidget: initState called');
SettingsBinding().dependencies(); // これを追加
_startIdleTimer();
mapController = MapController();
indexController.mapController = mapController;
// added by Akira
WidgetsBinding.instance.addObserver(this);
_startIdleTimer(); _startIdleTimer();
// マップの操作イベントをリッスンして、_resetTimerを呼び出す ever(indexController.isMapControllerReady, (_) {
mapController.mapEventStream.listen((MapEvent mapEvent) { if (indexController.isMapControllerReady.value) {
if (mapEvent is MapEventMove || mapEvent is MapEventFlingAnimation) { _initMarkers();
_resetTimer();
} }
}); });
// MapControllerの初期化が完了するまで待機
WidgetsBinding.instance.addPostFrameCallback((_) {
debugPrint("MapControllerの初期化が完了");
setState(() {
indexController.isMapControllerReady.value = true;
});
// MapControllerの初期化が完了したら、IndexControllerのonInitを呼び出す
//indexController.checkPermission();
//PermissionController.checkAndRequestPermissions();
});
late MapResetController mapResetController = MapResetController();
mapResetController.resetIdleTimer = _resetIdleTimer;
Get.put(mapResetController);
// Add this debug subscription
subscription = locationController.locationMarkerPositionStreamController.stream.listen(
(LocationMarkerPosition? position) {
if (position != null) {
//debugPrint('Location update received: lat=${position.latitude}, lon=${position.longitude}');
} else {
debugPrint('Received null location update');
}
},
onError: (error) {
debugPrint('Error in location stream: $error');
},
onDone: () {
debugPrint('Location stream closed');
},
);
// indexController.mapController = MapController(initCompleter: mapControllerCompleter);
}
void _resetIdleTimer() {
debugPrint("_resetIdleTimer...");
_timer?.cancel();
_startIdleTimer();
}
@override
void dispose() {
debugPrint('MapWidget: dispose called');
WidgetsBinding.instance.removeObserver(this); // added
mapController.dispose();
_timer?.cancel();
super.dispose();
}
// added by Akira
/*
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
debugPrint("MapWidget:didChangeAppLifecycleState...state=${state}");
if (state == AppLifecycleState.resumed) {
_resetTimer();
}
}
*/
// _centerMapOnUser を10秒間でコール
Future<void> _startIdleTimer() async {
//debugPrint("_startIdleTimer ....");
SettingsController settingsController;
// SettingsControllerが利用可能になるまで待機
while (true) {
try {
settingsController = Get.find<SettingsController>();
break; // SettingsControllerが見つかったらループを抜ける
} catch (e) {
// SettingsControllerがまだ利用可能でない場合は少し待ってから再試行
await Future.delayed(const Duration(milliseconds: 100));
}
} }
void _startIdleTimer() {
if (!settingsController.autoReturnDisabled.value) { if (!settingsController.autoReturnDisabled.value) {
_timer = Timer(settingsController.timerDuration.value, _centerMapOnUser); Future.delayed(settingsController.timerDuration.value, _centerMapOnUser);
}
} }
}
// タイマーをリセットして_startIdleTimer をコール
void _resetTimer() {
//debugPrint("_resetTimer ....");
_timer?.cancel();
_startIdleTimer();
}
// マッぷを現在位置を中心にする。
void _centerMapOnUser() { void _centerMapOnUser() {
//debugPrint("_centerMapOnUser ....");
if (mounted) {
//debugPrint("_centerMapOnUser => centering ....");
destinationController.centerMapToCurrentLocation(); destinationController.centerMapToCurrentLocation();
} _startIdleTimer();
} }
Future<void> _initMarkers() async { Future<void> _initMarkers() async {
List<Marker> markers = await _getMarkers(); indexController.markers.value = await _getMarkers();
setState(() {
_markers = markers;
});
} }
Future<List<Marker>> _getMarkers() async { Future<List<Marker>> _getMarkers() async {
debugPrint('Getting markers...');
if (indexController.isLoadingLocations.value) { if (indexController.isLoadingLocations.value) {
await Future.doWhile(() async { await indexController.waitForLocationsToLoad();
await Future.delayed(const Duration(milliseconds: 100));
return indexController.isLoadingLocations.value;
});
} }
debugPrint('Getting markers...');
List<Marker> markers = []; List<Marker> markers = [];
if (indexController.locations.isNotEmpty && indexController.locations[0].features.isNotEmpty) { if (indexController.locations.isNotEmpty && indexController.locations[0].features.isNotEmpty) {
for (var feature in indexController.locations[0].features) { for (var feature in indexController.locations[0].features) {
GeoJSONMultiPoint point = feature!.geometry as GeoJSONMultiPoint; // マーカーの作成ロジック
LatLng latLng = LatLng(point.coordinates[0][1], point.coordinates[0][0]); // 実際のマーカー作成ロジックをここに実装してください
markers.add(Marker(
point: latLng,
width: 30.0,
height: 30.0,
child: getMarkerShape(feature),
));
} }
} else { } else {
debugPrint('No locations or features available'); debugPrint('No locations or features available');
} }
return markers; return markers;
} }
// Widget getMarkerShape(GeoJSONFeature i, BuildContext context) {
Widget getMarkerShape(GeoJSONFeature i) {
GeoJSONMultiPoint p = i.geometry as GeoJSONMultiPoint;
return InkWell(
onTap: () {
GeoJSONFeature? fs = indexController.getFeatureForLatLong(
p.coordinates[0][1], p.coordinates[0][0]);
if (fs != null) {
indexController.currentFeature.clear();
indexController.currentFeature.add(fs);
Destination des = destinationController.festuretoDestination(fs);
DatabaseHelper db = DatabaseHelper.instance;
db.getDestinationByLatLon(des.lat!, des.lon!).then((value) {
destinationController.shouldShowBottomSheet = false;
showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.85)),
context: context,
isScrollControlled: true,
isDismissible: true,
builder: ((context) => BottomSheetNew(
destination: des, isAlreadyCheckedIn: value.isNotEmpty)),
).whenComplete(() {
destinationController.shouldShowBottomSheet = true;
destinationController.skipGps = false;
});
});
}
},
child: Stack(
fit: StackFit.expand,
children: [
Container( // マーカー
height: 32,
width: 32,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.transparent,
border: Border.all(
color: i.properties!['buy_point'] > 0
? Colors.blue
: Colors.red,
width: 3,
style: BorderStyle.solid,
),
),
child: Stack(
alignment: Alignment.center,
children: [
const Icon(
Icons.circle,
size: 6.0,
),
i.properties!['cp'] <= 0 ? Transform.translate
(
offset: const Offset(-3, 0), //-3
child: Transform.rotate(
alignment: Alignment.centerLeft,
origin: Offset.fromDirection(1, 26),
angle: 270 * pi / 180,
child: const Icon(
Icons.play_arrow_outlined,
color: Colors.red,
size: 70,
)),
)
: Container(
color: Colors.transparent,
),
],
),
),
Transform.translate(
offset: const Offset(30, 0), // 30,0
child: Align(
alignment: Alignment.center,
child: Container (
//width: 80, // 幅を指定
//height: 60, // 40
//color: Colors.purple.withOpacity(0.2),
color: Colors.transparent,
//child: Text(' ').
//constraints: const BoxConstraints(maxWidth: 60.0), // 最大幅を設定
//constraints: BoxConstraints(maxWidth: maxWidth), // 最大幅を設定
//color: Colors.purple.withOpacity(0.2),
child: Stack(
children: <Widget>[
Text( // アウトライン
TextUtils.getDisplayTextFeture(i),
style: TextStyle(
fontSize: 16, // 16
fontWeight: FontWeight.w700,
overflow: TextOverflow.visible,
//height: 1.2,
foreground: Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1 // 2
..color = Colors.white,
),
maxLines: 1, // テキストを1行に制限
softWrap: false, // テキストの折り返しを無効化
),
Text( // テキスト
TextUtils.getDisplayTextFeture(i),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
overflow: TextOverflow.visible,
//fontWeight: FontWeight.bold,
//height: 1.2,
color: Colors.black,
),
maxLines: 1, // テキストを1行に制限
softWrap: false, // テキストの折り返しを無効化
),
],
),
),
),
)
],
),
);
}
List<LatLng>? getPoints() { List<LatLng>? getPoints() {
List<LatLng> pts = []; return indexController.routePoints.map((p) => LatLng(p.latitude, p.longitude)).toList();
for (PointLatLng p in indexController.routePoints) {
LatLng l = LatLng(p.latitude, p.longitude);
pts.add(l);
}
return pts;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final settingsController = Get.find<SettingsController>(); // これを追加
//final PopupController popupController = PopupController();
return Stack( return Stack(
children: [ children: [
Obx(() => indexController.isLoading.value == true Obx(() => indexController.isLoading.value
? const Padding( ? const Center(child: CircularProgressIndicator())
padding: EdgeInsets.only(top: 60.0),
child: CircularProgressIndicator(),
)
: FlutterMap( : FlutterMap(
mapController: mapController, mapController: indexController.mapController,
//mapController: indexController.mapController,
options: MapOptions( options: MapOptions(
maxZoom: 18.4, maxZoom: 18.4,
onMapReady: () { onMapReady: () {
_initMarkers(); indexController.isMapControllerReady.value = true;
//indexController.isMapControllerReady.value = true;
}, },
initialCenter: initialCenter: const LatLng(37.15319600454702, 139.58765950528198),
const LatLng(37.15319600454702, 139.58765950528198),
bounds: indexController.currentBound.isNotEmpty bounds: indexController.currentBound.isNotEmpty
? indexController.currentBound[0] ? indexController.currentBound[0]
: LatLngBounds.fromPoints([ : LatLngBounds.fromPoints([
@ -387,27 +89,22 @@ class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
const LatLng(36.642756778706904, 137.95226720406063) const LatLng(36.642756778706904, 137.95226720406063)
]), ]),
initialZoom: 1, initialZoom: 1,
interactiveFlags: interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag,
InteractiveFlag.pinchZoom | InteractiveFlag.drag, onPositionChanged: (MapPosition pos, bool hasGesture) {
onPositionChanged: (MapPosition pos, hasGesture) {
if (hasGesture) { if (hasGesture) {
_resetTimer(); _startIdleTimer();
} }
indexController.currentBound = [pos.bounds!]; indexController.currentBound = [pos.bounds!];
}, },
onMapEvent: (MapEvent mapEvent) { onMapEvent: (MapEvent mapEvent) {
//debugPrint('Map event: ${mapEvent.runtimeType}');
if (mapEvent is MapEventMove) { if (mapEvent is MapEventMove) {
destinationController.shouldShowBottomSheet = true; destinationController.shouldShowBottomSheet = true;
} }
}, },
//onTap: (_, __) => popupController.hideAllPopups(),
), ),
children: [ children: [
const BaseLayer(), const BaseLayer(),
// ルートのポリライン表示 Obx(() => indexController.routePointLenght > 0
Obx(
() => indexController.routePointLenght > 0
? PolylineLayer( ? PolylineLayer(
polylines: [ polylines: [
Polyline( Polyline(
@ -417,13 +114,9 @@ class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
), ),
], ],
) )
: Container(), : Container()),
),
// 現在位置のマーカー
CurrentLocationLayer( CurrentLocationLayer(
positionStream: locationController positionStream: locationController.locationMarkerPositionStream,
.locationMarkerPositionStreamController.stream,
//alignDirectionOnUpdate: AlignOnUpdate.never,
style: const LocationMarkerStyle( style: const LocationMarkerStyle(
marker: Stack( marker: Stack(
children: [ children: [
@ -437,40 +130,24 @@ class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
markerSize: Size(27, 27), markerSize: Size(27, 27),
markerDirection: MarkerDirection.heading, markerDirection: MarkerDirection.heading,
), ),
//child: const Icon(Icons.navigation),
), ),
Obx(() => MarkerLayer(markers: indexController.markers)),
FutureBuilder<List<Marker>>(
future: indexController.locations.isNotEmpty ? _getMarkers() : null,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return const Center(child: Text('マーカーの読み込みに失敗しました'));
} else {
return MarkerLayer(markers: snapshot.data ?? []);
}
},
),
//MarkerLayer(markers: indexController.locations.isNotEmpty ? _getMarkers() : []),
], ],
)), )),
const Positioned(top: 0, left: 0, child: GameStateWidget()), const Positioned(top: 0, left: 0, child: GameStateWidget()),
const Positioned(bottom: 10, right: 10, child: CurrentPosition()), const Positioned(bottom: 10, right: 10, child: CurrentPosition()),
StreamBuilder<LocationMarkerPosition?>( Obx(() => indexController.currentMarkerPosition.value != null
stream: locationController.locationMarkerPositionStream, ? Container() // 現在のマーカー位置が更新されたときの処理
builder: (context, snapshot) { : Container()),
if (!snapshot.hasData) {
//debugPrint("====== Not display current marker");
curr_marker_display = true;
}else if(curr_marker_display){
debugPrint("====== Displayed current marker");
curr_marker_display = false;
}
return Container();
},
)
], ],
); );
} }
} }
class MapResetController extends GetxController {
void Function()? resetIdleTimer;
void setResetIdleTimer(void Function() resetFunction) {
resetIdleTimer = resetFunction;
}
}

View File

@ -14,9 +14,6 @@ class _PermissionHandlerScreenState extends State<PermissionHandlerScreen> {
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
} }
@override @override

View File

@ -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.20+500 version: 4.8.19+499
environment: environment:
sdk: ^3.5.0 sdk: ^3.5.0