1 Commits

Author SHA1 Message Date
e37c4ceebd temporary update 2024-09-08 18:16:51 +09:00
32 changed files with 1235 additions and 1189 deletions

1
TODO.txt Normal file
View File

@ -0,0 +1 @@
期限切れのイベントは表示しない。==> メッセージで期限切れであることを表示すること。

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,8 +12,9 @@ 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: "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
@ -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

@ -19,26 +19,37 @@
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/> <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="岐阜ナビ" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WPs-nj-CIV"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="岐阜ナビ" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WPs-nj-CIV">
<rect key="frame" x="46" y="123" width="314" height="59"/> <rect key="frame" x="36" y="123.99999999999999" width="321" height="38.333333333333329"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="32"/> <fontDescription key="fontDescription" type="system" pointSize="32"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="NPO 岐阜aiネットワーク" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TM1-SD-6RA"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="NPO 岐阜aiネットワーク" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TM1-SD-6RA">
<rect key="frame" x="46" y="722" width="314" height="41"/> <rect key="frame" x="36" y="717.66666666666663" width="321" height="20.333333333333371"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="hCH-Iu-4S2"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="hCH-Iu-4S2">
<rect key="frame" x="83" y="314" width="240" height="224"/> <rect key="frame" x="36" y="212.33333333333334" width="321" height="405.33333333333326"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <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>
<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="20" id="3lP-Fl-Xzw"/>
<constraint firstAttribute="trailingMargin" secondItem="TM1-SD-6RA" secondAttribute="trailing" constant="20" id="E8j-R8-JVy"/>
<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="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" constant="20" id="N5f-Kk-hnZ"/>
<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="top" secondItem="Ydg-fD-yQy" secondAttribute="bottom" constant="65" id="aPG-JX-zf5"/>
<constraint firstItem="hCH-Iu-4S2" firstAttribute="top" secondItem="WPs-nj-CIV" secondAttribute="bottom" constant="50" id="jKu-Hc-2ln"/>
<constraint firstItem="WPs-nj-CIV" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" constant="20" id="u9d-2i-Jy1"/>
</constraints>
</view> </view>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>

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';
@ -198,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();
@ -216,10 +227,7 @@ void main() async {
}; };
try { try {
await initServices(); await initServices();
runApp(const ProviderScope(child: MyApp())); runApp(const ProviderScope(child: MyApp()));
}catch(e, stackTrace){ }catch(e, stackTrace){
print('Error during initialization: $e'); print('Error during initialization: $e');
@ -230,21 +238,76 @@ 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>())); Get.lazyPut(() => IndexController(apiService: Get.find<ApiService>()));
debugPrint("2: start IndexController"); debugPrint("2: start IndexController");
@ -256,7 +319,8 @@ Future<void> initServices() async {
debugPrint("3: start DestinationController"); debugPrint("3: start DestinationController");
Get.lazyPut(() => LocationController()); Get.lazyPut(() => LocationController());
debugPrint("4: start LocationController"); debugPrint("4: start LocationController");
*/ Get.lazyPut(() => PermissionController());
}
// 非同期処理を並列実行 // 非同期処理を並列実行
await Future.wait([ await Future.wait([
@ -267,10 +331,11 @@ 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');
} }
@ -294,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 {
@ -474,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');
} }
@ -569,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));
} }
@ -580,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;
} }
@ -785,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:
@ -835,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) {
@ -865,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
@ -929,7 +1051,4 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
enableLog: true, enableLog: true,
); );
} }
} }

View File

