diff --git a/.metadata b/.metadata index a0a9eb4..391c336 100644 --- a/.metadata +++ b/.metadata @@ -1,11 +1,11 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled. +# This file should be version controlled and should not be manually edited. version: - revision: 2ad6cd72c040113b47ee9055e722606a490ef0da - channel: stable + revision: "5874a72aa4c779a02553007c47dacbefba2374dc" + channel: "stable" project_type: app @@ -13,11 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da - base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + - platform: android + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: ios - create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da - base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + - platform: linux + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + - platform: macos + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + - platform: web + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + - platform: windows + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc # User provided section diff --git a/README.md b/README.md index 6e97046..739c1bd 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,29 @@ For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. +## iOS のマニュアル更新内容 + +* Info.plist + CFBundleDisplayName + 岐阜ナビ + + CFBundleName + 岐阜ナビ + + NSCameraUsageDescription + 岐阜ナビはチェックポイントで撮影した写真を写真ライブラリに保存し、通過記録を保持し、競技結果として提出することができます。 + NSLocationAlwaysAndWhenInUseUsageDescription + 岐阜ナビはアプリが閉じられているときでも位置情報へのアクセスが必要です。これにより、走行履歴の記録ができ、レビュー時の参考にすることができます。 + NSLocationAlwaysUsageDescription + このアプリではバックグラウンドで位置情報にアクセスします。 + NSLocationWhenInUseUsageDescription + このアプリはチェックポイントへのチェックインや走行履歴を記録するために、位置情報にアクセスします。 + NSMicrophoneUsageDescription + このアプリではカメラは使用しますが、マイクの使用は当面行いません。 + NSPhotoLibraryUsageDescription + 撮影した写真はデバイスのアルバムに保存されます。これにより、不具合時の通過記録を安全に担保することができます。 + + # 更新履歴 0. flutter_compass は pub.dev cache で 34 に変更。キャッシュをクリアしたら修正が必要。 @@ -157,4 +180,13 @@ samples, guidance on mobile development, and a full API reference. テスト用位置情報: 大垣駅: 35.36701369466119, 136.61783662683948 大垣城: 35.36182698266251, 136.61558088722234 -関ケ原駅:35.36365422752628, 136.47061844402452 \ No newline at end of file +関ケ原駅:35.36365422752628, 136.47061844402452 +高山駅:36.14130783620718, 137.25050201764944 +ガソリンスタンド:36.13826570797936, 137.21513450124928 + +バグ: +履歴の写真:アクセスエラー +バックアップをイベントごとに保存・レストア + +ログインした際に、イベントが選択されていなければ、イベントを選択するように促す。 +事前チェックインした写真が履歴に表示されない。 diff --git a/android/app/src/main/kotlin/com/dvox/gifunavi_git/MainActivity.kt b/android/app/src/main/kotlin/com/dvox/gifunavi_git/MainActivity.kt new file mode 100644 index 0000000..29bb72e --- /dev/null +++ b/android/app/src/main/kotlin/com/dvox/gifunavi_git/MainActivity.kt @@ -0,0 +1,5 @@ +package com.dvox.gifunavi_git + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/ios/GifuNavigation.mobileprovision b/ios/GifuNavigation.mobileprovision deleted file mode 100644 index c523c3f..0000000 Binary files a/ios/GifuNavigation.mobileprovision and /dev/null differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2531a8c..af37d55 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,6 +4,8 @@ PODS: - connectivity_plus (0.0.1): - Flutter - FlutterMacOS + - device_info_plus (0.0.1): + - Flutter - Flutter (1.0.0) - flutter_compass (0.0.1): - Flutter @@ -43,6 +45,7 @@ PODS: DEPENDENCIES: - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - flutter_compass (from `.symlinks/plugins/flutter_compass/ios`) - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) @@ -68,6 +71,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/camera_avfoundation/ios" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/darwin" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" Flutter: :path: Flutter flutter_compass: @@ -102,6 +107,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4 connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db + device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_compass: cbbd285cea1584c7ac9c4e0c3e1f17cbea55e855 flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 986ec01..298f241 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -8,14 +8,14 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 2EAA11F0595B6B1A6B6ADED8 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53C8E795FF5969F8C7B6A237 /* Pods_RunnerTests.framework */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 712BA8D3720206B23B820A9B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4E153E327A403FD75830D36 /* Pods_RunnerTests.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 8B39E7104A3E3C6F3B501CB2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A39AA3BE6DA0B7B42041019 /* Pods_Runner.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - B6A4962F862B554C98AD7E0A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F31FDD2E828C96A459E4AA85 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -46,14 +46,16 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3592C2DA8DF570D2D07650A8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 53C8E795FF5969F8C7B6A237 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 402073277F0CF7D1DE2A9F29 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 6850CF9EE994EB8682BD460F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 6A39AA3BE6DA0B7B42041019 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6FFD6A432600780CEB6D6374 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 84738EC4DA98A3780DDAB81F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 89205C63F97F88BDB459168E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 89EEC485D24E5B1BC0DD7B8E /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 86D6A3692EBE5943C3C75411 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -61,10 +63,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AC42218FD0A0DB137D157E93 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - D773E9161AA2211A3E2B94DD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - DA00EBAA91366A0B5BFDAE96 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - F31FDD2E828C96A459E4AA85 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9E76BC5648643A00E08CF70B /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + C4E153E327A403FD75830D36 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -72,15 +72,15 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B6A4962F862B554C98AD7E0A /* Pods_Runner.framework in Frameworks */, + 8B39E7104A3E3C6F3B501CB2 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - CEDA9491D04A70CE2A58DF32 /* Frameworks */ = { + F68F88FFE2DC7D6754431798 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 2EAA11F0595B6B1A6B6ADED8 /* Pods_RunnerTests.framework in Frameworks */, + 712BA8D3720206B23B820A9B /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -95,11 +95,24 @@ path = RunnerTests; sourceTree = ""; }; - 3806CD679A8B2BB869428B93 /* Frameworks */ = { + 7F718872327E3605229882F1 /* Pods */ = { isa = PBXGroup; children = ( - F31FDD2E828C96A459E4AA85 /* Pods_Runner.framework */, - 53C8E795FF5969F8C7B6A237 /* Pods_RunnerTests.framework */, + 402073277F0CF7D1DE2A9F29 /* Pods-Runner.debug.xcconfig */, + 3592C2DA8DF570D2D07650A8 /* Pods-Runner.release.xcconfig */, + 6FFD6A432600780CEB6D6374 /* Pods-Runner.profile.xcconfig */, + 86D6A3692EBE5943C3C75411 /* Pods-RunnerTests.debug.xcconfig */, + 6850CF9EE994EB8682BD460F /* Pods-RunnerTests.release.xcconfig */, + 9E76BC5648643A00E08CF70B /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 85F630F9575BB5DDC6F6B3B2 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6A39AA3BE6DA0B7B42041019 /* Pods_Runner.framework */, + C4E153E327A403FD75830D36 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -122,8 +135,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, - B9F40AE3939A8E59418B2C25 /* Pods */, - 3806CD679A8B2BB869428B93 /* Frameworks */, + 7F718872327E3605229882F1 /* Pods */, + 85F630F9575BB5DDC6F6B3B2 /* Frameworks */, ); sourceTree = ""; }; @@ -151,19 +164,6 @@ path = Runner; sourceTree = ""; }; - B9F40AE3939A8E59418B2C25 /* Pods */ = { - isa = PBXGroup; - children = ( - DA00EBAA91366A0B5BFDAE96 /* Pods-Runner.debug.xcconfig */, - D773E9161AA2211A3E2B94DD /* Pods-Runner.release.xcconfig */, - 84738EC4DA98A3780DDAB81F /* Pods-Runner.profile.xcconfig */, - AC42218FD0A0DB137D157E93 /* Pods-RunnerTests.debug.xcconfig */, - 89205C63F97F88BDB459168E /* Pods-RunnerTests.release.xcconfig */, - 89EEC485D24E5B1BC0DD7B8E /* Pods-RunnerTests.profile.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -171,10 +171,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - CD76B1A18202CBE77CBE3DCF /* [CP] Check Pods Manifest.lock */, + 7F66DF627A66DF465C5F63D3 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, - CEDA9491D04A70CE2A58DF32 /* Frameworks */, + F68F88FFE2DC7D6754431798 /* Frameworks */, ); buildRules = ( ); @@ -190,15 +190,15 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 7EC19810B524B833F58A16B8 /* [CP] Check Pods Manifest.lock */, + 9BC3CC202AEB33DDD6358396 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 3CF3DF1DB3C3E48099A705D1 /* [CP] Embed Pods Frameworks */, - 9D3C0722FFFB9D74548C2A72 /* [CP] Copy Pods Resources */, + B576B812EBF41188E37C7BA1 /* [CP] Embed Pods Frameworks */, + 8EF00F2B7B12B1557BEC9D5A /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -286,24 +286,61 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 3CF3DF1DB3C3E48099A705D1 /* [CP] Embed Pods Frameworks */ = { + 7F66DF627A66DF465C5F63D3 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 7EC19810B524B833F58A16B8 /* [CP] Check Pods Manifest.lock */ = { + 8EF00F2B7B12B1557BEC9D5A /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + 9BC3CC202AEB33DDD6358396 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -325,58 +362,21 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; - 9D3C0722FFFB9D74548C2A72 /* [CP] Copy Pods Resources */ = { + B576B812EBF41188E37C7BA1 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - CD76B1A18202CBE77CBE3DCF /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -508,14 +508,14 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AC42218FD0A0DB137D157E93 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 86D6A3692EBE5943C3C75411 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.gifunavi.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunaviGit.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -526,14 +526,14 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 89205C63F97F88BDB459168E /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 6850CF9EE994EB8682BD460F /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.gifunavi.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunaviGit.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -542,14 +542,14 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 89EEC485D24E5B1BC0DD7B8E /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 9E76BC5648643A00E08CF70B /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.gifunavi.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunaviGit.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -560,7 +560,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -617,7 +617,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index e25381f..dc9ada4 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 3641719..7353c41 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 683703e..797d452 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 33f79d9..6ed2d93 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index cc4b3ac..4cd7b00 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index c1b47e2..fe73094 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index c1e2731..321773c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 683703e..797d452 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 8a55696..502f463 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a14e94b..0ec3034 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png deleted file mode 100644 index ee6c4d3..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png deleted file mode 100644 index 4d15a63..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png deleted file mode 100644 index ad1119d..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png deleted file mode 100644 index c3a317b..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a14e94b..0ec3034 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 1599df8..e9f5fea 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png deleted file mode 100644 index 899012d..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png deleted file mode 100644 index c94b6f2..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 39ec196..84ac32a 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 8106b9a..8953cba 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index a964c5e..0467bf1 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 5bf9375..5d91f6a 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -24,28 +24,24 @@ ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) - LSApplicationCategoryType - LSRequiresIPhoneOS - NSCameraUsageDescription - 岐阜ナビはチェックポイントで撮影した写真を写真ライブラリに保存し、通過記録を保持し、競技結果として提出することができます。 - NSLocationAlwaysAndWhenInUseUsageDescription - 岐阜ナビはアプリが閉じられているときでも位置情報へのアクセスが必要です。これにより、走行履歴の記録ができ、レビュー時の参考にすることができます。 - NSLocationAlwaysUsageDescription - このアプリではバックグラウンドで位置情報にアクセスします。 - NSLocationWhenInUseUsageDescription - このアプリはチェックポイントへのチェックインや走行履歴を記録するために、位置情報にアクセスします。 - NSMicrophoneUsageDescription - このアプリではカメラは使用しますが、マイクの使用は当面行いません。 - NSPhotoLibraryUsageDescription - 撮影した写真はデバイスのアルバムに保存されます。これにより、不具合時の通過記録を安全に担保することができます。 + NSCameraUsageDescription + 岐阜ナビはチェックポイントで撮影した写真を写真ライブラリに保存し、通過記録を保持し、競技結果として提出することができます。 + NSLocationAlwaysAndWhenInUseUsageDescription + 岐阜ナビはアプリが閉じられているときでも位置情報へのアクセスが必要です。これにより、走行履歴の記録ができ、レビュー時の参考にすることができます。 + NSLocationAlwaysUsageDescription + このアプリではバックグラウンドで位置情報にアクセスします。 + NSLocationWhenInUseUsageDescription + このアプリはチェックポイントへのチェックインや走行履歴を記録するために、位置情報にアクセスします。 + NSMicrophoneUsageDescription + このアプリではカメラは使用しますが、マイクの使用は当面行いません。 + NSPhotoLibraryAddUsageDescription + 撮影した写真はデバイスのアルバムに保存されます。これにより、不具合時の通過記録を安全に担保することができます。 + NSPhotoLibraryUsageDescription + 撮影した写真はデバイスのアルバムに保存されます。これにより、不具合時の通過記録を安全に担保することができます。 UIApplicationSupportsIndirectInputEvents - UIBackgroundModes - - location - UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -63,9 +59,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - - io.flutter.embedded_views_preview - + LSApplicationCategoryType + diff --git a/lib/main.dart b/lib/main.dart index f1a4da6..61d31df 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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 deviceInfo = {}; @@ -56,14 +58,25 @@ void saveGameState() async { */ // 現在のユーザーのIDも一緒に保存するようにします。 -void saveGameState() async { +Future saveGameState() async { DestinationController destinationController = Get.find(); IndexController indexController = Get.find(); 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.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 restoreGame() async { SharedPreferences pref = await SharedPreferences.getInstance(); + IndexController indexController = Get.find(); 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(); + + 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.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(); + + DestinationController destinationController = Get.find(); + 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())); + 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 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 _initLocationController() async { + if (!Get.isRegistered()) { + Get.put(LocationController()); + } + print('=== 1. LocationController started...'); +} + +Future _initTimeZone() async { + tz.initializeTimeZones(); +} + +Future _initCacheProvider() async { + await CacheProvider.initialize(); +} + +Future _checkPermissions() async { + await PermissionController.checkAndRequestPermissions(); +} + +Future _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 requestLocationPermission() async { try { final status = await Permission.locationAlways.request(); @@ -300,70 +387,80 @@ String team_name=""; String event_code=""; Future startBackgroundTracking() async { - if (Platform.isIOS && background==false) { - - final IndexController indexController = Get.find(); - 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(); + 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 addGPStoDB(double la, double ln) async { } Future 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 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()) { - 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 with WidgetsBindingObserver { debugPrint("Start MyAppState..."); } + Future _restoreGameAndInitialize() async { + await restoreGame(); + // ここに他の初期化処理を追加できます + } + + void _initializeControllers() { + /* + if (!Get.isRegistered()) { + _locationController = Get.put(LocationController(), permanent: true); + } + if (!Get.isRegistered()) { + _indexController = Get.put(IndexController(apiService: Get.find()), permanent: true); + } + if (!Get.isRegistered()) { + _destinationController = + Get.put(DestinationController(), permanent: true); + } + if (!Get.isRegistered()) { + _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> _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 with WidgetsBindingObserver { @override void dispose() { WidgetsBinding.instance.removeObserver(this); + _memoryCheckTimer?.cancel(); super.dispose(); } @@ -487,110 +692,118 @@ class _MyAppState extends State with WidgetsBindingObserver { @override Future didChangeAppLifecycleState(AppLifecycleState state) async { try { - LocationController locationController = Get.find(); + if (!Get.isRegistered()) { + _indexController = Get.find(); + } + if (!Get.isRegistered()) { + _locationController = Get.find(); + } + if (!Get.isRegistered()) { + _destinationController = Get.find(); + } - DestinationController destinationController = Get.find< - DestinationController>(); - - //DestinationController destinationController = - // Get.find(); 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 _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 _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 _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 _onDetached() async { + debugPrint(" ==(Status Changed)==> DETACHED アプリは終了します。"); + await saveGameState(); + // アプリ終了時の追加処理 + } + + + Future _onHidden() async { + debugPrint(" ==(Status Changed)==> Hidden アプリが隠れた"); + await saveGameState(); + } @override Widget build(BuildContext context) { diff --git a/lib/pages/camera/camera_page.dart b/lib/pages/camera/camera_page.dart index 0046d21..9c79c2d 100644 --- a/lib/pages/camera/camera_page.dart +++ b/lib/pages/camera/camera_page.dart @@ -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: [ + 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; }, diff --git a/lib/pages/camera/custom_camera_view.dart b/lib/pages/camera/custom_camera_view.dart index 7684b43..4d89d9e 100644 --- a/lib/pages/camera/custom_camera_view.dart +++ b/lib/pages/camera/custom_camera_view.dart @@ -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 { CameraController? _controller; + bool _isCameraAvailable = true; + late List _cameras; int _selectedCameraIndex = 0; double _currentScale = 1.0; @@ -31,10 +44,25 @@ class _CustomCameraViewState extends State { } Future _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 { } 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 _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 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 { ], ); } + + 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); + } } \ No newline at end of file diff --git a/lib/pages/destination/destination_controller.dart b/lib/pages/destination/destination_controller.dart index 931657f..259e911 100644 --- a/lib/pages/destination/destination_controller.dart +++ b/lib/pages/destination/destination_controller.dart @@ -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で、最後にゴールしてから24時間経過していれば、"); + debugPrint("**1: 目的地がない場合で、スタート地点。開始CPで、最後にゴールしてから24時間経過していれば、"); 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 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 pushGPStoServer() async { @@ -1089,55 +1122,46 @@ class DestinationController extends GetxController { } } - Future _saveImageToGallery(String imagePath) async { + Future _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 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(); diff --git a/lib/pages/entry/entry_binding.dart b/lib/pages/entry/entry_binding.dart index b29ec11..2937251 100644 --- a/lib/pages/entry/entry_binding.dart +++ b/lib/pages/entry/entry_binding.dart @@ -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() { diff --git a/lib/pages/entry/entry_controller.dart b/lib/pages/entry/entry_controller.dart index 4e71d86..ea30eca 100644 --- a/lib/pages/entry/entry_controller.dart +++ b/lib/pages/entry/entry_controller.dart @@ -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; } diff --git a/lib/pages/history/history_page.dart b/lib/pages/history/history_page.dart index acc462c..a85e1ed 100644 --- a/lib/pages/history/history_page.dart +++ b/lib/pages/history/history_page.dart @@ -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 { DatabaseHelper db = DatabaseHelper.instance; + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("pass_history".tr), + ), + body: FutureBuilder>( + 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 { + DatabaseHelper db = DatabaseHelper.instance; + @override Widget build(BuildContext context) { return Scaffold( @@ -86,8 +130,97 @@ class _HistoryPageState extends State { ); } } +*/ 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( + 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 _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 { ); } } +*/ \ No newline at end of file diff --git a/lib/pages/index/index_binding.dart b/lib/pages/index/index_binding.dart index 3d4d69c..1bf2878 100644 --- a/lib/pages/index/index_binding.dart +++ b/lib/pages/index/index_binding.dart @@ -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()); - //Get.put(IndexController()); - Get.put(LocationController()); - Get.put(DestinationController()); + //Get.lazyPut(() => IndexController()); + ////Get.put(IndexController()); + //Get.put(LocationController()); + //Get.put(DestinationController()); + + Get.put(IndexController(apiService: Get.find()), permanent: true); + Get.put(LocationController(), permanent: true); + Get.put(DestinationController(), permanent: true); } } diff --git a/lib/pages/index/index_controller.dart b/lib/pages/index/index_controller.dart index aa43b9b..7b934ae 100644 --- a/lib/pages/index/index_controller.dart +++ b/lib/pages/index/index_controller.dart @@ -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 locations = [].obs; List currentFeature = [].obs; @@ -68,9 +71,14 @@ class IndexController extends GetxController with WidgetsBindingObserver { String? userToken; //late final ApiService _apiService; - final ApiService _apiService = Get.find(); + final ApiService _apiService; // = Get.find(); 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 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 _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 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 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 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.populateDestinations(); @@ -619,28 +686,48 @@ class IndexController extends GetxController with WidgetsBindingObserver { } */ - void changeUser(Map value, {bool replace = true}) async{ - currentUser.clear(); - currentUser.add(value); - if (replace) { - saveToDevice(currentUser[0]["token"]); + Future changeUser(Map 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 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 fetchUserEventInfo() async { @@ -697,7 +784,7 @@ class IndexController extends GetxController with WidgetsBindingObserver { Future fetchTeamData() async { try { - Get.put(TeamController()); + Get.put(TeamController(apiService:Get.find())); // \"TeamController\" not found. You need to call \"Get.put(TeamController())\" or \"Get.lazyPut(()=>TeamController())\" final teamController = Get.find(); await teamController.fetchTeams(); @@ -945,7 +1032,7 @@ class IndexController extends GetxController with WidgetsBindingObserver { Future checkEntryData() async { // エントリーデータの有無をチェックするロジック - final teamController = TeamController(); + final teamController = TeamController(apiService:Get.find()); bool hasEntryData = teamController.checkIfUserHasEntryData(); if (!hasEntryData) { await showHelperDialog( diff --git a/lib/pages/index/index_page.dart b/lib/pages/index/index_page.dart index 883e45d..4813a16 100644 --- a/lib/pages/index/index_page.dart +++ b/lib/pages/index/index_page.dart @@ -38,10 +38,21 @@ class _IndexPageState extends State { 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 { ); }, ); + }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 { // IndexPage({Key? key}) : super(key: key); diff --git a/lib/pages/login/login_page.dart b/lib/pages/login/login_page.dart index c02aa6e..729ce3e 100644 --- a/lib/pages/login/login_page.dart +++ b/lib/pages/login/login_page.dart @@ -119,8 +119,7 @@ class _LoginPageState extends State { 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 { true; indexController.login( emailController.text, - passwordController.text, - context); + passwordController.text + ); }, color: Colors.indigoAccent[400], shape: RoundedRectangleBorder( @@ -271,37 +270,34 @@ class _LoginPageState extends State { ), ], ), - 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回は、岐阜県の令和5年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています", - style: TextStyle( - fontSize: 10.0, - ), + style: const TextStyle(fontSize: 10.0), + textAlign: TextAlign.center, ), ), - ), - ], + ], + ), ), ], ), diff --git a/lib/pages/login_popup/login_popup_page.dart b/lib/pages/login_popup/login_popup_page.dart index 5bba4f1..6f132b1 100644 --- a/lib/pages/login_popup/login_popup_page.dart +++ b/lib/pages/login_popup/login_popup_page.dart @@ -120,8 +120,8 @@ class LoginPopupPage extends StatelessWidget { true; indexController.login( emailController.text, - passwordController.text, - context); + passwordController.text + ); }, color: Colors.indigoAccent[400], shape: RoundedRectangleBorder( diff --git a/lib/pages/permission/permission.dart b/lib/pages/permission/permission.dart index 637bc51..073b935 100644 --- a/lib/pages/permission/permission.dart +++ b/lib/pages/permission/permission.dart @@ -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? _permissionCompleter; - static Future 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 checkAndRequestPermissions() async { if (_isRequestingPermission) { return _permissionCompleter!.future; @@ -27,13 +20,119 @@ class PermissionController { _isRequestingPermission = true; _permissionCompleter = Completer(); - 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 _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 _requestAllLocationPermissions() async { + await Permission.location.request(); + await Permission.locationWhenInUse.request(); + final alwaysStatus = await Permission.locationAlways.request(); + + return alwaysStatus == PermissionStatus.granted; + } + + static Future _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 _isAndroid13OrAbove() async { + if (Platform.isAndroid) { + final androidVersion = int.tryParse(Platform.operatingSystemVersion.split('.').first) ?? 0; + return androidVersion >= 13; + } + return false; + } + + + static Future 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 checkAndRequestPermissions_old() async { + if (_isRequestingPermission) { + return _permissionCompleter!.future; + } + + _isRequestingPermission = true; + _permissionCompleter = Completer(); + + 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 showLocationDisclosure() async { - return await Get.dialog( - AlertDialog( - title: const Text('位置情報の使用について'), - content: const SingleChildScrollView( - child: ListBody( - children: [ - 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( + AlertDialog( + title: const Text('位置情報の使用について'), + content: const SingleChildScrollView( + child: ListBody( + children: [ + Text('このアプリでは、以下の目的で位置情報を使用します:'), + Text( + '• チェックポイントの自動チェックイン(アプリが閉じているときも含む)'), + Text('• 移動履歴の記録(バックグラウンドでも継続)'), + Text('• 現在地周辺の情報表示'), + Text('\nバックグラウンドでも位置情報を継続的に取得します。'), + Text('これにより、バッテリーの消費が増加する可能性があります。'), + Text('同意しない場合には、アプリは終了します。'), + ], + ), ), + actions: [ + TextButton( + child: const Text('同意しない'), + onPressed: () => Get.back(result: false), + ), + TextButton( + child: const Text('同意する'), + onPressed: () => Get.back(result: true), + ), + ], ), - actions: [ - 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) { diff --git a/lib/pages/team/member_binding.dart b/lib/pages/team/member_binding.dart index b06e7d2..5d671ba 100644 --- a/lib/pages/team/member_binding.dart +++ b/lib/pages/team/member_binding.dart @@ -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() { diff --git a/lib/pages/team/team_binding.dart b/lib/pages/team/team_binding.dart index e41d819..a91a557 100644 --- a/lib/pages/team/team_binding.dart +++ b/lib/pages/team/team_binding.dart @@ -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()); - Get.lazyPut(() => TeamController()); + Get.lazyPut(() => TeamController( + apiService:Get.find()) + ); } } \ No newline at end of file diff --git a/lib/pages/team/team_controller.dart b/lib/pages/team/team_controller.dart index cae0cf8..9417baf 100644 --- a/lib/pages/team/team_controller.dart +++ b/lib/pages/team/team_controller.dart @@ -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 = [].obs; final categories = [].obs; @@ -35,12 +41,12 @@ class TeamController extends GetxController { void onInit() async { super.onInit(); try { - _apiService = Get.find(); + //_apiService = Get.find(); - if (!Get.isRegistered()) { - Get.put(EntryController()); - } - _entryController = Get.find(); + //if (!Get.isRegistered()) { + // Get.put(EntryController()); + //} + //_entryController = Get.find(); await loadInitialData(); } catch (e) { diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 243943e..94be685 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -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 init() async { try { + //if (!Get.isRegistered()) { + // Get.put(IndexController(apiService: Get.find())); + //} + // ここで必要な初期化処理を行う 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 getToken2 () async { // IndexControllerの初期化を待つ + if (!Get.isRegistered()) { + Get.find(); + } final indexController = Get.find(); - 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()) { + Get.find(); + } + final indexController = Get.find(); + if (indexController.currentUser.isNotEmpty) { + token = indexController.currentUser[0]['token'] ?? ''; + print("Get token = $token"); + }else{ + token = ""; + } + return token; + } + + Future _handleRequest(Future 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 _handleUnauthorized() async { + // トークンをクリアし、ユーザーをログアウトさせる + final indexController = Get.find(); + await indexController.logout(); + Get.offAllNamed(AppPages.LOGIN); + } + Future> 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> 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 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 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 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().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 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 createTeam_old(String teamName, int categoryId) async { init(); getToken(); @@ -268,6 +349,15 @@ class ApiService extends GetxService{ } Future 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 updateTeam_old(int teamId, String teamName, int categoryId) async { init(); getToken(); @@ -293,6 +383,14 @@ class ApiService extends GetxService{ } Future 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 deleteTeamold_(int teamId) async { init(); getToken(); @@ -311,6 +409,14 @@ class ApiService extends GetxService{ } Future> 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> getTeamMembers_old(int teamId) async { init(); getToken(); @@ -330,7 +436,23 @@ class ApiService extends GetxService{ } } - Future createTeamMember(int teamId, String? email, String? firstname, String? lastname, DateTime? dateOfBirth,bool? female) async { + Future 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 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 updateTeamMember(int teamId,int? memberId, String firstname, String lastname, DateTime? dateOfBirth,bool? female) async { + Future 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 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 deleteTeamMember(int teamId,int memberId) async { + Future 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 deleteTeamMember_old(int teamId,int memberId) async { init(); getToken(); @@ -442,6 +587,14 @@ class ApiService extends GetxService{ } Future> 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> getEntries_old() async { init(); getToken(); @@ -500,7 +653,23 @@ class ApiService extends GetxService{ } } - Future createEntry(int teamId, int eventId, int categoryId, DateTime date,String zekkenNumber) async { + Future 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 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 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 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 error_dict = blk[0]['error']; + String ? error_message = error_dict['non_field_errors'][0].string; + + throw Exception(error_message); } } diff --git a/lib/services/external_service.dart b/lib/services/external_service.dart index 4235c8f..3b5b6ca 100644 --- a/lib/services/external_service.dart +++ b/lib/services/external_service.dart @@ -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 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 diff --git a/lib/utils/database_helper.dart b/lib/utils/database_helper.dart index 08b22f2..05e53e2 100644 --- a/lib/utils/database_helper.dart +++ b/lib/utils/database_helper.dart @@ -13,6 +13,11 @@ class DatabaseHelper { static Database? _database; Future get database async => _database ??= await _initDatabase(); + // データベース初期化: + // + // シングルトンパターンを使用してDatabaseHelperのインスタンスを管理しています。 + // _initDatabase()メソッドでデータベースを初期化し、必要なテーブルを作成します。 + // Future _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 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 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 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 isAlreadyAvailable(int locationId) async { Database db = await instance.database; var dest = diff --git a/lib/utils/location_controller.dart b/lib/utils/location_controller.dart index dad666d..724b7a6 100644 --- a/lib/utils/location_controller.dart +++ b/lib/utils/location_controller.dart @@ -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 _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 // 位置情報サービスの有効性をチェックし、無効な場合はエラーハンドリングを行います。 // diff --git a/lib/utils/memory_monitor.dart b/lib/utils/memory_monitor.dart new file mode 100644 index 0000000..61f645c --- /dev/null +++ b/lib/utils/memory_monitor.dart @@ -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 _getCurrentMemoryUsage() async { + // プラットフォーム固有のメモリ使用量取得ロジックを実装 + // この例では仮の値を返しています + return 150 * 1024 * 1024; // 仮に150MBとする +} \ No newline at end of file diff --git a/lib/widgets/bottom_sheet_new.dart b/lib/widgets/bottom_sheet_new.dart index 40c552b..ac41767 100644 --- a/lib/widgets/bottom_sheet_new.dart +++ b/lib/widgets/bottom_sheet_new.dart @@ -175,7 +175,12 @@ class BottomSheetNew extends GetView { 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 { //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 { } : null, child: Text( - "finish_rogaining".tr, + "finish_rogaining".tr, // ロゲゴール style: const TextStyle(color: Colors.white), )); @@ -353,6 +366,9 @@ class BottomSheetNew extends GetView { 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 { }, 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 diff --git a/lib/widgets/game_state_view.dart b/lib/widgets/game_state_view.dart index cee38a7..26432b4 100644 --- a/lib/widgets/game_state_view.dart +++ b/lib/widgets/game_state_view.dart @@ -151,26 +151,23 @@ class _GameStateWidgetState extends State { padding: const EdgeInsets.all(4.0), child: StreamBuilder>( 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 { 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 diff --git a/lib/widgets/helper_dialog.dart b/lib/widgets/helper_dialog.dart index d0ad208..d873603 100644 --- a/lib/widgets/helper_dialog.dart +++ b/lib/widgets/helper_dialog.dart @@ -26,7 +26,8 @@ class _HelperDialogState extends State { Text('ヘルプ'), ], ), - content: Column( + content: SingleChildScrollView( + child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -42,11 +43,17 @@ class _HelperDialogState extends State { }); }, ), - const Text('この画面を二度と表示しない'), + const Flexible( + child: Text( + 'この画面を二度と表示しない', + overflow: TextOverflow.ellipsis, + ), + ), ], ), ], ), + ), actions: [ TextButton( child: const Text('OK'), @@ -68,6 +75,9 @@ Future 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, + ); } } \ No newline at end of file diff --git a/lib/widgets/map_widget.dart b/lib/widgets/map_widget.dart index d807237..b0ede84 100644 --- a/lib/widgets/map_widget.dart +++ b/lib/widgets/map_widget.dart @@ -52,6 +52,12 @@ class _MapWidgetState extends State 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 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); } diff --git a/lib/widgets/permission_handler_screen.dart b/lib/widgets/permission_handler_screen.dart index 3a0b52e..337a898 100644 --- a/lib/widgets/permission_handler_screen.dart +++ b/lib/widgets/permission_handler_screen.dart @@ -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 { appBar: AppBar( title: const Text('権限の確認'), ), - body: const Center( - child: Text('権限の確認中...'), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('権限の確認中...'), + const SizedBox(height: 20), + ], + ), ), ); } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index a347b3d..bbe28e6 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import connectivity_plus +import device_info_plus import file_selector_macos import geolocator_apple import package_info_plus @@ -17,6 +18,7 @@ import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 9dd9c98..93dbaab 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -185,6 +185,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.10" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 + url: "https://pub.dev" + source: hosted + version: "10.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" + url: "https://pub.dev" + source: hosted + version: "7.0.1" fake_async: dependency: transitive description: @@ -802,7 +818,7 @@ packages: source: hosted version: "3.0.1" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" @@ -1314,10 +1330,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" web: dependency: transitive description: @@ -1366,6 +1382,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.5.4" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" + url: "https://pub.dev" + source: hosted + version: "1.1.4" wkt_parser: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 701a89c..888b7bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,9 @@ dependencies: # rules and activating additional ones. flutter_lints: ^4.0.0 + path: ^1.8.0 + device_info_plus: #^8.0.0 + win32: ^5.5.3 sqflite: ^2.0.1 get: ^4.6.6 diff --git a/テスト端末.xlsx b/テスト端末.xlsx new file mode 100644 index 0000000..469853c Binary files /dev/null and b/テスト端末.xlsx differ