2024-09-02 ほぼOK

This commit is contained in:
2024-09-02 21:25:19 +09:00
parent dc58dc0584
commit fe46d46ab6
59 changed files with 2006 additions and 677 deletions

View File

@ -38,8 +38,10 @@ import 'pages/permission/permission.dart';
import 'package:gifunavi/services/api_service.dart';
import 'package:gifunavi/provider/cached_tile_provider.dart';
import 'package:gifunavi/pages/entry/entry_controller.dart';
import 'package:gifunavi/pages/team/team_controller.dart';
//import 'package:gifunavi/pages/entry/entry_controller.dart';
//import 'package:gifunavi/pages/team/team_controller.dart';
import 'package:timezone/timezone.dart' as tz;
Map<String, dynamic> deviceInfo = {};
@ -56,14 +58,25 @@ void saveGameState() async {
*/
// 現在のユーザーのIDも一緒に保存するようにします。
void saveGameState() async {
Future<void> saveGameState() async {
DestinationController destinationController =
Get.find<DestinationController>();
IndexController indexController = Get.find<IndexController>();
SharedPreferences pref = await SharedPreferences.getInstance();
debugPrint("indexController.currentUser = ${indexController.currentUser}");
debugPrint("ゲームステータス保存  = ${indexController.currentUser}");
if(indexController.currentUser.isNotEmpty) {
pref.setInt("user_id", indexController.currentUser[0]["user"]["id"]);
if(indexController.currentUser[0]["user"]["event_date"]!=null) {
final date = indexController.currentUser[0]["user"]["event_date"];
pref.setString('eventDate', date.toIso8601String());
debugPrint("Saved date is ${date} => ${date.toIso8601String()}");
pref.setString('eventCode', indexController.currentUser[0]["user"]["event_code"]);
pref.setString('teamName', indexController.currentUser[0]["user"]["team_name"]);
pref.setString('group', indexController.currentUser[0]["user"]["group"]);
//final zekken = indexController.currentUser[0]["user"]["zekken_number"];
pref.setInt('zekkenNumber', indexController.currentUser[0]["user"]["zekken_number"]);
}
}else{
debugPrint("User is empty....");
}
@ -73,75 +86,118 @@ void saveGameState() async {
pref.setBool("ready_for_goal", DestinationController.ready_for_goal);
}
/*
void restoreGame() async {
SharedPreferences pref = await SharedPreferences.getInstance();
DestinationController destinationController =
Get.find<DestinationController>();
destinationController.skipGps = false;
destinationController.isInRog.value = pref.getBool("is_in_rog") ?? false;
destinationController.rogainingCounted.value =
pref.getBool("rogaining_counted") ?? false;
DestinationController.ready_for_goal =
pref.getBool("ready_for_goal") ?? false;
//print(
// "--restored -- destinationController.isInRog.value ${pref.getBool("is_in_rog")} -- ${pref.getBool("rogaining_counted")}");
}
*/
void restoreGame() async {
// _indexController.currentUser[0]["user"]["event_date"] = entryDate; // 追加2024-8-9
// _indexController.currentUser[0]["user"]["event_code"] = entry.event.eventName;
// _indexController.currentUser[0]["user"]["team_name"] = entry.team.teamName;
// _indexController.currentUser[0]["user"]["group"] = entry.team.category.categoryName;
// _indexController.currentUser[0]["user"]["zekken_number"] = entry.zekkenNumber;
Future<void> restoreGame() async {
SharedPreferences pref = await SharedPreferences.getInstance();
IndexController indexController = Get.find<IndexController>();
int? savedUserId = pref.getInt("user_id");
//int? currUserId = indexController.currentUser[0]['user']['id'];
//debugPrint("savedUserId=${savedUserId}, currentUser=${currUserId}");
debugPrint("ゲームステータス再現  savedUserId=${savedUserId}");
if (indexController.currentUser.isNotEmpty &&
indexController.currentUser[0]["user"]["id"] == savedUserId) {
DestinationController destinationController =
Get.find<DestinationController>();
final dateString = pref.getString('eventDate');
if (dateString != null) {
final parsedDate = DateTime.parse(dateString);
final jstDate = tz.TZDateTime.from(parsedDate, tz.getLocation('Asia/Tokyo'));
debugPrint("restore date is ${dateString} => ${jstDate}");
indexController.currentUser[0]["user"]["event_date"] = jstDate;
//indexController.currentUser[0]["user"]["event_date"] = DateTime.parse(dateString);
}
//debugPrint("restore date is ${dateString?} => ${DateTime.parse(dateString)}");
indexController.currentUser[0]["user"]["event_code"] = pref.getString('eventCode');
indexController.currentUser[0]["user"]["team_name"] = pref.getString('teamName');
indexController.currentUser[0]["user"]["group"] = pref.getString('group');
indexController.currentUser[0]["user"]["zekken_number"] = pref.getInt('zekkenNumber');
debugPrint("user = ${indexController.currentUser[0]["user"]}");
DestinationController destinationController = Get.find<DestinationController>();
destinationController.skipGps = false;
destinationController.isInRog.value = pref.getBool("is_in_rog") ?? false;
destinationController.rogainingCounted.value =
pref.getBool("rogaining_counted") ?? false;
DestinationController.ready_for_goal =
pref.getBool("ready_for_goal") ?? false;
await Get.putAsync(() => ApiService().init());
destinationController.rogainingCounted.value = pref.getBool("rogaining_counted") ?? false;
DestinationController.ready_for_goal = pref.getBool("ready_for_goal") ?? false;
//await Get.putAsync(() => ApiService().init());
if (indexController.currentUser[0]["user"]["event_code"] != null) {
indexController.setSelectedEventName(indexController.currentUser[0]["user"]["event_code"]);
} else {
indexController.setSelectedEventName('未参加');
}
}else{
indexController.setSelectedEventName('未参加');
}
}
/*
void restoreGame_new() async {
SharedPreferences pref = await SharedPreferences.getInstance();
IndexController indexController = Get.find<IndexController>();
DestinationController destinationController = Get.find<DestinationController>();
destinationController.skipGps = false;
destinationController.isInRog.value = pref.getBool("is_in_rog") ?? false;
destinationController.rogainingCounted.value = pref.getBool("rogaining_counted") ?? false;
DestinationController.ready_for_goal = pref.getBool("ready_for_goal") ?? false;
int? savedUserId = pref.getInt("user_id");
if (indexController.currentUser.isNotEmpty && indexController.currentUser[0]["user"]["id"] == savedUserId) {
final dateString = pref.getString('eventDate');
if (dateString != null) {
indexController.currentUser[0]["user"]["event_date"] = DateTime.parse(dateString);
}
indexController.currentUser[0]["user"]["event_code"] = pref.getString('eventCode');
indexController.currentUser[0]["user"]["team_name"] = pref.getString('teamName');
indexController.currentUser[0]["user"]["group"] = pref.getString('group');
indexController.currentUser[0]["user"]["zekken_number"] = pref.getInt('zekkenNumber');
if (indexController.currentUser[0]["user"]["event_code"] != null) {
indexController.setSelectedEventName(indexController.currentUser[0]["user"]["event_code"]);
} else {
indexController.setSelectedEventName('未参加');
_showEventSelectionWarning();
Get.toNamed(AppPages.EVENT_ENTRY);
}
} else {
indexController.setSelectedEventName('未参加');
_showEventSelectionWarning();
Get.toNamed(AppPages.EVENT_ENTRY);
}
await Get.putAsync(() => ApiService().init());
}
*/
void _showEventSelectionWarning() {
Get.dialog(
AlertDialog(
title: Text('警告'),
content: Text('イベントを選択してください。'),
actions: [
TextButton(
child: Text('OK'),
onPressed: () => Get.back(),
),
],
),
);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Get.put(LocationController());
/*
Get.put(ApiService());
Get.put(TeamController());
Get.put(EntryController());
Get.put(IndexController());
*/
/*
await FlutterMapTileCaching.initialise();
final StoreDirectory instanceA = FMTC.instance('OpenStreetMap (A)');
await instanceA.manage.createAsync();
await instanceA.metadata.addAsync(
key: 'sourceURL',
value: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
);
await instanceA.metadata.addAsync(
key: 'validDuration',
value: '14',
);
await instanceA.metadata.addAsync(
key: 'behaviour',
value: 'cacheFirst',
);
*/
// 新しいキャッシュプロバイダーの初期化
await CacheProvider.initialize();
// 使用不可
//deviceInfo = await DeviceInfoService.getDeviceInfo();
final IndexController _indexController;
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
@ -150,67 +206,98 @@ void main() async {
ErrorService.reportError(details.exception, details.stack ?? StackTrace.current, deviceInfo, LogManager().operationLogs);
};
//Get.put(LocationController());
//await PermissionController.checkAndRequestPermissions();
//requestLocationPermission();
// startMemoryMonitoring(); // 2024-4-8 Akira: メモリ使用量のチェックを開始 See #2810
Get.put(SettingsController()); // これを追加
/*
runZonedGuarded(() {
runApp(const ProviderScope(child: MyApp()));
}, (error, stackTrace) {
ErrorService.reportError(error, stackTrace, deviceInfo);
});
*/
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
debugPrint('Flutter error: ${details.exception}');
debugPrint('Stack trace: ${details.stack}');
};
try {
tz.initializeTimeZones();
// ApiServiceを初期化
//await Get.putAsync(() => ApiService().init());
await _initApiService();
debugPrint("1: start ApiService");
// すべてのコントローラーとサービスを非同期で初期化
Get.lazyPut(() => IndexController(apiService: Get.find<ApiService>()));
debugPrint("2: start IndexController");
// その他のコントローラーを遅延初期化
Get.lazyPut(() => SettingsController());
debugPrint("2: start SettingsController");
Get.lazyPut(() => DestinationController());
debugPrint("3: start DestinationController");
await initServices();
runApp(const ProviderScope(child: MyApp()));
//runApp(HomePage()); // MyApp()からHomePage()に変更
//runApp(const MyApp());
}catch(e, stackTrace){
print('Error during initialization: $e');
print('Stack trace: $stackTrace');
// エラーが発生した場合、エラー画面を表示
runApp(ErrorApp(error: e.toString()));
}
}
Future<void> initServices() async {
print('Starting services ...');
try {
await Get.putAsync(() => ApiService().init());
print('All services started...');
// 非同期処理を並列実行
await Future.wait([
_initTimeZone(),
_initCacheProvider(),
]);
print('=== 5. Initialized TimeZone...');
print('=== 6. CacheProvider started...');
Get.put(PermissionController());
await _checkPermissions();
debugPrint("7: start PermissionController");
}catch(e){
print('Error initializing ApiService: $e');
print('Error initializing : $e');
}
try {
Get.put(SettingsController());
print('SettingsController initialized successfully');
} catch (e) {
print('Error initializing SettingsController: $e');
}
print('All services started...');
}
Future<void> _initLocationController() async {
if (!Get.isRegistered<LocationController>()) {
Get.put(LocationController());
}
print('=== 1. LocationController started...');
}
Future<void> _initTimeZone() async {
tz.initializeTimeZones();
}
Future<void> _initCacheProvider() async {
await CacheProvider.initialize();
}
Future<void> _checkPermissions() async {
await PermissionController.checkAndRequestPermissions();
}
Future<void> _initApiService() async {
await Get.putAsync(() => ApiService().init());
//Get.lazyPut(() => ApiService());
}
class ErrorApp extends StatelessWidget {
final String error;
const ErrorApp({super.key, required this.error});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Text('アプリの起動中にエラーが発生しました: $error'),
),
),
);
}
}
Future<void> requestLocationPermission() async {
try {
final status = await Permission.locationAlways.request();
@ -300,70 +387,80 @@ String team_name="";
String event_code="";
Future<void> startBackgroundTracking() async {
if (Platform.isIOS && background==false) {
final IndexController indexController = Get.find<IndexController>();
if(indexController.currentUser.isNotEmpty) {
team_name = indexController.currentUser[0]["user"]['team_name'];
event_code = indexController.currentUser[0]["user"]["event_code"];
}
try {
if (Platform.isIOS && background == false) {
final IndexController indexController = Get.find<IndexController>();
if (indexController.currentUser.isNotEmpty &&
indexController.currentUser[0]["user"]['team_name'] != null) {
team_name = indexController.currentUser[0]["user"]['team_name'];
event_code = indexController.currentUser[0]["user"]["event_code"];
}
background = true;
debugPrint("バックグラウンド処理を開始しました。");
const LocationSettings locationSettings = LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 100,
);
debugPrint("バックグラウンド処理を開始しました。");
const LocationSettings locationSettings = LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 100,
);
try {
positionStream = Geolocator.getPositionStream(locationSettings: locationSettings)
.listen((Position? position) async {
if (position != null) {
final lat = position.latitude;
final lng = position.longitude;
//final timestamp = DateTime.now();
final accuracy = position.accuracy;
try {
positionStream =
Geolocator.getPositionStream(locationSettings: locationSettings)
.listen((Position? position) async {
if (position != null) {
final lat = position.latitude;
final lng = position.longitude;
//final timestamp = DateTime.now();
final accuracy = position.accuracy;
// GPS信号強度がlowの場合はスキップ
if (accuracy > 100) {
debugPrint("GPS signal strength is low. Skipping data saving.");
return;
}
// GPS信号強度がlowの場合はスキップ
if (accuracy > 100) {
debugPrint(
"GPS signal strength is low. Skipping data saving.");
return;
}
Duration difference = lastGPSCollectedTime.difference(DateTime.now())
.abs();
// 最後にGPS信号を取得した時刻から10秒以上経過、かつ10m以上経過普通に歩くスピード
//debugPrint("時間差:${difference}");
if (difference.inSeconds >= 10 ) {
debugPrint("バックグラウンドでのGPS取得時の処理(10secおき) count=${difference.inSeconds}, time=${DateTime.now()}");
Duration difference = lastGPSCollectedTime.difference(
DateTime.now())
.abs();
// 最後にGPS信号を取得した時刻から10秒以上経過、かつ10m以上経過普通に歩くスピード
//debugPrint("時間差:${difference}");
if (difference.inSeconds >= 10) {
debugPrint(
"バックグラウンドでのGPS取得時の処理(10secおき) count=${difference
.inSeconds}, time=${DateTime.now()}");
// DBにGPSデータを保存 pages/destination/destination_controller.dart
await addGPStoDB(lat, lng);
// DBにGPSデータを保存 pages/destination/destination_controller.dart
await addGPStoDB(lat, lng);
lastGPSCollectedTime = DateTime.now();
}
}
}, onError: (error) {
if (error is LocationServiceDisabledException) {
print('Location services are disabled');
} else if (error is PermissionDeniedException) {
print('Location permissions are denied');
} else {
print('Location Error: $error');
}
});
} catch (e) {
print('Error starting background tracking: $e');
}
}else if (Platform.isAndroid && background == false) {
background = true;
debugPrint("バックグラウンド処理を開始しました。");
try {
// 位置情報の権限が許可されているかを確認
await PermissionController.checkAndRequestPermissions();
}catch(e){
print('Error starting background tracking: $e');
lastGPSCollectedTime = DateTime.now();
}
}
}, onError: (error) {
if (error is LocationServiceDisabledException) {
print('Location services are disabled');
} else if (error is PermissionDeniedException) {
print('Location permissions are denied');
} else {
print('Location Error: $error');
}
});
} catch (e) {
print('Error starting background tracking: $e');
}
} else if (Platform.isAndroid && background == false) {
background = true;
debugPrint("バックグラウンド処理を開始しました。");
try {
// 位置情報の権限が許可されているかを確認
await PermissionController.checkAndRequestPermissions();
} catch (e) {
print('Error starting background tracking: $e');
}
}
} catch (e) {
print('Error starting background tracking: $e');
// 再試行するか、エラーを適切に処理
}
}
@ -388,20 +485,24 @@ Future<void> addGPStoDB(double la, double ln) async {
}
Future<void> stopBackgroundTracking() async {
if (Platform.isIOS && background==true) {
background=false;
debugPrint("バックグラウンド処理:停止しました。");
await positionStream?.cancel();
positionStream = null;
}else if(Platform.isAndroid && background==true){
background=false;
debugPrint("バックグラウンド処理:停止しました。");
const platform = MethodChannel('location');
try {
await platform.invokeMethod('stopLocationService');
} on PlatformException catch (e) {
print("Failed to stop location service: '${e.message}'.");
try {
if (Platform.isIOS && background == true) {
background = false;
debugPrint("バックグラウンド処理:停止しました。");
await positionStream?.cancel();
positionStream = null;
} else if (Platform.isAndroid && background == true) {
background = false;
debugPrint("バックグラウンド処理:停止しました。");
const platform = MethodChannel('location');
try {
await platform.invokeMethod('stopLocationService');
} on PlatformException catch (e) {
print("Failed to stop location service: '${e.message}'.");
}
}
} catch(e){
print('Error stopping background tracking: $e');
}
}
@ -414,18 +515,22 @@ class MyApp extends StatefulWidget {
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
// This widget is the root of your application.
late final LocationController _locationController;
late final IndexController _indexController;
late final DestinationController _destinationController;
late final PermissionController _permissionController;
Timer? _memoryCheckTimer;
@override
void initState() {
super.initState();
if (!Get.isRegistered<LocationController>()) {
Get.put(LocationController());
}
_initializeControllers();
WidgetsBinding.instance.addObserver(this);
//_startMemoryMonitoring();
if (context.mounted) {
restoreGame();
// _restoreGameAndInitialize();
}
WidgetsBinding.instance.addObserver(this);
// ウィジェットが構築された後に権限をチェック
WidgetsBinding.instance.addPostFrameCallback((_) {
@ -435,6 +540,105 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
debugPrint("Start MyAppState...");
}
Future<void> _restoreGameAndInitialize() async {
await restoreGame();
// ここに他の初期化処理を追加できます
}
void _initializeControllers() {
/*
if (!Get.isRegistered<IndexController>()) {
_locationController = Get.put(LocationController(), permanent: true);
}
if (!Get.isRegistered<IndexController>()) {
_indexController = Get.put(IndexController(apiService: Get.find<ApiService>()), permanent: true);
}
if (!Get.isRegistered<DestinationController>()) {
_destinationController =
Get.put(DestinationController(), permanent: true);
}
if (!Get.isRegistered<PermissionController>()) {
_permissionController = Get.put(PermissionController());
}
// 他の必要なコントローラーの初期化
*/
}
void _startMemoryMonitoring() {
/*
_memoryCheckTimer = Timer.periodic(const Duration(seconds: 10), (timer) {
_checkMemoryUsage();
});
*/
}
void _checkMemoryUsage() async {
final memoryInfo = await _getMemoryInfo();
//debugPrint('Current memory usage: ${memoryInfo['used']} MB');
if (memoryInfo['used']! > 100) { // 100MB以上使用している場合
_performMemoryCleanup();
}
}
Future<Map<String, int>> _getMemoryInfo() async {
// プラットフォーム固有のメモリ情報取得ロジックを実装
// この例では仮の値を返しています
return {'total': 1024, 'used': 512};
}
void _performMemoryCleanup() {
/*
debugPrint('Performing memory cleanup');
// キャッシュのクリア
Get.deleteAll(force: false); // 永続的なコントローラーを除外してキャッシュをクリア
imageCache.clear();
imageCache.clearLiveImages();
// 大きなオブジェクトの解放
_clearLargeObjects();
// 未使用のリソースの解放
_releaseUnusedResources();
// ガベージコレクションの促進
_forceGarbageCollection();
debugPrint('Performing memory cleanup');
*/
}
void _clearLargeObjects() {
// 大きなリストやマップをクリア
// 例: myLargeList.clear();
}
void _releaseUnusedResources() {
// 使用していないストリームのクローズ
// 例: myStream?.close();
}
void _forceGarbageCollection() {
/*
Timer(const Duration(seconds: 1), () {
debugPrint('Forcing garbage collection');
// ignore: dead_code
bool didRun = false;
assert(() {
didRun = true;
return true;
}());
if (didRun) {
debugPrint('Garbage collection forced in debug mode');
}
});
*/
}
/*
void showPermissionRequiredDialog() {
showDialog(
@ -474,6 +678,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_memoryCheckTimer?.cancel();
super.dispose();
}
@ -487,110 +692,118 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
try {
LocationController locationController = Get.find<LocationController>();
if (!Get.isRegistered<IndexController>()) {
_indexController = Get.find<IndexController>();
}
if (!Get.isRegistered<LocationController>()) {
_locationController = Get.find<LocationController>();
}
if (!Get.isRegistered<DestinationController>()) {
_destinationController = Get.find<DestinationController>();
}
DestinationController destinationController = Get.find<
DestinationController>();
//DestinationController destinationController =
// Get.find<DestinationController>();
switch (state) {
case AppLifecycleState.resumed:
// 追加 2024.8.13.
await stopBackgroundTracking();
destinationController.restartGPS();
// 追加 2024.8.13.
// バックグラウンド処理を停止
if (Platform.isIOS && destinationController.isRunningBackgroundGPS) {
// Foreground に戻った時の処理
debugPrint(
" ==(Status Changed)==> RESUMED. フォアグラウンドに戻りました");
locationController.resumePositionStream();
//print("RESUMED");
restoreGame();
stopBackgroundTracking();
destinationController.isRunningBackgroundGPS = false;
destinationController.restartGPS();
} else if (Platform.isAndroid) {
if (destinationController.isRunningBackgroundGPS) {
const platform = MethodChannel('location');
platform.invokeMethod('stopLocationService');
destinationController.isRunningBackgroundGPS = false;
destinationController.restartGPS();
debugPrint("stopped android location service..");
}
debugPrint(
"==(Status Changed)==> RESUMED. android フォアグラウンドに戻りました");
locationController.resumePositionStream();
//print("RESUMED");
restoreGame();
} else {
debugPrint("==(Status Changed)==> RESUMED 不明状態");
}
//await _onResumed();
await _onResumed();
break;
case AppLifecycleState.inactive:
// アプリが非アクティブになったときに発生します。
if (Platform.isIOS && !destinationController
.isRunningBackgroundGPS) { // iOSはバックグラウンドでもフロントの処理が生きている。
// これは、別のアプリやシステムのオーバーレイ(着信通話やアラームなど)によって一時的に中断された状態です。
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
//locationController.resumePositionStream();
// 追加: フロントエンドのGPS信号のlistenを停止
locationController.stopPositionStream();
destinationController.isRunningBackgroundGPS = true;
startBackgroundTracking();
} else if (Platform.isAndroid &&
!destinationController.isRunningBackgroundGPS) {
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
} else {
debugPrint("==(Status Changed)==> INACTIVE 不明状態");
}
saveGameState();
await _onInactive();
break;
case AppLifecycleState.paused:
// バックグラウンドに移行したときの処理
//locationController.resumePositionStream();
debugPrint(" ==(Status Changed)==> PAUSED. バックグラウンド処理。");
if (Platform.isIOS && !destinationController.isRunningBackgroundGPS) {
debugPrint(
"iOS already running background GPS processing when it's inactive");
} else if (Platform.isAndroid &&
!destinationController.isRunningBackgroundGPS) {
debugPrint(
" ==(Status Changed)==> PAUSED. Android バックグラウンド処理。");
locationController.stopPositionStream();
const platform = MethodChannel('location');
platform.invokeMethod('startLocationService');
//platform.invokeMethod('stopLocationService');
destinationController.isRunningBackgroundGPS = true;
//startBackgroundTracking();
}
saveGameState();
await _onPaused();
break;
case AppLifecycleState.detached:
// アプリが終了する直前に発生します。この状態では、アプリはメモリから解放される予定です。
//locationController.resumePositionStream();
debugPrint(" ==(Status Changed)==> DETACHED アプリは終了します。");
saveGameState();
await _onDetached();
break;
case AppLifecycleState.hidden:
// Web用の特殊な状態で、モバイルアプリでは発生しません。
//locationController.resumePositionStream();
debugPrint(" ==(Status Changed)==> Hidden アプリが隠れた");
saveGameState();
await _onHidden();
break;
}
}catch(e){
print('Error finding LocationController: $e');
print('Error finding didChangeAppLifecycleState: $e');
_initializeControllers();
}
}
Future<void> _onResumed() async {
debugPrint("==(Status Changed)==> RESUMED");
try {
_initializeControllers();
await stopBackgroundTracking();
_destinationController.restartGPS();
if (Platform.isIOS && _destinationController.isRunningBackgroundGPS) {
_locationController.resumePositionStream();
await restoreGame();
_destinationController.isRunningBackgroundGPS = false;
} else if (Platform.isAndroid) {
if (_destinationController.isRunningBackgroundGPS) {
const platform = MethodChannel('location');
await platform.invokeMethod('stopLocationService');
_destinationController.isRunningBackgroundGPS = false;
_destinationController.restartGPS();
}
_locationController.resumePositionStream();
await restoreGame();
}
} catch(e) {
print('Error in _onResumed: $e');
// 必要に応じて再試行またはエラー処理
}
}
Future<void> _onInactive() async {
debugPrint("==(Status Changed)==> INACTIVE");
if (Platform.isIOS && !_destinationController.isRunningBackgroundGPS) {
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
_locationController.stopPositionStream();
_destinationController.isRunningBackgroundGPS = true;
await startBackgroundTracking();
} else if (Platform.isAndroid && !_destinationController.isRunningBackgroundGPS) {
// Android特有の処理があれば追加
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
}else{
debugPrint("==(Status Changed)==> INACTIVE 不明状態");
}
await saveGameState();
}
Future<void> _onPaused() async {
debugPrint(" ==(Status Changed)==> PAUSED. バックグラウンド処理。");
if (Platform.isAndroid && !_destinationController.isRunningBackgroundGPS) {
debugPrint(" ==(Status Changed)==> PAUSED. Android バックグラウンド処理。");
_locationController.stopPositionStream();
const platform = MethodChannel('location');
await platform.invokeMethod('startLocationService');
_destinationController.isRunningBackgroundGPS = true;
}
await saveGameState();
}
Future<void> _onDetached() async {
debugPrint(" ==(Status Changed)==> DETACHED アプリは終了します。");
await saveGameState();
// アプリ終了時の追加処理
}
Future<void> _onHidden() async {
debugPrint(" ==(Status Changed)==> Hidden アプリが隠れた");
await saveGameState();
}
@override
Widget build(BuildContext context) {

View File

@ -14,8 +14,11 @@ import 'package:gifunavi/services/external_service.dart';
import 'package:gifunavi/utils/const.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:http/http.dart' as http; // この行を追加
import 'package:http/http.dart' as http;
import '../../routes/app_pages.dart'; // この行を追加
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
// 関数 getTagText は、特定の条件に基づいて文字列から特定の部分を抽出し、返却するためのものです。
// 関数は2つのパラメータを受け取り、条件分岐を通じて結果を返します。
@ -220,10 +223,67 @@ class CameraPage extends StatelessWidget {
Timer? timer;
bool isValidEventParticipation() {
final eventCode = indexController.currentUser[0]["user"]["event_code"];
final teamName = indexController.currentUser[0]["user"]["team_name"];
final dateString = indexController.currentUser[0]["user"]["event_date"];
//final parsedDate = DateTime.parse(dateString);
//final eventDate = tz.TZDateTime.from(parsedDate, tz.getLocation('Asia/Tokyo'));
//final today = DateTime.now();
return eventCode != null &&
teamName != null &&
dateString != null ;
// isSameDay(eventDate, today);
}
bool isSameDay(DateTime date1, DateTime date2) {
return date1.year == date2.year &&
date1.month == date2.month &&
date1.day == date2.day;
}
void showEventParticipationWarning(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("警告"),
content: const Text("今日のイベントにまず参加しないと事前チェックインはできません。サブメニューからイベント参加をタップして今日のイベントに参加してください。"),
actions: <Widget>[
TextButton(
child: const Text("キャンセル"),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text("参加する"),
onPressed: () {
Navigator.of(context).pop();
Get.toNamed(AppPages.EVENT_ENTRY);
},
),
],
);
},
);
}
// 現在の状態に基づいて、適切なアクションボタンを返します。
// 要修正:エラーハンドリングが不十分です。例外が発生した場合の処理を追加することをお勧めします。
//
Widget getAction(BuildContext context) {
if (!isValidEventParticipation()) {
return ElevatedButton(
onPressed: () => showEventParticipationWarning(context),
child: const Text("チェックイン"),
);
}
if (manulaCheckin == true) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
@ -253,7 +313,9 @@ class CameraPage extends StatelessWidget {
onPressed: () async {
await destinationController.makeCheckin(destination, true,
destinationController.photos[0].path);
destinationController.rogainingCounted.value = true;
if( destinationController.isInRog.value==true ) {
destinationController.rogainingCounted.value = true; // ロゲ開始後のみ許可
}
destinationController.skipGps = false;
destinationController.isPhotoShoot.value = false;
@ -387,7 +449,9 @@ class CameraPage extends StatelessWidget {
await destinationController.makeBuyPoint(
destination, destinationController.photos[0].path);
Get.back();
destinationController.rogainingCounted.value = true;
if( destinationController.isInRog.value==true ) {
destinationController.rogainingCounted.value = true; // ロゲ開始後のみ許可
}
destinationController.skipGps = false;
destinationController.isPhotoShoot.value = false;
Get.snackbar("お買い物加点を行いました。",
@ -428,7 +492,9 @@ class CameraPage extends StatelessWidget {
await destinationController.makeBuyPoint(
destination, destinationController.photos[0].path);
Get.back();
destinationController.rogainingCounted.value = true;
if( destinationController.isInRog.value==true ) {
destinationController.rogainingCounted.value = true; //ロゲ開始後のみ許可
}
destinationController.skipGps = false;
destinationController.isPhotoShoot.value = false;
Get.snackbar("お買い物加点を行いました。",
@ -465,7 +531,9 @@ class CameraPage extends StatelessWidget {
true,
destinationController.photos[0].path);
//Get.back();
destinationController.rogainingCounted.value = true;
if( destinationController.isInRog.value==true ) {
destinationController.rogainingCounted.value = true; //ロゲ開始後のみ許可
}
destinationController.skipGps = false;
destinationController.isPhotoShoot.value = false;
@ -496,6 +564,7 @@ class CameraPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//print("---- photos ${destination.photos} ----");
if (buyPointPhoto == true) {
// buyPointPhotoがtrueの場合は、BuyPointCameraウィジェットを返します。
@ -780,7 +849,9 @@ class BuyPointCamera extends StatelessWidget {
onPressed: () async {
await destinationController.cancelBuyPoint(destination);
Navigator.of(Get.context!).pop();
destinationController.rogainingCounted.value = true;
if( destinationController.isInRog.value==true ) {
destinationController.rogainingCounted.value = true; // ロゲ開始後のみ許可
}
destinationController.skipGps = false;
destinationController.isPhotoShoot.value = false;
},

View File

@ -1,6 +1,8 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:gifunavi/model/destination.dart';
@ -8,8 +10,17 @@ import 'package:gifunavi/model/destination.dart';
class CustomCameraView extends StatefulWidget {
final Function(String) onImageCaptured;
final Destination? destination;
final Function(bool) onCameraStatusChanged;
//const CustomCameraView({super.key, required this.onImageCaptured, required this.destination});
const CustomCameraView({
Key? key,
required this.onImageCaptured,
required this.destination,
required this.onCameraStatusChanged, // 新しいコールバック
}) : super(key: key);
const CustomCameraView({super.key, required this.onImageCaptured, required this.destination});
@override
_CustomCameraViewState createState() => _CustomCameraViewState();
@ -17,6 +28,8 @@ class CustomCameraView extends StatefulWidget {
class _CustomCameraViewState extends State<CustomCameraView> {
CameraController? _controller;
bool _isCameraAvailable = true;
late List<CameraDescription> _cameras;
int _selectedCameraIndex = 0;
double _currentScale = 1.0;
@ -31,10 +44,25 @@ class _CustomCameraViewState extends State<CustomCameraView> {
}
Future<void> _initializeCamera() async {
_cameras = await availableCameras();
_controller = CameraController(_cameras[_selectedCameraIndex], ResolutionPreset.medium);
await _controller!.initialize();
setState(() {});
try {
_cameras = await availableCameras();
if (_cameras.isNotEmpty) {
_controller = CameraController(
_cameras[_selectedCameraIndex], ResolutionPreset.medium);
await _controller!.initialize();
setState(() {
_isCameraAvailable = true;
});
} else {
throw Exception('Camera is not available');
}
}catch(err){
print("Error initializing camera: $err");
setState(() {
_isCameraAvailable = false;
});
}
widget.onCameraStatusChanged(_isCameraAvailable);
}
@override
@ -82,20 +110,46 @@ class _CustomCameraViewState extends State<CustomCameraView> {
}
void _captureImage() async {
if (_controller!.value.isInitialized) {
final Directory appDirectory = await getApplicationDocumentsDirectory();
final String imagePath = path.join(appDirectory.path, '${DateTime.now()}.jpg');
if (_isCameraAvailable) {
if (_controller!.value.isInitialized) {
final Directory appDirectory = await getApplicationDocumentsDirectory();
final String imagePath = path.join(
appDirectory.path, '${DateTime.now()}.jpg');
final XFile imageFile = await _controller!.takePicture();
await imageFile.saveTo(imagePath);
final XFile imageFile = await _controller!.takePicture();
await imageFile.saveTo(imagePath);
widget.onImageCaptured(imagePath);
Navigator.pop(context);
}
}else{
// ダミー画像を使用
final String imagePath = await _saveDummyImage();
widget.onImageCaptured(imagePath);
Navigator.pop(context);
}
}
Future<String> _saveDummyImage() async {
final Directory appDirectory = await getApplicationDocumentsDirectory();
final String imagePath = path.join(appDirectory.path, 'dummy_${DateTime.now()}.png');
// アセットからダミー画像を読み込む
ByteData data = await rootBundle.load('assets/images/dummy_camera_image.png');
List<int> bytes = data.buffer.asUint8List();
// ダミー画像をファイルとして保存
await File(imagePath).writeAsBytes(bytes);
return imagePath;
}
@override
Widget build(BuildContext context) {
if (!_isCameraAvailable) {
return _buildDummyCameraView();
}
if (_controller == null || !_controller!.value.isInitialized) {
return Container();
}
@ -182,4 +236,64 @@ class _CustomCameraViewState extends State<CustomCameraView> {
],
);
}
Widget _buildDummyCameraView() {
return Stack(
children: [
Container(
color: Colors.black,
child: const Center(
child: Text(
'カメラを利用できません',
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
),
Positioned(
bottom: 16.0,
left: 16.0,
right: 16.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.flash_off, color: Colors.white),
iconSize: 32,
),
GestureDetector(
onTap: _captureEmulatedImage,
child: Container(
height: 80,
width: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
border: Border.all(color: Colors.red, width: 4),
),
child: const Icon(Icons.camera_alt, color: Colors.red, size: 40),
),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.flip_camera_ios, color: Colors.white),
iconSize: 32,
),
],
),
),
],
);
}
void _captureEmulatedImage() async {
final Directory appDirectory = await getApplicationDocumentsDirectory();
final String imagePath = path.join(appDirectory.path, '${DateTime.now()}.jpg');
// ダミーの画像ファイルを作成
await File(imagePath).writeAsBytes(Uint8List(0));
widget.onImageCaptured(imagePath);
Navigator.pop(context);
}
}

View File

@ -29,12 +29,18 @@ import 'dart:async';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:gifunavi/widgets/debug_widget.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:gifunavi/pages/permission/permission.dart' ;
// 新しいインポート
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
// 目的地に関連する状態管理とロジックを担当するクラスです。
//
class DestinationController extends GetxController {
@ -423,11 +429,11 @@ class DestinationController extends GetxController {
// 写真撮影モードでない場合
if (ds.isEmpty) {
debugPrint("* 目的地がない場合 ==> 検知半径=-1の場合");
//debugPrint("* 目的地がない場合 ==> ds=${ds}");
// print("----- in location popup cp - ${d.cp}----");
if ((d.cp == -1 || d.cp==0 ) && DateTime.now().difference(lastGoalAt).inHours >= 10) {
debugPrint("**1: 開始CPで、最後にゴールしてから時間経過していれば、");
debugPrint("**1: 目的地がない場合で、スタート地点。開始CPで、最後にゴールしてから時間経過していれば、");
chekcs = 1;
//start
@ -461,15 +467,15 @@ class DestinationController extends GetxController {
// 以下の条件分岐を追加
} else if (ds.isNotEmpty && ds[0].checkedin == true) {
// 目的地がDBに存在し、すでにチェックインしている場合は自動ポップアップを表示しない
debugPrint("チェックイン済み");
debugPrint("目的地がない場合で、チェックイン済み");
return;
} else if (isInRog.value == true &&
indexController.rogMode.value == 1 &&
(locationAlreadyCheckedIn==false) &&
d.cp != -1 && d.cp != 0 && d.cp != -2) {
} else if (isInRog.value == true && // 常にfalse だよ。。
indexController.rogMode.value == 1 && // マップではなくリストページだよ。
(locationAlreadyCheckedIn==false) && // まだチェックインしてないよ。
d.cp != -1 && d.cp != 0 && d.cp != -2) { // スタートでもゴールでもないよ。
debugPrint("**2: 標準CP まだチェックインしていない。");
debugPrint("**2: 目的地がない場合で、標準CP まだチェックインしていない。");
// print("----- in location popup checkin cp - ${d.cp}----");
chekcs = 2; // 標準CP
@ -494,14 +500,20 @@ class DestinationController extends GetxController {
});
}
return;
}else{
debugPrint("**Else: isInRog=${isInRog.value}, rogMode=${indexController.rogMode.value},locationAlreadyCheckedIn=${locationAlreadyCheckedIn},d.cp=${d.cp}");
}
}
// 以降、検知範囲にある場合。
//debugPrint("検知範囲にある場合");
debugPrint("検知範囲にある場合だよ...");
debugPrint("---- 検知範囲: ${d.checkin_radious} > 距離:${distance} ----");
debugPrint("---- チェックイン済みか? $locationAlreadyCheckedIn ----");
debugPrint("---- isInRog : ${isInRog.value}, checkingin = ${isCheckingIn.value}");
debugPrint("---- buyPointImageAdded: ${buyPointImageAdded}, ds.isNotEmpty?: ${ds.isNotEmpty},buyPoint:${buyPoint},buyPointCanceled=${buyPointCanceled}");
debugPrint(" ");
// print("---- location checkin radious ${d.checkin_radious} ----");
// print("---- already checked in $locationAlreadyCheckedIn ----");
if ((checkinRadious >= distance || checkinRadious == -1) &&
locationAlreadyCheckedIn == false &&
isInRog.value == true &&
@ -515,9 +527,9 @@ class DestinationController extends GetxController {
//print(
// "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ make checkin ${d.sub_loc_id}@@@@@@@@@@@");
makeCheckin(d, true, ""); // チェックインして
if (d.cp != -1 && d.cp != -2 && d.cp != 0 ) {
rogainingCounted.value = true; // ゴール用チェックイン済み
}
//if (d.cp != -1 && d.cp != -2 && d.cp != 0 ) {
// rogainingCounted.value = true; // ゴール用チェックイン済み
//}
skipGps = false;
}
return; // 戻る
@ -552,7 +564,9 @@ class DestinationController extends GetxController {
))).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
rogainingCounted.value = true;
if( isInRog.value==true ) {
rogainingCounted.value = true;
}
chekcs = 0;
isInCheckin.value = false;
isCheckingIn.value = false;
@ -729,13 +743,15 @@ class DestinationController extends GetxController {
DatabaseHelper db = DatabaseHelper.instance;
if (isgoal == false) {
await db.deleteAllDestinations();
await db.deleteAllRogaining();
// await db.deleteAllDestinations();
// await db.deleteAllRogaining();
await db.deleteAllDestinationsExceptTodayCheckins();
await db.deleteAllRogainingExceptToday();
}
int? latgoal = await db.latestGoal();
lastGoalAt = DateTime.fromMicrosecondsSinceEpoch(latgoal!);
//print("===== last goal : $last_goal_at =====");
debugPrint("===== last goal : $lastGoalAt =====");
dbService.updateDatabase();
}
@ -743,7 +759,7 @@ class DestinationController extends GetxController {
//
void deleteAllDestinations() {
DatabaseHelper db = DatabaseHelper.instance;
db.deleteAllDestinations().then((value) {
db.deleteAllDestinationsExceptTodayCheckins().then((value) {
populateDestinations();
});
}
@ -760,6 +776,18 @@ class DestinationController extends GetxController {
photos.add(File(imagePath));
},
destination: destination,
onCameraStatusChanged: (isAvailable) {
// カメラの状態が変更されたときの処理
if (!isAvailable) {
// カメラが利用できない場合の処理
Get.snackbar(
'エラー',
'カメラを初期化できませんでした。',
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
},
),
/*
builder: (_) => CameraCamera(
@ -859,7 +887,9 @@ class DestinationController extends GetxController {
dbDest: dss,
))).whenComplete(() {
skipGps = false;
rogainingCounted.value = true;
if( isInRog.value == true ) { // ロゲ開始していれば、遠くまで来たことにする。
rogainingCounted.value = true;
}
chekcs = 0;
isInCheckin.value = false;
//Get.back();
@ -984,6 +1014,7 @@ class DestinationController extends GetxController {
//
// 2024-4-8 Akira : See 2809
// checkForCheckinメソッドの再帰呼び出しをunawaitedで囲んで、非同期処理の結果を待たずに先に進むようにしました。また、再帰呼び出しの前に一定時間待機するようにしました。
// 2024-8-24 ... 佐伯呼び出しが必要なのか?
//
Future<void> checkForCheckin() async {
//print("--- Start of checkForCheckin function ---");
@ -1022,13 +1053,15 @@ class DestinationController extends GetxController {
} catch (e) {
print("An error occurred: $e");
// await checkForCheckin();
} finally {
await Future.delayed(const Duration(seconds: 1)); // 一定時間待機してから再帰呼び出し
//} finally {
// await Future.delayed(const Duration(seconds: 1)); // 一定時間待機してから再帰呼び出し
//print("--- End of checkForCheckin function, calling recursively ---");
unawaited( checkForCheckin() );
//unawaited( checkForCheckin() );
}
}
// GPSデータをサーバーにプッシュする関数です。
//
Future<void> pushGPStoServer() async {
@ -1089,55 +1122,46 @@ class DestinationController extends GetxController {
}
}
Future<void> _saveImageToGallery(String imagePath) async {
Future<String?> _saveImageToGallery(String imagePath) async {
final status = await PermissionController.checkStoragePermission();
if(!status){
await PermissionController.requestStoragePermission();
}
/*
final status = await Permission.storage.status;
if (!status.isGranted) {
final result = await Permission.storage.request();
if (!result.isGranted) {
// ユーザーがストレージの権限を拒否した場合の処理
showDialog(
context: Get.context!,
builder: (BuildContext context) {
return AlertDialog(
title: Text('ストレージの権限が必要です'),
content: Text(
'画像をギャラリーに保存するには、ストレージの権限が必要です。アプリの設定画面で権限を許可してください。'),
actions: [
TextButton(
child: Text('キャンセル'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('設定'),
onPressed: () {
Navigator.of(context).pop();
openAppSettings(); // アプリの設定画面を開く
},
),
],
);
}
);
try {
final appDir = await getApplicationDocumentsDirectory();
//final fileName = path.basename(imagePath);
final fileName = 'checkin_${DateTime.now().millisecondsSinceEpoch}.jpg';
final savedImage = await File(imagePath).copy('${appDir.path}/$fileName');
return;
debugPrint("fileName=${fileName}, appDir=${appDir} => ${savedImage}");
// ギャラリーにも保存
//await ImageGallerySaver.saveFile(savedImage.path);
await Future.delayed(const Duration(seconds: 3), () async {
final result = await ImageGallerySaver.saveFile(savedImage.path);
print("Save result: $result");
}).timeout(const Duration(seconds: 5));
debugPrint('Image saved to: ${savedImage.path}');
return savedImage.path;
/*
final result = await ImageGallerySaver.saveFile(imagePath);
debugPrint('Image saved to gallery: $result');
if (result['isSuccess']) {
return result['filePath'];
}
*/
} catch (e) {
if (e is TimeoutException) {
print("Operation timed out");
} else {
print('Failed to save image to gallery: $e');
}
}
*/
try {
final result = await ImageGallerySaver.saveFile(imagePath);
print('Image saved to gallery: $result');
} catch (e) {
print('Failed to save image to gallery: $e');
}
return null;
}
// 買い物ポイントを作成する関数です。 指定された目的地に対して買い物ポイントの処理を行います。
@ -1145,13 +1169,10 @@ class DestinationController extends GetxController {
// 買い物ポイントの作成に失敗した場合のエラーハンドリングを追加することを検討してください。
//
Future<void> makeBuyPoint(Destination destination, String imageurl) async {
String? savedImagePath = await _saveImageToGallery(imageurl);
DatabaseHelper db = DatabaseHelper.instance;
await db.updateBuyPoint(destination, imageurl);
await db.updateBuyPoint(destination, savedImagePath ?? imageurl);
populateDestinations();
//await _saveImageFromPath(imageurl);
await _saveImageToGallery(imageurl);
if (indexController.currentUser.isNotEmpty) {
double cpNum = destination.cp!;
@ -1160,6 +1181,7 @@ class DestinationController extends GetxController {
int userId = indexController.currentUser[0]["user"]["id"];
//print("--- Pressed -----");
debugPrint("user=${indexController.currentUser[0]["user"]}");
String team = indexController.currentUser[0]["user"]['team_name'];
//print("--- _team : ${_team}-----");
String eventCode = indexController.currentUser[0]["user"]["event_code"];
@ -1172,7 +1194,7 @@ class DestinationController extends GetxController {
//print("------ checkin event $eventCode ------");
ExternalService()
.makeCheckpoint(userId, token, formattedDate, team, cpNum.round(),
eventCode, imageurl)
eventCode, savedImagePath ?? imageurl)
.then((value) {
//print("------Ext service check point $value ------");
});
@ -1197,7 +1219,10 @@ class DestinationController extends GetxController {
if (ddd.isEmpty) {
destination.checkedin = true;
destination.checkin_image = imageurl;
if (imageurl.isNotEmpty) {
String? savedImagePath = await _saveImageToGallery(imageurl);
destination.checkin_image = savedImagePath ?? imageurl;
}
await db.insertDestination(destination);
// print("~~~~ inserted into db ~~~~");
}
@ -1212,7 +1237,8 @@ class DestinationController extends GetxController {
//await _saveImageFromPath(imageurl!);
}
if (imageurl.isNotEmpty) {
await _saveImageToGallery(imageurl);
String? savedImagePath = await _saveImageToGallery(imageurl);
destination.checkin_image = savedImagePath ?? imageurl;
}
populateDestinations();
@ -1299,11 +1325,11 @@ class DestinationController extends GetxController {
void onInit() async {
super.onInit();
/*
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
*/
startGPSCheckTimer();
@ -1810,7 +1836,7 @@ class DestinationController extends GetxController {
//
void deleteDBDestinations() {
DatabaseHelper db = DatabaseHelper.instance;
db.deleteAllDestinations().then((value) {
db.deleteAllDestinationsExceptTodayCheckins().then((value) {
populateDestinations();
});
dbService.updateDatabase();

View File

@ -2,6 +2,8 @@ import 'package:get/get.dart';
import 'package:gifunavi/pages/entry/entry_controller.dart';
import 'package:gifunavi/services/api_service.dart';
import '../index/index_controller.dart';
class EntryBinding extends Bindings {
@override
void dependencies() {

View File

@ -191,18 +191,21 @@ class EntryController extends GetxController {
final updatedCategory = await _apiService.getZekkenNumber(selectedCategory.value!.id);
final zekkenNumber = updatedCategory.categoryNumber.toString();
// selectedDate.value に 9時間を加えてJSTのオフセットを適用
final jstDate = selectedDate.value!.add(const Duration(hours: 9));
final newEntry = await _apiService.createEntry(
selectedTeam.value!.id,
selectedEvent.value!.id,
selectedCategory.value!.id,
selectedDate.value!,
jstDate, // JSTオフセットが適用された日付を使用
zekkenNumber,
);
entries.add(newEntry);
Get.back();
} catch (e) {
print('Error creating entry: $e');
Get.snackbar('Error', 'Failed to create entry');
Get.snackbar('Error', '$e');
} finally {
isLoading.value = false;
}

View File

@ -4,6 +4,8 @@ import 'package:flutter/material.dart';
import 'package:gifunavi/model/destination.dart';
import 'package:gifunavi/utils/database_helper.dart';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
class HistoryPage extends StatefulWidget {
const HistoryPage({super.key});
@ -15,6 +17,48 @@ class HistoryPage extends StatefulWidget {
class _HistoryPageState extends State<HistoryPage> {
DatabaseHelper db = DatabaseHelper.instance;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("pass_history".tr),
),
body: FutureBuilder<List<Destination>>(
future: db.getDestinations(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text("no_checkin_yet".tr));
}
final dests = snapshot.data!;
return ListView.builder(
itemCount: dests.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: CustomWidget(
title: dests[index].name ?? 'No Name',
subtitle: "${dests[index].sub_loc_id ?? 'N/A'} : ${dests[index].name ?? 'N/A'}",
image1Path: dests[index].checkin_image,
image2Path: dests[index].buypoint_image,
),
);
},
);
},
),
);
}
}
/*
class _HistoryPageState_old extends State<HistoryPage> {
DatabaseHelper db = DatabaseHelper.instance;
@override
Widget build(BuildContext context) {
return Scaffold(
@ -86,8 +130,97 @@ class _HistoryPageState extends State<HistoryPage> {
);
}
}
*/
class CustomWidget extends StatelessWidget {
final String? image1Path;
final String? image2Path;
final String title;
final String subtitle;
const CustomWidget({
super.key,
this.image1Path,
this.image2Path,
required this.title,
required this.subtitle,
});
Widget _buildImage(String? path) {
if (path == null) return const SizedBox.shrink();
return FutureBuilder<String>(
future: _getFullImagePath(path),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
return Image.file(
File(snapshot.data!),
width: 50,
height: 100,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
print('Error loading image: $error');
return const Icon(Icons.error);
},
);
} else if (snapshot.hasError) {
print('Error loading image path: ${snapshot.error}');
return const Icon(Icons.error);
} else {
return const CircularProgressIndicator();
}
},
);
}
Future<String> _getFullImagePath(String imagePath) async {
final appDir = await getApplicationDocumentsDirectory();
final fileName = path.basename(imagePath);
final fullPath = path.join(appDir.path, fileName);
debugPrint("Full image path: $fullPath");
return fullPath;
}
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 104,
child: Row(
children: [
_buildImage(image1Path),
if (image1Path != null && image2Path != null) const SizedBox(width: 2),
_buildImage(image2Path),
],
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
maxLines: null,
),
Text(
subtitle,
style: const TextStyle(fontSize: 16),
maxLines: null,
),
],
),
),
],
);
}
}
/*
class CustomWidget_old extends StatelessWidget {
final Image? image1;
final Image? image2;
final String title;
@ -152,3 +285,4 @@ class CustomWidget extends StatelessWidget {
);
}
}
*/

View File

@ -3,12 +3,18 @@ import 'package:gifunavi/pages/destination/destination_controller.dart';
import 'package:gifunavi/pages/index/index_controller.dart';
import 'package:gifunavi/utils/location_controller.dart';
import '../../services/api_service.dart';
class IndexBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<IndexController>(() => IndexController());
//Get.put<IndexController>(IndexController());
Get.put<LocationController>(LocationController());
Get.put<DestinationController>(DestinationController());
//Get.lazyPut<IndexController>(() => IndexController());
////Get.put<IndexController>(IndexController());
//Get.put<LocationController>(LocationController());
//Get.put<DestinationController>(DestinationController());
Get.put(IndexController(apiService: Get.find<ApiService>()), permanent: true);
Get.put(LocationController(), permanent: true);
Get.put(DestinationController(), permanent: true);
}
}

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
@ -31,6 +32,8 @@ import 'package:gifunavi/widgets/helper_dialog.dart';
import 'package:timezone/timezone.dart' as tz;
import 'package:timezone/data/latest.dart' as tz;
import '../permission/permission.dart';
class IndexController extends GetxController with WidgetsBindingObserver {
List<GeoJSONFeatureCollection> locations = <GeoJSONFeatureCollection>[].obs;
List<GeoJSONFeature> currentFeature = <GeoJSONFeature>[].obs;
@ -68,9 +71,14 @@ class IndexController extends GetxController with WidgetsBindingObserver {
String? userToken;
//late final ApiService _apiService;
final ApiService _apiService = Get.find<ApiService>();
final ApiService _apiService; // = Get.find<ApiService>();
final DatabaseHelper _dbHelper = DatabaseHelper.instance;
IndexController({
required ApiService apiService,
}) : _apiService = apiService;
// mode = 0 is map mode, mode = 1 list mode
var mode = 0.obs;
@ -235,6 +243,10 @@ class IndexController extends GetxController with WidgetsBindingObserver {
initConnectivity();
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
WidgetsBinding.instance.addObserver(this);
_startLocationService(); // アプリ起動時にLocationServiceを開始する
@ -263,10 +275,10 @@ class IndexController extends GetxController with WidgetsBindingObserver {
connectionStatusName.value = "WiFi";
break;
case ConnectivityResult.mobile:
connectionStatusName.value = "モバイルデータ";
connectionStatusName.value = "mobile";
break;
case ConnectivityResult.none:
connectionStatusName.value = "オフライン";
connectionStatusName.value = "offline";
break;
default:
connectionStatusName.value = "不明";
@ -352,22 +364,53 @@ class IndexController extends GetxController with WidgetsBindingObserver {
}
void _startLocationService() async {
const platform = MethodChannel('location');
try {
logManager.addOperationLog("Called start location service.");
await platform.invokeMethod('startLocationService');
} on PlatformException catch (e) {
print("Failed to start location service: '${e.message}'.");
if (Platform.isAndroid) {
const platform = MethodChannel('location');
try {
logManager.addOperationLog("Called start location service.");
await platform.invokeMethod('startLocationService');
} on PlatformException catch (e) {
print("Failed to start location service: '${e.message}'.");
}
}else if (Platform.isIOS) {
// iOSの位置情報サービス開始ロジック
// 例: geolocatorプラグインを使用する場合
try {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
// 位置情報サービスが無効の場合の処理
return;
}
// 位置情報の権限確認と取得開始
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
// 権限が拒否された場合の処理
return;
}
}
// 位置情報の取得開始
Geolocator.getPositionStream().listen((Position position) {
// 位置情報を使用した処理
});
} catch (e) {
print('Error starting iOS location service: $e');
}
}
}
void _stopLocationService() async {
const platform = MethodChannel('location');
try {
logManager.addOperationLog("Called stop location service.");
await platform.invokeMethod('stopLocationService');
} on PlatformException catch (e) {
print("Failed to stop location service: '${e.message}'.");
if (Platform.isAndroid) {
const platform = MethodChannel('location');
try {
logManager.addOperationLog("Called stop location service.");
await platform.invokeMethod('stopLocationService');
} on PlatformException catch (e) {
print("Failed to stop location service: '${e.message}'.");
}
}else{
debugPrint("stopLocation for iOS");
}
}
@ -433,51 +476,75 @@ class IndexController extends GetxController with WidgetsBindingObserver {
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
void login(String email, String password, BuildContext context) async{
AuthService.login(email, password).then((value) async {
print("------- logged in user details ######## $value ###### --------");
if (value.isNotEmpty) {
logManager.addOperationLog("User logged in : $value.");
// Navigator.pop(context);
print("--------- user details login ----- $value");
changeUser(value);
// ログイン成功後、api_serviceを初期化
await Get.putAsync(() => ApiService().init());
// ユーザー情報の完全性をチェック
if (await checkUserInfoComplete()) {
Get.offAllNamed(AppPages.INDEX);
} else {
Get.offAllNamed(AppPages.USER_DETAILS_EDIT);
}
Future<void> login(String email, String password) async {
try {
final value = await AuthService.login(email, password);
if (value.isNotEmpty && value['token'] != null) {
await changeUser(value);
await _initializeUserData();
Get.offAllNamed(AppPages.INDEX);
} else {
logManager.addOperationLog("User failed login : $email , $password.");
isLoading.value = false;
Get.snackbar(
"login_failed".tr,
"check_login_id_or_password".tr,
Get.snackbar('Login Failed', 'Invalid credentials');
}
} catch (e) {
print('Login error: $e');
Get.snackbar('Login Failed', 'An error occurred. Please try again.');
}
}
Future<void> _initializeUserData() async {
try {
await fetchUserEventInfo();
await fetchTeamData();
// 他の必要なデータ取得処理
} catch (e) {
print('Error initializing user data: $e');
Get.snackbar('Error', 'Failed to load user data. Please try again.');
}
}
Future<void> login_old(String email, String password, BuildContext context) async{
try {
AuthService.login(email, password).then((value) async {
print("------- logged in user details ######## $value ###### --------");
if (value.isNotEmpty && value['token']!=null) {
logManager.addOperationLog("User logged in : $value.");
// Navigator.pop(context);
print("--------- user details login ----- $value");
// ログイン成功後、api_serviceを初期化
await Get.putAsync(() => ApiService().init());
// ユーザー情報の完全性をチェック
if (await checkUserInfoComplete()) {
Get.offAllNamed(AppPages.INDEX);
} else {
Get.offAllNamed(AppPages.USER_DETAILS_EDIT);
}
} else {
logManager.addOperationLog("User failed login : $email , $password.");
isLoading.value = false;
Get.snackbar(
"login_failed".tr,
"check_login_id_or_password".tr,
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
//backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
}
});
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
//backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
}
});
} catch(e ){
print('Login error: $e');
Get.snackbar('Login Failed', 'An error occurred. Please try again.');
}
}
Future<bool> checkUserInfoComplete() async {
final user = await ApiService.to.getCurrentUser();
return user.firstname.isNotEmpty &&
user.lastname.isNotEmpty &&
user.dateOfBirth != null;
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
@ -532,12 +599,12 @@ class IndexController extends GetxController with WidgetsBindingObserver {
}
*/
void logout() async {
Future<void> logout() async {
logManager.addOperationLog("User logout : $currentUser .");
saveGameState();
locations.clear();
DatabaseHelper db = DatabaseHelper.instance;
db.deleteAllDestinations().then((value) {
db.deleteAllDestinationsExceptTodayCheckins().then((value) {
DestinationController destinationController =
Get.find<DestinationController>();
destinationController.populateDestinations();
@ -619,28 +686,48 @@ class IndexController extends GetxController with WidgetsBindingObserver {
}
*/
void changeUser(Map<String, dynamic> value, {bool replace = true}) async{
currentUser.clear();
currentUser.add(value);
if (replace) {
saveToDevice(currentUser[0]["token"]);
Future<void> changeUser(Map<String, dynamic> value, {bool replace = true}) async{
try {
if (value['user'] == null || value['token'] == null) {
throw Exception('Invalid user data');
}
currentUser.clear();
currentUser.add(value);
if (replace) {
saveToDevice(currentUser[0]["token"]);
}
isLoading.value = false;
// ユーザーのイベント情報を取得
await fetchUserEventInfo();
loadLocationsBound(currentUser[0]["user"]["event_code"]);
if (currentUser.isNotEmpty) {
rogMode.value = 0;
restoreGame();
// チームデータを取得
await fetchTeamData();
} else {
rogMode.value = 1;
}
Get.toNamed(AppPages.INDEX);
} catch( e ){
print('Error in changeUser: $e');
Get.snackbar('Error', 'Failed to update user information');
}
isLoading.value = false;
}
// ユーザーのイベント情報を取得
await fetchUserEventInfo();
loadLocationsBound( currentUser[0]["user"]["event_code"]);
if (currentUser.isNotEmpty) {
rogMode.value = 0;
restoreGame();
// チームデータを取得
await fetchTeamData();
} else {
rogMode.value = 1;
Future<bool> checkUserInfoComplete() async {
try {
final user = await ApiService.to.getCurrentUser();
return user.firstname.isNotEmpty &&
user.lastname.isNotEmpty &&
user.dateOfBirth != null;
} catch (e) {
print('Error checking user info: $e');
return false;
}
Get.toNamed(AppPages.INDEX);
}
Future<void> fetchUserEventInfo() async {
@ -697,7 +784,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
Future<void> fetchTeamData() async {
try {
Get.put(TeamController());
Get.put(TeamController(apiService:Get.find<ApiService>()));
// \"TeamController\" not found. You need to call \"Get.put(TeamController())\" or \"Get.lazyPut(()=>TeamController())\"
final teamController = Get.find<TeamController>();
await teamController.fetchTeams();
@ -945,7 +1032,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
Future<void> checkEntryData() async {
// エントリーデータの有無をチェックするロジック
final teamController = TeamController();
final teamController = TeamController(apiService:Get.find<ApiService>());
bool hasEntryData = teamController.checkIfUserHasEntryData();
if (!hasEntryData) {
await showHelperDialog(

View File

@ -38,10 +38,21 @@ class _IndexPageState extends State<IndexPage> {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
//checkLoginAndShowDialog();
//checkEventAndNavigate();
});
}
void checkLoginAndShowDialog() {
void checkEventAndNavigate() async {
if (indexController.currentUser.isNotEmpty &&
indexController.currentUser[0]["user"]["event_code"] == null) {
// イベントコードがない場合、EVENT_ENTRYページに遷移
await Get.toNamed(AppPages.EVENT_ENTRY);
// EVENT_ENTRYページから戻ってきた後に警告を表示
_showEventSelectionWarning();
}
}
void checkLoginAndShowDialog() async {
if (indexController.currentUser.isEmpty) {
showDialog(
context: context,
@ -78,9 +89,30 @@ class _IndexPageState extends State<IndexPage> {
);
},
);
}else{
if(indexController.currentUser[0]["user"]["event_code"] == null) {
// イベントコードがない場合、EVENT_ENTRYページに遷移
await Get.toNamed(AppPages.EVENT_ENTRY);
// EVENT_ENTRYページから戻ってきた後に警告を表示
_showEventSelectionWarning();
}
}
}
void _showEventSelectionWarning() {
Get.dialog(
AlertDialog(
title: Text('警告'),
content: Text('イベントを選択してください。'),
actions: [
TextButton(
child: Text('OK'),
onPressed: () => Get.back(),
),
],
),
);
}
// class IndexPage extends GetView<IndexController> {
// IndexPage({Key? key}) : super(key: key);

View File

@ -119,8 +119,7 @@ class _LoginPageState extends State<LoginPage> {
body: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: indexController.currentUser.isEmpty
? SizedBox(
width: double.infinity,
? SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
@ -216,8 +215,8 @@ class _LoginPageState extends State<LoginPage> {
true;
indexController.login(
emailController.text,
passwordController.text,
context);
passwordController.text
);
},
color: Colors.indigoAccent[400],
shape: RoundedRectangleBorder(
@ -271,37 +270,34 @@ class _LoginPageState extends State<LoginPage> {
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.all(8.0),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Text(
"app_developed_by_gifu_dx".tr,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
fontSize: 10.0),
style: const TextStyle(fontSize: 10.0),
textAlign: TextAlign.center,
),
),
),
],
],
),
),
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: EdgeInsets.all(8.0),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Text(
"※第8回と第9回は、岐阜県の令和年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています",
style: TextStyle(
fontSize: 10.0,
),
style: const TextStyle(fontSize: 10.0),
textAlign: TextAlign.center,
),
),
),
],
],
),
),
],
),

View File

@ -120,8 +120,8 @@ class LoginPopupPage extends StatelessWidget {
true;
indexController.login(
emailController.text,
passwordController.text,
context);
passwordController.text
);
},
color: Colors.indigoAccent[400],
shape: RoundedRectangleBorder(

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
@ -10,15 +12,6 @@ class PermissionController {
static bool _isRequestingPermission = false;
static Completer<bool>? _permissionCompleter;
static Future<bool> checkLocationPermissions() async {
final locationPermission = await Permission.location.status;
final whenInUsePermission = await Permission.locationWhenInUse.status;
final alwaysPermission = await Permission.locationAlways.status;
return locationPermission == PermissionStatus.granted &&
(whenInUsePermission == PermissionStatus.granted || alwaysPermission == PermissionStatus.granted);
}
static Future<bool> checkAndRequestPermissions() async {
if (_isRequestingPermission) {
return _permissionCompleter!.future;
@ -27,13 +20,119 @@ class PermissionController {
_isRequestingPermission = true;
_permissionCompleter = Completer<bool>();
bool hasPermissions = await checkLocationPermissions();
try {
bool hasPermissions = await _checkLocationPermissions();
if (!hasPermissions) {
bool userAgreed = await showLocationDisclosure();
if (userAgreed) {
if (Platform.isAndroid && !await _isAndroid13OrAbove()) {
hasPermissions = await _requestAndroidPreS();
} else {
hasPermissions = await _requestAllLocationPermissions();
}
} else {
print('User did not agree to location usage');
hasPermissions = false;
SystemNavigator.pop();
}
}
_isRequestingPermission = false;
_permissionCompleter!.complete(hasPermissions);
} catch (e) {
print('Error in permission request: $e');
_isRequestingPermission = false;
_permissionCompleter!.complete(false);
}
return _permissionCompleter!.future;
}
static Future<bool> _checkLocationPermissions() async {
final locationPermission = await Permission.location.status;
final whenInUsePermission = await Permission.locationWhenInUse.status;
final alwaysPermission = await Permission.locationAlways.status;
return locationPermission == PermissionStatus.granted &&
(whenInUsePermission == PermissionStatus.granted || alwaysPermission == PermissionStatus.granted);
}
static Future<bool> _requestAllLocationPermissions() async {
await Permission.location.request();
await Permission.locationWhenInUse.request();
final alwaysStatus = await Permission.locationAlways.request();
return alwaysStatus == PermissionStatus.granted;
}
static Future<bool> _requestAndroidPreS() async {
await Permission.location.request();
await Permission.locationWhenInUse.request();
// Android 13以前では、ユーザーに設定画面で権限を許可するように促す
await showDialog(
context: Get.context!,
builder: (context) => AlertDialog(
title: Text('バックグラウンド位置情報の許可'),
content: Text('アプリの設定画面で「常に許可」を選択してください。'),
actions: [
TextButton(
child: Text('設定を開く'),
onPressed: () {
openAppSettings();
Navigator.of(context).pop();
},
),
],
),
);
// 設定画面から戻ってきた後、再度権限をチェック
return await Permission.locationAlways.isGranted;
}
static Future<bool> _isAndroid13OrAbove() async {
if (Platform.isAndroid) {
final androidVersion = int.tryParse(Platform.operatingSystemVersion.split('.').first) ?? 0;
return androidVersion >= 13;
}
return false;
}
static Future<bool> checkLocationPermissions_old() async {
final locationPermission = await Permission.location.status;
if (locationPermission.isDenied) {
await showLocationDisclosure();
final result = await Permission.location.request();
if (result.isDenied) {
await openAppSettings();
}
}
final whenInUsePermission = await Permission.locationWhenInUse.status;
final alwaysPermission = await Permission.locationAlways.status;
return locationPermission == PermissionStatus.granted &&
(whenInUsePermission == PermissionStatus.granted || alwaysPermission == PermissionStatus.granted);
}
static Future<bool> checkAndRequestPermissions_old() async {
if (_isRequestingPermission) {
return _permissionCompleter!.future;
}
_isRequestingPermission = true;
_permissionCompleter = Completer<bool>();
bool hasPermissions = await _checkLocationPermissions();
if (!hasPermissions) {
bool userAgreed = await showLocationDisclosure();
if (userAgreed) {
try {
await requestAllLocationPermissions();
hasPermissions = await checkLocationPermissions();
hasPermissions = await _checkLocationPermissions();
} catch (e) {
print('Error requesting location permissions: $e');
hasPermissions = false;
@ -48,6 +147,8 @@ class PermissionController {
_isRequestingPermission = false;
_permissionCompleter!.complete(hasPermissions);
debugPrint("Finish checkAndRequestPermissions...");
return _permissionCompleter!.future;
}
@ -67,35 +168,51 @@ class PermissionController {
}
static Future<bool> showLocationDisclosure() async {
return await Get.dialog<bool>(
AlertDialog(
title: const Text('位置情報の使用について'),
content: const SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('このアプリでは、以下の目的で位置情報を使用します:'),
Text('• チェックポイントの自動チェックイン(アプリが閉じているときも含む)'),
Text('• 移動履歴の記録(バックグラウンドでも継続)'),
Text('• 現在地周辺の情報表示'),
Text('\nバックグラウンドでも位置情報を継続的に取得します。'),
Text('これにより、バッテリーの消費が増加する可能性があります。'),
Text('同意しない場合には、アプリは終了します。'),
],
if (Get.context == null) {
print('Context is null, cannot show dialog');
return false;
}
if (Get.isDialogOpen ?? false) {
print('A dialog is already open');
return false;
}
try {
final result = await Get.dialog<bool>(
AlertDialog(
title: const Text('位置情報の使用について'),
content: const SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('このアプリでは、以下の目的で位置情報を使用します:'),
Text(
'• チェックポイントの自動チェックイン(アプリが閉じているときも含む)'),
Text('• 移動履歴の記録(バックグラウンドでも継続)'),
Text('• 現在地周辺の情報表示'),
Text('\nバックグラウンドでも位置情報を継続的に取得します。'),
Text('これにより、バッテリーの消費が増加する可能性があります。'),
Text('同意しない場合には、アプリは終了します。'),
],
),
),
actions: <Widget>[
TextButton(
child: const Text('同意しない'),
onPressed: () => Get.back(result: false),
),
TextButton(
child: const Text('同意する'),
onPressed: () => Get.back(result: true),
),
],
),
actions: <Widget>[
TextButton(
child: const Text('同意しない'),
onPressed: () => Get.back(result: false),
),
TextButton(
child: const Text('同意する'),
onPressed: () => Get.back(result: true),
),
],
),
barrierDismissible: false,
) ?? false;
barrierDismissible: false,
);
return result ?? false;
}catch(e){
print('Dialog error: $e');
return false;
}
}
static void showPermissionDeniedDialog(String title,String message) {

View File

@ -2,6 +2,8 @@ import 'package:get/get.dart';
import 'package:gifunavi/pages/team/member_controller.dart';
import 'package:gifunavi/services/api_service.dart';
import '../index/index_controller.dart';
class MemberBinding extends Bindings {
@override
void dependencies() {

View File

@ -2,10 +2,15 @@ import 'package:get/get.dart';
import 'package:gifunavi/pages/team/team_controller.dart';
import 'package:gifunavi/services/api_service.dart';
//import '../entry/entry_controller.dart';
import '../index/index_controller.dart';
class TeamBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<ApiService>(() => ApiService());
Get.lazyPut<TeamController>(() => TeamController());
Get.lazyPut<TeamController>(() => TeamController(
apiService:Get.find<ApiService>())
);
}
}

View File

@ -15,8 +15,14 @@ import 'package:gifunavi/model/event.dart';
class TeamController extends GetxController {
late final ApiService _apiService;
late final EntryController _entryController;
final ApiService _apiService;
//final EntryController _entryController;
TeamController({
required ApiService apiService,
//required EntryController entryController,
}) : _apiService = apiService;
//_entryController = entryController;
final teams = <Team>[].obs;
final categories = <NewCategory>[].obs;
@ -35,12 +41,12 @@ class TeamController extends GetxController {
void onInit() async {
super.onInit();
try {
_apiService = Get.find<ApiService>();
//_apiService = Get.find<ApiService>();
if (!Get.isRegistered<EntryController>()) {
Get.put(EntryController());
}
_entryController = Get.find<EntryController>();
//if (!Get.isRegistered<EntryController>()) {
// Get.put(EntryController());
//}
//_entryController = Get.find<EntryController>();
await loadInitialData();
} catch (e) {

View File

@ -9,6 +9,7 @@ import 'package:gifunavi/model/team.dart';
import 'package:gifunavi/model/category.dart';
import 'package:gifunavi/model/user.dart';
import 'package:gifunavi/pages/index/index_controller.dart';
import '../routes/app_pages.dart';
import '../utils/const.dart';
import 'package:intl/intl.dart';
@ -22,11 +23,15 @@ class ApiService extends GetxService{
Future<ApiService> init() async {
try {
//if (!Get.isRegistered<IndexController>()) {
// Get.put(IndexController(apiService: Get.find<ApiService>()));
//}
// ここで必要な初期化処理を行う
serverUrl = ConstValues.currentServer();
baseUrl = '$serverUrl/api';
//await Future.delayed(Duration(seconds: 2)); // 仮の遅延(実際の初期化処理に置き換えてください)
print('ApiService initialized successfully');
print('ApiService initialized successfully . baseUrl = $baseUrl');
return this;
} catch(e) {
print('Error in ApiService initialization: $e');
@ -47,11 +52,13 @@ class ApiService extends GetxService{
注意点として、API のレスポンス形式が変更された場合や、新しいフィールドが追加された場合は、このメソッドも更新する必要があります。そのため、API の変更とクライアントサイドのコードの同期を保つことが重要です。
*/
String getToken()
Future<String> getToken2 () async
{
// IndexControllerの初期化を待つ
if (!Get.isRegistered<IndexController>()) {
Get.find<IndexController>();
}
final indexController = Get.find<IndexController>();
if (indexController.currentUser.isNotEmpty) {
token = indexController.currentUser[0]['token'] ?? '';
print("Get token = $token");
@ -61,11 +68,61 @@ class ApiService extends GetxService{
return token;
}
String getToken()
{
// IndexControllerの初期化を待つ
if (!Get.isRegistered<IndexController>()) {
Get.find<IndexController>();
}
final indexController = Get.find<IndexController>();
if (indexController.currentUser.isNotEmpty) {
token = indexController.currentUser[0]['token'] ?? '';
print("Get token = $token");
}else{
token = "";
}
return token;
}
Future<dynamic> _handleRequest(Future<http.Response> Function() request) async {
try {
final response = await request();
if (response.statusCode == 200) {
return json.decode(utf8.decode(response.bodyBytes));
} else if (response.statusCode == 401) {
await _handleUnauthorized();
throw Exception('Authentication failed. Please log in again.');
} else {
throw Exception('Request failed with status: ${response.statusCode}');
}
} catch (e) {
print('API request error: $e');
rethrow;
}
}
Future<void> _handleUnauthorized() async {
// トークンをクリアし、ユーザーをログアウトさせる
final indexController = Get.find<IndexController>();
await indexController.logout();
Get.offAllNamed(AppPages.LOGIN);
}
Future<List<Team>> getTeams() async {
final token = await getToken2();
return _handleRequest(() => http.get(
Uri.parse('$baseUrl/teams/'),
headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"},
)).then((data) => (data as List).map((json) => Team.fromJson(json)).toList());
}
Future<List<Team>> getTeams_old() async {
init();
getToken();
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"},
@ -136,6 +193,14 @@ class ApiService extends GetxService{
}
Future<NewCategory> getZekkenNumber(int categoryId) async {
final token = await getToken2();
return _handleRequest(() => http.post(
Uri.parse('$baseUrl/categories-viewset/$categoryId/get_zekken_number/'),
headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"},
)).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/'),
@ -157,7 +222,7 @@ class ApiService extends GetxService{
Future<User> getCurrentUser() async {
init();
getToken();
final token = getToken();
try {
final response = await http.get(
@ -174,6 +239,13 @@ class ApiService extends GetxService{
//_printDataComparison(jsonData, User);
return User.fromJson(jsonData);
} else if (response.statusCode == 401) {
// トークンが無効な場合、ログアウトしてログインページにリダイレクト
await Get.find<IndexController>().logout();
//indexController.logout();
Get.offAllNamed(AppPages.LOGIN);
throw Exception('Authentication failed. Please log in again.');
} else {
throw Exception('Failed to get current user. Status code: ${response.statusCode}');
}
@ -244,6 +316,15 @@ class ApiService extends GetxService{
}
Future<Team> createTeam(String teamName, int categoryId) async {
final token = await getToken2();
return _handleRequest(() => 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}),
)).then((data) => Team.fromJson(data));
}
Future<Team> createTeam_old(String teamName, int categoryId) async {
init();
getToken();
@ -268,6 +349,15 @@ class ApiService extends GetxService{
}
Future<Team> updateTeam(int teamId, String teamName, int categoryId) async {
final token = await getToken2();
return _handleRequest(() => 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}),
)).then((data) => Team.fromJson(data));
}
Future<Team> updateTeam_old(int teamId, String teamName, int categoryId) async {
init();
getToken();
@ -293,6 +383,14 @@ class ApiService extends GetxService{
}
Future<void> deleteTeam(int teamId) async {
final token = await getToken2();
await _handleRequest(() => http.delete(
Uri.parse('$baseUrl/teams/$teamId/'),
headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"},
));
}
Future<void> deleteTeamold_(int teamId) async {
init();
getToken();
@ -311,6 +409,14 @@ class ApiService extends GetxService{
}
Future<List<User>> getTeamMembers(int teamId) async {
final token = await getToken2();
return _handleRequest(() => http.get(
Uri.parse('$baseUrl/teams/$teamId/members/'),
headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"},
)).then((data) => (data as List).map((json) => User.fromJson(json)).toList());
}
Future<List<User>> getTeamMembers_old(int teamId) async {
init();
getToken();
@ -330,7 +436,23 @@ class ApiService extends GetxService{
}
}
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();
String? formattedDateOfBirth = dateOfBirth != null ? DateFormat('yyyy-MM-dd').format(dateOfBirth) : null;
return _handleRequest(() => 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,
}),
)).then((data) => User.fromJson(data));
}
Future<User> createTeamMember_old(int teamId, String? email, String? firstname, String? lastname, DateTime? dateOfBirth,bool? female) async {
init();
getToken();
@ -370,7 +492,22 @@ class ApiService extends GetxService{
}
}
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();
String? formattedDateOfBirth = dateOfBirth != null ? DateFormat('yyyy-MM-dd').format(dateOfBirth) : null;
return _handleRequest(() => 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,
}),
)).then((data) => User.fromJson(data));
}
Future<User> updateTeamMember_old(int teamId,int? memberId, String firstname, String lastname, DateTime? dateOfBirth,bool? female) async {
init();
getToken();
@ -401,7 +538,15 @@ class ApiService extends GetxService{
}
}
Future<void> deleteTeamMember(int teamId,int memberId) async {
Future<void> deleteTeamMember(int teamId, int memberId) async {
final token = await getToken2();
await _handleRequest(() => http.delete(
Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'),
headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"},
));
}
Future<void> deleteTeamMember_old(int teamId,int memberId) async {
init();
getToken();
@ -442,6 +587,14 @@ class ApiService extends GetxService{
}
Future<List<Entry>> getEntries() async {
final token = await getToken2();
return _handleRequest(() => http.get(
Uri.parse('$baseUrl/entry/'),
headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"},
)).then((data) => (data as List).map((json) => Entry.fromJson(json)).toList());
}
Future<List<Entry>> getEntries_old() async {
init();
getToken();
@ -500,7 +653,23 @@ class ApiService extends GetxService{
}
}
Future<Entry> createEntry(int teamId, int eventId, int categoryId, DateTime date,String zekkenNumber) async {
Future<Entry> createEntry(int teamId, int eventId, int categoryId, DateTime date, String zekkenNumber) async {
final token = await getToken2();
String formattedDate = DateFormat('yyyy-MM-dd').format(date);
return _handleRequest(() => 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,
}),
)).then((data) => Entry.fromJson(data));
}
Future<Entry> createEntry_old(int teamId, int eventId, int categoryId, DateTime date,String zekkenNumber) async {
init();
getToken();
@ -529,11 +698,28 @@ class ApiService extends GetxService{
} else {
final decodedResponse = utf8.decode(response.bodyBytes);
print("decodedResponse = $decodedResponse");
throw Exception('Failed to create entry');
final errorInfo = json.decode(decodedResponse);
throw Exception(errorInfo['error']);
}
}
Future<void> updateUserInfo(int userId, Entry entry) async {
final token = await getToken2();
String formattedDate = DateFormat('yyyy-MM-dd').format(entry.date!);
await _handleRequest(() => 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,
}),
));
}
Future<void> updateUserInfo_old(int userId, Entry entry) async {
init();
getToken();
@ -598,7 +784,10 @@ class ApiService extends GetxService{
final decodedResponse = utf8.decode(response.bodyBytes);
final blk = json.decode(decodedResponse);
throw Exception('Failed to update entry');
Map<String, dynamic> error_dict = blk[0]['error'];
String ? error_message = error_dict['non_field_errors'][0].string;
throw Exception(error_message);
}
}

View File

@ -51,8 +51,8 @@ class ExternalService {
//print("--- _team : ${_team}-----");
String eventCode = indexController.currentUser[0]["user"]["event_code"];
if (indexController.connectionStatusName.value != "wifi" &&
indexController.connectionStatusName.value != "mobile") {
if (indexController.connectionStatusName.value.toLowerCase() != "wifi" &&
indexController.connectionStatusName.value.toLowerCase() != "mobile") {
debugPrint("== No network ==");
DatabaseHelper db = DatabaseHelper.instance;
Rog rog = Rog(
@ -114,8 +114,8 @@ class ExternalService {
//int teamId = indexController.teamController.teams[0];
if (indexController.connectionStatusName.value != "wifi" &&
indexController.connectionStatusName.value != "mobile") {
if (indexController.connectionStatusName.value.toLowerCase() != "wifi" &&
indexController.connectionStatusName.value.toLowerCase() != "mobile") {
debugPrint("== checkin without network ==");
DatabaseHelper db = DatabaseHelper.instance;
@ -326,8 +326,8 @@ class ExternalService {
//print("--- _team : ${_team}-----");
String eventCode = indexController.currentUser[0]["user"]["event_code"];
if (indexController.connectionStatusName.value != "wifi" &&
indexController.connectionStatusName.value != "mobile") {
if (indexController.connectionStatusName.value.toLowerCase() != "wifi" &&
indexController.connectionStatusName.value.toLowerCase() != "mobile") {
return Future.value(false);
} else {
String serverUrl = ConstValues.currentServer();
@ -376,8 +376,8 @@ class ExternalService {
List<GpsData> gpsDataList = [];
if (indexController.connectionStatusName.value != "wifi" &&
indexController.connectionStatusName.value != "mobile") {
if (indexController.connectionStatusName.value.toLowerCase() != "wifi" &&
indexController.connectionStatusName.value.toLowerCase() != "mobile") {
return Future.value(false);
} else {
// Step 1: Fetch data from the local database

View File

@ -13,6 +13,11 @@ class DatabaseHelper {
static Database? _database;
Future<Database> get database async => _database ??= await _initDatabase();
// データベース初期化:
//
// シングルトンパターンを使用してDatabaseHelperのインスタンスを管理しています。
// _initDatabase()メソッドでデータベースを初期化し、必要なテーブルを作成します。
//
Future<Database> _initDatabase() async {
Directory documentDirectory = await getApplicationDocumentsDirectory();
String path = join(documentDirectory.path, 'rog.db');
@ -30,7 +35,10 @@ class DatabaseHelper {
onCreate: _onCreate);
}
// DBを初期化する際に、必要なテーブルを作成します。
//
Future _onCreate(Database db, int version) async {
// destinationテーブル: 目的地の情報を保存(位置、名前、住所、連絡先情報など)。
await db.execute('''
CREATE TABLE destination(
location_id INTEGER PRIMARY KEY,
@ -63,6 +71,7 @@ class DatabaseHelper {
)
''');
// rogainingテーブル: ロゲイニングorienteering的なアクティビティの記録を保存。
await db.execute('''
CREATE TABLE rogaining(
rog_id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -76,6 +85,7 @@ class DatabaseHelper {
)
''');
// rogテーブル: ロゲイニングのチェックポイント情報を保存。
await db.execute('''
CREATE TABLE rog(
id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -138,6 +148,22 @@ class DatabaseHelper {
await db.delete('rog');
}
Future<void> deleteAllRogainingExceptToday() async {
Database db = await instance.database;
// 今日の開始時刻をエポックミリ秒で取得
final now = DateTime.now();
final startOfDay = DateTime(now.year, now.month, now.day).millisecondsSinceEpoch;
// 今日チェックインしたもの以外を削除
await db.delete(
'rog',
where: 'checkintime < ?',
whereArgs: [startOfDay]
);
}
Future<bool> isRogAlreadyAvailable(int id) async {
Database db = await instance.database;
var rog = await db.query('rog', where: "id = $id");
@ -229,6 +255,27 @@ class DatabaseHelper {
await db.delete('destination');
}
Future<void> deleteAllDestinationsExceptTodayCheckins() async {
Database db = await instance.database;
// 今日の開始時刻をエポックからのミリ秒で取得
final now = DateTime.now();
final startOfDay = DateTime(now.year, now.month, now.day).millisecondsSinceEpoch;
// 今日チェックインされ、buy_pointを持つ目的地を除いて全て削除
await db.rawDelete('''
DELETE FROM destination
WHERE location_id NOT IN (
SELECT d.location_id
FROM destination d
JOIN rog r ON d.location_id = r.cp_number
WHERE date(r.checkintime / 1000, 'unixepoch', 'localtime') = date('now', 'localtime')
AND d.buy_point > 0
AND d.checkedin = 1
)
''', [startOfDay]);
}
Future<bool> isAlreadyAvailable(int locationId) async {
Database db = await instance.database;
var dest =

View File

@ -129,20 +129,25 @@ class LocationController extends GetxController {
// 現在位置を調整するメソッドを追加
LatLng? adjustCurrentLocation(Position? position) {
if (position == null) {
if (position == null) { // positionがnullなら、lastValidLocationを使用する。
if( lastValidLocation!=null ) {
debugPrint("== 現在位置なし。最後の位置を使用 ==");
//debugPrint("=== adjustCurrentLocation (Position:Null and using LastValidLocation ${lastValidLocation})===");
return LatLng(lastValidLocation!.latitude, lastValidLocation!.longitude);
}else {
print("=== adjustCurrentLocation (Position:Null and No LastValidLocation ... )===");
debugPrint("== 現在位置なし。最後の位置も無し ==");
//print("=== adjustCurrentLocation (Position:Null and No LastValidLocation ... )===");
return null;
}
//return lastValidLocation ?? LatLng(0, 0);
}
final signalStrength = getGpsSignalStrength(position);
if (signalStrength == 'high' || signalStrength == 'medium') {
debugPrint("== 信号強度 ${signalStrength} ==> 最新位置を使用 ==");
//debugPrint("=== adjustCurrentLocation (Position:Get and return Valid location:${position} ... )===");
lastValidLocation = LatLng(position.latitude, position.longitude);
}else{
debugPrint("== 信号強度 ${signalStrength} ==> 最後の位置を使用 ==");
}
return lastValidLocation ?? LatLng(lastValidLocation!.latitude, lastValidLocation!.longitude);
}
@ -169,10 +174,39 @@ class LocationController extends GetxController {
void onInit() {
super.onInit();
// Start listening to location updates when the controller is initialized
startPositionStream();
_initLocationService();
}
Future<void> _initLocationService() async {
try {
bool serviceEnabled;
LocationPermission permission;
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return Future.error('Location services are disabled.');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return Future.error('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.');
}
startPositionStream();
} catch( e ){
print('Error initializing location service: $e');
}
}
// 位置情報のストリームを開始するメソッドです。
// 位置情報サービスが有効か確認し、無効な場合はダイアログを表示します。
// 位置情報の権限を確認し、必要な権限がない場合は権限をリクエストします。
@ -185,7 +219,26 @@ class LocationController extends GetxController {
// 2024-4-8 Akira : See 2809
// stopPositionStreamメソッドを追加して、既存のストリームをキャンセルするようにしました。また、ストリームが完了したらnullに設定し、エラー発生時にストリームをキャンセルするようにしました。
//
void startPositionStream() async {
void startPositionStream() {
positionStream = Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 5, //10, 10mから5mに変更
),
).listen((Position position) {
currentPosition.value = position;
//debugPrint("== startPositionStream: ${position} ==");
locationMarkerPositionStreamController.add(
LocationMarkerPosition(
latitude: position.latitude,
longitude: position.longitude,
accuracy: position.accuracy,
),
);
});
}
void startPositionStream_old() async {
// Check for location service and permissions before starting the stream
// 位置情報サービスの有効性をチェックし、無効な場合はエラーハンドリングを行います。
//

View File

@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'dart:async';
void _performMemoryCleanup() {
//debugPrint('Performing memory cleanup');
// キャッシュのクリア
Get.deleteAll(); // GetXを使用している場合、すべてのコントローラをクリア
imageCache.clear(); // 画像キャッシュをクリア
imageCache.clearLiveImages(); // 使用中の画像キャッシュをクリア
// 大きなオブジェクトの解放
_clearLargeObjects();
// 未使用のリソースの解放
_releaseUnusedResources();
// ガベージコレクションの促進
_forceGarbageCollection();
debugPrint('Memory cleanup completed');
}
void _clearLargeObjects() {
// 大きなリストやマップをクリア
// 例: myLargeList.clear();
// 例: myLargeMap.clear();
}
void _releaseUnusedResources() {
// 使用していないストリームのクローズ
// 例: myStream?.close();
// 未使用のアニメーションコントローラーの破棄
// 例: myAnimationController?.dispose();
// テキスト編集コントローラーの破棄
// 例: myTextEditingController?.dispose();
}
void _forceGarbageCollection() {
// Dart VMにガベージコレクションを促す
Timer(const Duration(seconds: 1), () {
//debugPrint('Forcing garbage collection');
// この呼び出しは必ずしもガベージコレクションを即座に実行するわけではありませんが、
// Dart VMにガベージコレクションの実行を強く促します。
// ignore: dead_code
bool didRun = false;
assert(() {
didRun = true;
return true;
}());
if (didRun) {
//debugPrint('Garbage collection forced in debug mode');
}
});
}
// メモリ使用量を監視し、必要に応じてクリーンアップを実行する関数
void startMemoryMonitoring() {
const Duration checkInterval = Duration(minutes: 5);
Timer.periodic(checkInterval, (Timer timer) {
_checkMemoryUsage();
});
}
void _checkMemoryUsage() async {
// ここでメモリ使用量をチェックするロジックを実装
// 例えば、プラットフォーム固有の方法でメモリ使用量を取得する
// 仮の閾値(実際のアプリケーションに応じて調整が必要)
const int memoryThreshold = 100 * 1024 * 1024; // 100 MB
// 仮のメモリ使用量チェック(実際の実装に置き換える必要があります)
int currentMemoryUsage = await _getCurrentMemoryUsage();
if (currentMemoryUsage > memoryThreshold) {
debugPrint('High memory usage detected: $currentMemoryUsage bytes');
_performMemoryCleanup();
}
}
Future<int> _getCurrentMemoryUsage() async {
// プラットフォーム固有のメモリ使用量取得ロジックを実装
// この例では仮の値を返しています
return 150 * 1024 * 1024; // 仮に150MBとする
}

View File

@ -175,7 +175,12 @@ class BottomSheetNew extends GetView<BottomSheetController> {
destinationController.currentLat, destinationController.currentLon),
LatLng(cdest.lat!, cdest.lon!));
// Check conditions to show confirmation dialog
// スタートボタン:
// 表示条件:
// 1. 目的地のCP番号が-1または0の場合
// 2. ロゲイニングがまだ開始されていない場合destinationController.isInRog.value == false
// 3. 最後のゴールから10時間以上経過している場合
//
if (destinationController.isInRog.value == false &&
(destinationController.distanceToStart() <= 100 || destinationController.isGpsSignalWeak() ) && //追加 Akira 2024-4-5
(destination.cp == -1 || destination.cp == 0 ) &&
@ -294,7 +299,15 @@ class BottomSheetNew extends GetView<BottomSheetController> {
//print("counted ${destinationController.rogainingCounted.value}");
// ゴールボタン:
// 表示条件:
// 1. 目的地のCP番号が0、-2、または-1の場合
// 2. ロゲイニングが開始されている場合destinationController.rogainingCounted.value == true
// 3. スタート地点から500m以内にいる場合、または GPS信号が弱い場合
// 4. ゴール準備完了フラグが立っている場合DestinationController.ready_for_goal == true
//
}else if (destinationController.rogainingCounted.value == true &&
destinationController.isInRog.value == true &&
// destinationController.distanceToStart() <= 500 && ... GPS信号が弱い時でもOKとする。
(destinationController.distanceToStart() <= 500 || destinationController.isGpsSignalWeak() ) &&
(destination.cp == 0 || destination.cp == -2 || destination.cp == -1) &&
@ -334,7 +347,7 @@ class BottomSheetNew extends GetView<BottomSheetController> {
}
: null,
child: Text(
"finish_rogaining".tr,
"finish_rogaining".tr, // ロゲゴール
style: const TextStyle(color: Colors.white),
));
@ -353,6 +366,9 @@ class BottomSheetNew extends GetView<BottomSheetController> {
destinationController.isCheckingIn.value = true; // ここを追加
Get.back();
Get.back();
if(destinationController.isInRog.value==false && destination.cp == -1){
destinationController.rogainingCounted.value = false;
}
await Future.delayed(const Duration(milliseconds: 500));
await destinationController.callforCheckin(destination);
destinationController.isCheckingIn.value = false;
@ -371,8 +387,8 @@ class BottomSheetNew extends GetView<BottomSheetController> {
},
child: Text(
destination.cp == -1 &&
destinationController.isInRog.value == false &&
destinationController.rogainingCounted.value == false
destinationController.isInRog.value == false //&&
//destinationController.rogainingCounted.value == false
? "ロゲ開始"
: destinationController.isInRog.value == true &&
destination.cp == -1

View File

@ -151,26 +151,23 @@ class _GameStateWidgetState extends State<GameStateWidget> {
padding: const EdgeInsets.all(4.0),
child: StreamBuilder<List<Destination>>(
stream: dbService.destinationUpdatesStream,
initialData: const [], // 初期値を設定
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasError) {
return LocationVisitedWidget(
count: 0,
minimized: !isExpanded,
);
} else if (snapshot.hasData) {
return LocationVisitedWidget(
count: snapshot.data!.length,
minimized: !isExpanded,
);
} else {
if (snapshot.hasError) {
print('Error: ${snapshot.error}');
return LocationVisitedWidget(
count: 0,
minimized: !isExpanded,
);
}
// データがある場合はそのデータを使用し、ない場合は空のリストを使用
final destinations = snapshot.data ?? [];
return LocationVisitedWidget(
count: destinations.length,
minimized: !isExpanded,
);
},
),
@ -183,12 +180,12 @@ class _GameStateWidgetState extends State<GameStateWidget> {
padding: const EdgeInsets.all(4.0),
child: Obx(() => ConnectionStatusIndicator(
connectionStatus: (indexController
.connectionStatusName.value ==
.connectionStatusName.value.toLowerCase() ==
"wifi" ||
indexController
.connectionStatusName.value ==
.connectionStatusName.value.toLowerCase() ==
"mobile")
? indexController.connectionStatusName.value ==
? indexController.connectionStatusName.value.toLowerCase() ==
"wifi"
? ConnectionStatus.wifi
: ConnectionStatus.mobile

View File

@ -26,7 +26,8 @@ class _HelperDialogState extends State<HelperDialog> {
Text('ヘルプ'),
],
),
content: Column(
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -42,11 +43,17 @@ class _HelperDialogState extends State<HelperDialog> {
});
},
),
const Text('この画面を二度と表示しない'),
const Flexible(
child: Text(
'この画面を二度と表示しない',
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
),
actions: [
TextButton(
child: const Text('OK'),
@ -68,6 +75,9 @@ Future<void> showHelperDialog(String message, String screenKey) async {
final prefs = await SharedPreferences.getInstance();
final showHelper = prefs.getBool('helper_$screenKey') ?? true;
if (showHelper) {
Get.dialog(HelperDialog(message: message, screenKey: screenKey));
Get.dialog(
HelperDialog(message: message, screenKey: screenKey),
barrierDismissible: false,
);
}
}

View File

@ -52,6 +52,12 @@ class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
// 追加
WidgetsBinding.instance.addPostFrameCallback((_) {
PermissionController.checkAndRequestPermissions();
});
debugPrint('MapWidget: initState called');
SettingsBinding().dependencies(); // これを追加
_startIdleTimer();
@ -84,6 +90,23 @@ class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
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);
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:gifunavi/pages/permission/permission.dart';
import 'package:get/get.dart';
class PermissionHandlerScreen extends StatefulWidget {
const PermissionHandlerScreen({super.key});
@ -24,8 +25,14 @@ class _PermissionHandlerScreenState extends State<PermissionHandlerScreen> {
appBar: AppBar(
title: const Text('権限の確認'),
),
body: const Center(
child: Text('権限の確認中...'),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('権限の確認中...'),
const SizedBox(height: 20),
],
),
),
);
}