@ -11,6 +11,8 @@ class Entry {
final DateTime? date; final DateTime? date;
final int zekkenNumber; // 新しく追加 final int zekkenNumber; // 新しく追加
final String owner; final String owner;
bool hasParticipated;
bool hasGoaled;
Entry({ Entry({
required this.id, required this.id,
@ -20,6 +22,8 @@ class Entry {
required this.date, required this.date,
required this.zekkenNumber, required this.zekkenNumber,
required this.owner, required this.owner,
this.hasParticipated = false,
this.hasGoaled = false,
}); });
factory Entry.fromJson(Map<String, dynamic> json) { factory Entry.fromJson(Map<String, dynamic> json) {

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

@ -1126,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();
} }
@ -1333,12 +1333,6 @@ class DestinationController extends GetxController {
void onInit() async { void onInit() async {
super.onInit(); super.onInit();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
startGPSCheckTimer(); startGPSCheckTimer();
// MapControllerの初期化完了を待機するフラグを設定 // MapControllerの初期化完了を待機するフラグを設定
@ -1645,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) {
@ -1665,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) {
@ -1677,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 ----");
@ -1743,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);
} //}
} }
// 接続状態が変更されたときに呼び出される関数です。 // 接続状態が変更されたときに呼び出される関数です。
@ -1920,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

@ -27,6 +27,8 @@ class EntryController extends GetxController {
final currentEntry = Rx<Entry?>(null); final currentEntry = Rx<Entry?>(null);
final isLoading = true.obs; final isLoading = true.obs;
final activeEvents = <Event>[].obs; //有効なイベントリスト
@override @override
void onInit() async { void onInit() async {
super.onInit(); super.onInit();
@ -52,14 +54,15 @@ class EntryController extends GetxController {
fetchTeams(), fetchTeams(),
fetchCategories(), fetchCategories(),
]); ]);
updateActiveEvents(); // イベント取得後にアクティブなイベントを更新
if (Get.arguments != null && Get.arguments['entry'] != null) { if (Get.arguments != null && Get.arguments['entry'] != null) {
currentEntry.value = Get.arguments['entry']; currentEntry.value = Get.arguments['entry'];
initializeEditMode(currentEntry.value!); initializeEditMode(currentEntry.value!);
} else { } else {
// 新規作成モードの場合、最初のイベントを選択 // 新規作成モードの場合、最初のイベントを選択
if (events.isNotEmpty) { if (events.isNotEmpty) {
selectedEvent.value = events.first; selectedEvent.value = activeEvents.first;
selectedDate.value = events.first.startDatetime; selectedDate.value = activeEvents.first.startDatetime;
} }
} }
} catch(e) { } catch(e) {
@ -70,6 +73,10 @@ class EntryController extends GetxController {
} }
} }
void updateActiveEvents() {
final now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo'));
activeEvents.assignAll(events.where((event) => event.deadlineDateTime.isAfter(now)));
}
void initializeEditMode(Entry entry) { void initializeEditMode(Entry entry) {
currentEntry.value = entry; currentEntry.value = entry;
@ -143,6 +150,7 @@ class EntryController extends GetxController {
deadlineDateTime: deadlineDateTime, deadlineDateTime: deadlineDateTime,
); );
}).toList()); }).toList());
updateActiveEvents();
} catch (e) { } catch (e) {
print('Error fetching events: $e'); print('Error fetching events: $e');
Get.snackbar('Error', 'Failed to fetch events'); Get.snackbar('Error', 'Failed to fetch events');

View File

@ -39,10 +39,10 @@ class EntryDetailPage extends GetView<EntryController> {
children: [ children: [
_buildDropdown<Event>( _buildDropdown<Event>(
label: 'イベント', label: 'イベント',
items: controller.events, items: controller.activeEvents,
selectedId: controller.selectedEvent.value?.id, selectedId: controller.selectedEvent.value?.id,
onChanged: (eventId) => controller.updateEvent( onChanged: (eventId) => controller.updateEvent(
controller.events.firstWhere((e) => e.id == eventId) controller.activeEvents.firstWhere((e) => e.id == eventId)
), ),
getDisplayName: (event) => event.eventName, getDisplayName: (event) => event.eventName,
getId: (event) => event.id, getId: (event) => event.id,

View File

@ -7,6 +7,8 @@ import 'package:gifunavi/pages/entry/entry_controller.dart';
import 'package:gifunavi/routes/app_pages.dart'; import 'package:gifunavi/routes/app_pages.dart';
import 'package:timezone/timezone.dart' as tz; import 'package:timezone/timezone.dart' as tz;
import '../../model/entry.dart';
class EntryListPage extends GetView<EntryController> { class EntryListPage extends GetView<EntryController> {
const EntryListPage({super.key}); const EntryListPage({super.key});
@ -28,11 +30,35 @@ class EntryListPage extends GetView<EntryController> {
child: Text('表示するエントリーがありません。'), child: Text('表示するエントリーがありません。'),
); );
} }
final sortedEntries = controller.entries.toList()
..sort((b, a) => a.date!.compareTo(b.date!));
return ListView.builder( return ListView.builder(
itemCount: controller.entries.length, itemCount: sortedEntries.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final entry = controller.entries[index]; final entry = sortedEntries[index];
final now = DateTime.now();
final isEntryInFuture = _compareDatesOnly(entry.date!, now) >= 0;
//final isEntryInFuture = entry.date!.isAfter(now) || entry.date!.isAtSameMomentAs(now);
Widget? leadingIcon;
if (!isEntryInFuture) {
if (entry.hasParticipated) {
if (entry.hasGoaled) {
leadingIcon =
const Icon(Icons.check_circle, color: Colors.green);
} else {
leadingIcon = const Icon(Icons.warning, color: Colors.yellow);
}
} else {
leadingIcon = const Icon(Icons.cancel, color: Colors.red);
}
}
return ListTile( return ListTile(
leading: leadingIcon,
title: Row( title: Row(
children: [ children: [
Expanded( Expanded(
@ -49,9 +75,16 @@ class EntryListPage extends GetView<EntryController> {
Text('ゼッケン: ${entry.zekkenNumber ?? "未設定"}'), Text('ゼッケン: ${entry.zekkenNumber ?? "未設定"}'),
], ],
), ),
onTap: () => onTap: () {
if (isEntryInFuture) {
Get.toNamed(AppPages.ENTRY_DETAIL, Get.toNamed(AppPages.ENTRY_DETAIL,
arguments: {'mode': 'edit', 'entry': entry}), arguments: {'mode': 'edit', 'entry': entry});
} else if (entry.hasParticipated) {
Get.toNamed(AppPages.EVENT_RESULT, arguments: {'entry': entry});
} else {
_showNonParticipationDialog(context, entry);
}
}
); );
}, },
); );
@ -59,6 +92,11 @@ class EntryListPage extends GetView<EntryController> {
); );
} }
// 新しく追加するメソッド
int _compareDatesOnly(DateTime a, DateTime b) {
return DateTime(a.year, a.month, a.day).compareTo(DateTime(b.year, b.month, b.day));
}
String _formatDate(DateTime? date) { String _formatDate(DateTime? date) {
if (date == null) { if (date == null) {
return '日時未設定'; return '日時未設定';
@ -66,6 +104,26 @@ class EntryListPage extends GetView<EntryController> {
final jstDate = tz.TZDateTime.from(date, tz.getLocation('Asia/Tokyo')); final jstDate = tz.TZDateTime.from(date, tz.getLocation('Asia/Tokyo'));
return DateFormat('yyyy-MM-dd').format(jstDate); return DateFormat('yyyy-MM-dd').format(jstDate);
} }
void _showNonParticipationDialog(BuildContext context, Entry entry) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(entry.event.eventName),
content: Text('${_formatDate(entry.date)}\n\n不参加でした'),
actions: <Widget>[
TextButton(
child: const Text('閉じる'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
} }
class EntryListPage_old extends GetView<EntryController> { class EntryListPage_old extends GetView<EntryController> {
@ -85,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

@ -0,0 +1,155 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:gifunavi/model/entry.dart';
import 'package:gifunavi/pages/gps/gps_controller.dart';
import 'package:gifunavi/pages/history/history_controller.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:gifunavi/pages/gps/gps_controller.dart';
import 'package:gifunavi/pages/history/history_controller.dart';
import 'package:intl/intl.dart';
class EventResultPage extends StatefulWidget {
final Entry entry;
const EventResultPage({super.key, required this.entry});
@override
State<EventResultPage> createState() => _EventResultPageState();
}
class _EventResultPageState extends State<EventResultPage> {
late GpsController gpsController;
late HistoryController historyController;
@override
void initState() {
super.initState();
gpsController = Get.put(GpsController());
historyController = Get.put(HistoryController());
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text('${widget.entry.event.eventName} 結果'),
bottom: const TabBar(
tabs: [
Tab(text: 'ランキング'),
Tab(text: '走行経路'),
Tab(text: 'チェックポイント'),
],
),
),
body: TabBarView(
children: [
_buildFutureTab(), //_buildRankingTab(),
_buildFutureTab(), //_buildRouteTab(),
_buildFutureTab(), //_buildCheckpointTab(),
],
),
),
);
}
Widget _buildFutureTab() {
// ランキングの表示ロジックを実装
return const Center(child: Text('近日公開予定当面、HPを参照ください。'));
}
Widget _buildRankingTab() {
// ランキングの表示ロジックを実装
return const Center(child: Text('ランキング表示(実装が必要)'));
}
Widget _buildRouteTab() {
return Obx(() {
if (gpsController.gpsData.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
return FlutterMap(
options: MapOptions(
center: LatLng(gpsController.gpsData[0].lat, gpsController.gpsData[0].lon),
zoom: 13.0,
),
children: [
TileLayer(
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c'],
),
PolylineLayer(
polylines: [
Polyline(
points: gpsController.gpsData
.map((data) => LatLng(data.lat, data.lon))
.toList(),
color: Colors.red,
strokeWidth: 3.0,
),
],
),
MarkerLayer(
markers: gpsController.gpsData
.map((data) => Marker(
width: 80.0,
height: 80.0,
point: LatLng(data.lat, data.lon),
child: const Icon(Icons.location_on, color: Colors.red),
))
.toList(),
),
],
);
});
}
Widget _buildCheckpointTab() {
return Obx(() {
if (historyController.checkpoints.isEmpty) {
return const Center(child: Text('チェックポイント履歴がありません'));
}
return ListView.builder(
itemCount: historyController.checkpoints.length,
itemBuilder: (context, index) {
final checkpoint = historyController.checkpoints[index];
return ListTile(
title: Text('CP ${checkpoint.cp_number ?? 'Unknown'}'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('通過時刻: ${_formatDateTime(checkpoint.checkintime)}'),
Text('チーム: ${checkpoint.team_name ?? 'Unknown'}'),
Text('イベント: ${checkpoint.event_code ?? 'Unknown'}'),
],
),
leading: checkpoint.image != null
? Image.file(
File(checkpoint.image!),
width: 50,
height: 50,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
print('Error loading image: $error');
return const Icon(Icons.error);
},
)
: const Icon(Icons.image_not_supported),
);
}
);
});
}
}
String _formatDateTime(int? microsecondsSinceEpoch) {
if (microsecondsSinceEpoch == null) return 'Unknown';
final dateTime = DateTime.fromMicrosecondsSinceEpoch(microsecondsSinceEpoch);
return DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime);
}

View File

@ -0,0 +1,22 @@
import 'package:get/get.dart';
import 'package:gifunavi/model/gps_data.dart';
import 'package:gifunavi/utils/database_gps.dart';
import 'package:gifunavi/pages/index/index_controller.dart';
class GpsController extends GetxController {
final gpsData = <GpsData>[].obs;
@override
void onInit() {
super.onInit();
loadGpsData();
}
void loadGpsData() async {
final teamName = Get.find<IndexController>().currentUser[0]["user"]['team_name'];
final eventCode = Get.find<IndexController>().currentUser[0]["user"]["event_code"];
GpsDatabaseHelper db = GpsDatabaseHelper.instance;
var data = await db.getGPSData(teamName, eventCode);
gpsData.value = data;
}
}

View File

@ -0,0 +1,22 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:gifunavi/model/rog.dart';
import 'package:gifunavi/utils/database_helper.dart';
class HistoryController extends GetxController {
final checkpoints = <Rog>[].obs; // Rog オブジェクトのリストに変更
@override
void onInit() {
super.onInit();
loadCheckpoints();
}
void loadCheckpoints() async {
DatabaseHelper db = DatabaseHelper.instance;
var data = await db.allRogianing();
checkpoints.value = data;
}
}

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;
@ -242,13 +286,14 @@ class IndexController extends GetxController with WidgetsBindingObserver {
void onInit() { void onInit() {
try { try {
super.onInit(); 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),
); );
@ -1011,12 +1076,15 @@ class IndexController extends GetxController with WidgetsBindingObserver {
locations.add(value); locations.add(value);
} }
*/ */
} catch ( e) { } catch (e) {
print("Error in loadLocationsBound: $e"); print("Error in loadLocationsBound: $e");
// エラーが発生した場合のリトライ処理や適切なエラーメッセージの表示を行う // エラーが発生した場合のリトライ処理や適切なエラーメッセージの表示を行う
// 例えば、一定時間後に再度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 {
@ -171,10 +246,10 @@ class PermissionController {
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>(

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

@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_navigation/src/routes/get_route.dart'; import 'package:get/get_navigation/src/routes/get_route.dart';
import 'package:gifunavi/pages/changepassword/change_password_page.dart'; import 'package:gifunavi/pages/changepassword/change_password_page.dart';
@ -34,6 +35,10 @@ import 'package:gifunavi/pages/entry/event_entries_page.dart';
import 'package:gifunavi/pages/entry/event_entries_binding.dart'; import 'package:gifunavi/pages/entry/event_entries_binding.dart';
import 'package:gifunavi/pages/register/user_detail_page.dart'; import 'package:gifunavi/pages/register/user_detail_page.dart';
import 'package:gifunavi/pages/entry/event_result_page.dart';
import '../model/entry.dart';
part 'app_routes.dart'; part 'app_routes.dart';
class AppPages { class AppPages {
@ -70,6 +75,7 @@ class AppPages {
static const EVENT_ENTRY = Routes.EVENT_ENTRIES; static const EVENT_ENTRY = Routes.EVENT_ENTRIES;
static const USER_DETAILS_EDIT = Routes.USER_DETAILS_EDIT; static const USER_DETAILS_EDIT = Routes.USER_DETAILS_EDIT;
static const EVENT_RESULT = Routes.EVENT_RESULT;
static final routes = [ static final routes = [
GetPage( GetPage(
@ -175,6 +181,23 @@ class AppPages {
name: Routes.USER_DETAILS_EDIT, name: Routes.USER_DETAILS_EDIT,
page: () => const UserDetailsEditPage(), page: () => const UserDetailsEditPage(),
), ),
GetPage(
name: Routes.EVENT_RESULT,
page: () {
final args = Get.arguments;
if (args is Map<String, dynamic> && args.containsKey('entry')) {
return EventResultPage(entry: args['entry'] as Entry);
} else {
// エントリーが提供されていない場合のフォールバック
// 例: エラーページを表示するか、ホームページにリダイレクトする
return const Scaffold(
body: Center(
child: Text('エラー: イベント結果を表示できません。'),
),
);
}
},
),
]; ];
} }

View File

@ -38,4 +38,5 @@ abstract class Routes {
static const EVENT_ENTRIES = '/event-entries'; static const EVENT_ENTRIES = '/event-entries';
static const USER_DETAILS_EDIT = '/user-details-edit'; static const USER_DETAILS_EDIT = '/user-details-edit';
static const EVENT_RESULT = '/event-result';
} }

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 = "";
} }
@ -87,7 +87,7 @@ class ApiService extends GetxService{
Future<dynamic> _handleRequest(Future<http.Response> Function() request) async { Future<dynamic> _handleRequest(Future<http.Response> Function() request) async {
try { try {
final response = await request(); final response = await request();
if (response.statusCode == 200) { if (response.statusCode == 200 || response.statusCode == 201 ) {
return json.decode(utf8.decode(response.bodyBytes)); return json.decode(utf8.decode(response.bodyBytes));
} else if (response.statusCode == 401) { } else if (response.statusCode == 401) {
await _handleUnauthorized(); await _handleUnauthorized();
@ -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;

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 {
@ -248,7 +256,7 @@ class LocationController extends GetxController {
return; return;
} }
await PermissionController.checkAndRequestPermissions(); //await PermissionController.checkAndRequestPermissions();
// 位置情報の設定を行います。z11 // 位置情報の設定を行います。z11
// Set up the location options // Set up the location options

View File

@ -1,383 +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([
@ -385,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(
@ -415,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: [
@ -435,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