From ed1a6e4bc3201279581d71c1239d24c519cf9f41 Mon Sep 17 00:00:00 2001 From: lowrt Date: Wed, 31 Dec 2025 17:31:48 +0800 Subject: [PATCH 01/13] feat: shortcut --- ios/Runner/AppDelegate.swift | 59 ++++++++++++++++++++++++--- ios/Runner/Info.plist | 21 ++++++++-- ios/Runner/Runner.entitlements | 2 + ios/Runner/RunnerProfile.entitlements | 2 + lib/app.dart | 47 ++++++++++++++++----- lib/main.dart | 16 +++++++- 6 files changed, 125 insertions(+), 22 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 9d977a1cd..7968d9be9 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -16,15 +16,15 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { private var backgroundTask: UIBackgroundTaskIdentifier = .invalid // MARK: - Application Lifecycle - + override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication - .LaunchOptionsKey: Any]? - ) -> Bool { + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { GeneratedPluginRegistrant.register(with: self) setupFlutterChannels() setupLocationManager() + setupSiriShortcut() if let locationKey = launchOptions?[ UIApplication.LaunchOptionsKey.location] as? NSNumber, @@ -34,10 +34,31 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { } else if isLocationEnabled { startLocationUpdates() } - + return super.application( application, didFinishLaunchingWithOptions: launchOptions) } + + override func application( + _ application: UIApplication, + performActionFor shortcutItem: UIApplicationShortcutItem, + completionHandler: @escaping (Bool) -> Void + ) { + handleShortcut(shortcutItem) + completionHandler(true) + } + + private func handleShortcut(_ shortcutItem: UIApplicationShortcutItem) { + UserDefaults.standard.set(shortcutItem.type, forKey: "initialShortcut") + } + + override func application(_ application: UIApplication, continue userActivity: NSUserActivity, + restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + if userActivity.activityType == "com.exptech.dpip.monitor" { + UserDefaults.standard.set("monitor", forKey: "initialShortcut") + } + return true + } override func applicationDidEnterBackground(_ application: UIApplication) { startBackgroundTask() @@ -59,6 +80,21 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { locationChannel?.setMethodCallHandler { [weak self] (call, result) in self?.handleLocationChannelCall(call, result: result) } + + let shortcutChannel = FlutterMethodChannel( + name: "com.exptech.dpip/shortcut", + binaryMessenger: controller.binaryMessenger + ) + + shortcutChannel.setMethodCallHandler { call, result in + if call.method == "getInitialShortcut" { + let shortcut = UserDefaults.standard.string(forKey: "initialShortcut") + result(shortcut) + UserDefaults.standard.removeObject(forKey: "initialShortcut") + } else { + result(FlutterMethodNotImplemented) + } + } } private func setupLocationManager() { @@ -70,6 +106,17 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { forKey: "locationSendingEnabled") } + func setupSiriShortcut() { + let activity = NSUserActivity(activityType: "com.exptech.dpip.monitor") + activity.title = "打開強震監視器" + activity.isEligibleForSearch = true + activity.isEligibleForPrediction = true + activity.persistentIdentifier = NSUserActivityPersistentIdentifier("monitor") + + self.userActivity = activity + activity.becomeCurrent() + } + // MARK: - APNS Token Handling override func application( diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 9130a9506..ed30c27c4 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -41,6 +41,10 @@ LSRequiresIPhoneOS + NSBonjourServices + + ${DART_DEBUG_BONJOUR_SERVICE} + NSLocationAlwaysAndWhenInUseUsageDescription DPIP 將利用你的位置資訊,用以自動設定所在地並在地震發生時能較準確地預估所在地的最大震度。 NSLocationAlwaysUsageDescription @@ -51,6 +55,19 @@ 用於儲存地震報告圖片。 NSPhotoLibraryUsageDescription 用於儲存地震報告圖片至您的相簿。 + NSSiriUsageDescription + 用於打開捷徑。 + UIApplicationShortcutItems + + + UIApplicationShortcutItemIconType + UIApplicationShortcutIconTypeFavorite + UIApplicationShortcutItemTitle + 強震監視器 + UIApplicationShortcutItemType + monitor + + UIApplicationSupportsIndirectInputEvents UIBackgroundModes @@ -74,9 +91,5 @@ UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - NSBonjourServices - - ${DART_DEBUG_BONJOUR_SERVICE} - diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements index be4adcbe1..1e44dd1d2 100644 --- a/ios/Runner/Runner.entitlements +++ b/ios/Runner/Runner.entitlements @@ -4,6 +4,8 @@ aps-environment development + com.apple.developer.siri + com.apple.developer.usernotifications.critical-alerts diff --git a/ios/Runner/RunnerProfile.entitlements b/ios/Runner/RunnerProfile.entitlements index be4adcbe1..1e44dd1d2 100644 --- a/ios/Runner/RunnerProfile.entitlements +++ b/ios/Runner/RunnerProfile.entitlements @@ -4,6 +4,8 @@ aps-environment development + com.apple.developer.siri + com.apple.developer.usernotifications.critical-alerts diff --git a/lib/app.dart b/lib/app.dart index bc827c5aa..720bc5306 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -19,6 +19,8 @@ import 'package:in_app_update/in_app_update.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; +import 'app/map/_lib/utils.dart'; +import 'app/map/page.dart'; import 'main.dart'; /// The root widget of the application. @@ -27,7 +29,8 @@ import 'main.dart'; /// infrastructure. class DpipApp extends StatefulWidget { /// Creates a new [DpipApp] instance. - const DpipApp({super.key}); + final String? initialShortcut; + const DpipApp({super.key, this.initialShortcut}); @override State createState() => _DpipAppState(); @@ -35,6 +38,7 @@ class DpipApp extends StatefulWidget { class _DpipAppState extends State with WidgetsBindingObserver { bool _hasHandledPendingNotification = false; + bool _hasHandledInitialShortcut = false; Future _checkUpdate() async { try { @@ -78,13 +82,38 @@ class _DpipAppState extends State with WidgetsBindingObserver { } } - void _handlePendingNotificationWhenReady() { - if (_hasHandledPendingNotification) return; + void _tryHandleStartupNavigation() { + if (!mounted) return; - final context = router.routerDelegate.navigatorKey.currentContext; - if (context != null) { + final ctx = router.routerDelegate.navigatorKey.currentContext; + if (ctx == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _tryHandleStartupNavigation(); + }); + return; + } + + if (!_hasHandledInitialShortcut && widget.initialShortcut != null) { + _hasHandledInitialShortcut = true; + + switch (widget.initialShortcut) { + case 'monitor': + ctx.push( + MapPage.route( + options: MapPageOptions( + initialLayers: {MapLayer.monitor}, + ), + ), + ); + return; + } + } + + if (!_hasHandledPendingNotification) { _hasHandledPendingNotification = true; - handlePendingNotificationNavigation(context); + Future.delayed(const Duration(milliseconds: 500), () { + handlePendingNotificationNavigation(ctx); + }); } } @@ -101,12 +130,9 @@ class _DpipAppState extends State with WidgetsBindingObserver { WidgetsBinding.instance.addObserver(this); GlobalProviders.data.startFetching(); _checkNotificationPermission(); - router.routerDelegate.addListener(_handlePendingNotificationWhenReady); WidgetsBinding.instance.addPostFrameCallback((_) { - Future.delayed(const Duration(milliseconds: 500), () { - _handlePendingNotificationWhenReady(); - }); + _tryHandleStartupNavigation(); }); } @@ -194,7 +220,6 @@ class _DpipAppState extends State with WidgetsBindingObserver { @override void dispose() { - router.routerDelegate.removeListener(_handlePendingNotificationWhenReady); WidgetsBinding.instance.removeObserver(this); super.dispose(); } diff --git a/lib/main.dart b/lib/main.dart index e4215233d..3b2002656 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,6 +29,7 @@ void main() async { talker.log('--- 冷啟動偵測開始 ---'); talker.log('🔥 1. (main) 啟動時間: ${overallStartTime.toIso8601String()}'); WidgetsFlutterBinding.ensureInitialized(); + String? initialShortcut; if (Platform.isIOS) { // iOS 14 以下改回用 StoreKit1 InAppPurchaseStoreKitPlatform.enableStoreKit1(); @@ -61,6 +62,7 @@ void main() async { final isFirstLaunch = Preference.instance.getBool('isFirstLaunch') ?? true; GlobalProviders.init(); initializeTimeZones(); + initialShortcut = await getInitialShortcut(); talker.log('⏳ 3. 啟動 並行任務... (測量總耗時)'); final futureWaitStart = DateTime.now(); @@ -136,7 +138,7 @@ void main() async { ChangeNotifierProvider.value(value: GlobalProviders.notification), ChangeNotifierProvider.value(value: GlobalProviders.ui), ], - child: const DpipApp(), + child: DpipApp(initialShortcut: initialShortcut), ), ), ); @@ -172,6 +174,18 @@ void main() async { }); } +const platform = MethodChannel('com.exptech.dpip/shortcut'); + +Future getInitialShortcut() async { + try { + final result = await platform.invokeMethod('getInitialShortcut'); + return result; + } on PlatformException catch (e) { + print('Failed to get initial shortcut: $e'); + return null; + } +} + Future _loggedTask(String taskName, Future future) async { final start = DateTime.now(); try { From e3d44840772eb0a8049d674d7db839963202a520 Mon Sep 17 00:00:00 2001 From: lowrt Date: Wed, 31 Dec 2025 21:23:53 +0800 Subject: [PATCH 02/13] fix(ios/Runner): swift --- ios/Runner.xcodeproj/project.pbxproj | 2 +- ios/Runner/AppDelegate.swift | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 3d2985742..ebf9ed5df 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -285,7 +285,7 @@ }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; + compatibilityVersion = "Xcode 13.0"; developmentRegion = zh_TW; hasScannedForEncodings = 0; knownRegions = ( diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 7968d9be9..253ddab63 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,6 +1,7 @@ import CoreLocation import Flutter import UIKit +import Intents import UserNotifications @UIApplicationMain @@ -18,9 +19,9 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { // MARK: - Application Lifecycle override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { GeneratedPluginRegistrant.register(with: self) setupFlutterChannels() setupLocationManager() @@ -52,7 +53,9 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { UserDefaults.standard.set(shortcutItem.type, forKey: "initialShortcut") } - override func application(_ application: UIApplication, continue userActivity: NSUserActivity, + override func application( + _ application: UIApplication, + continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if userActivity.activityType == "com.exptech.dpip.monitor" { UserDefaults.standard.set("monitor", forKey: "initialShortcut") @@ -108,10 +111,11 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { func setupSiriShortcut() { let activity = NSUserActivity(activityType: "com.exptech.dpip.monitor") - activity.title = "打開強震監視器" + activity.title = "強震監視器" activity.isEligibleForSearch = true activity.isEligibleForPrediction = true activity.persistentIdentifier = NSUserActivityPersistentIdentifier("monitor") + activity.suggestedInvocationPhrase = "強震監視器" self.userActivity = activity activity.becomeCurrent() From 7f383497810bf68a35bd7300d2b0d393e7b6544d Mon Sep 17 00:00:00 2001 From: lowrt Date: Wed, 31 Dec 2025 21:29:38 +0800 Subject: [PATCH 03/13] feat: Android shortcut --- android/app/src/main/AndroidManifest.xml | 12 ++++- .../kotlin/com/exptech/dpip/MainActivity.kt | 45 ++++++++++++++++++- android/app/src/main/res/values/strings.xml | 2 + android/app/src/main/res/xml/shortcuts.xml | 17 +++++++ 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 android/app/src/main/res/xml/shortcuts.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1431169a6..e54bd9adb 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -42,10 +42,18 @@ android:screenOrientation="portrait" android:showWhenLocked="true" android:turnScreenOn="true"> + + + + + + + android:resource="@style/NormalTheme"/> + diff --git a/android/app/src/main/kotlin/com/exptech/dpip/MainActivity.kt b/android/app/src/main/kotlin/com/exptech/dpip/MainActivity.kt index a751c415c..92fdaf9f1 100644 --- a/android/app/src/main/kotlin/com/exptech/dpip/MainActivity.kt +++ b/android/app/src/main/kotlin/com/exptech/dpip/MainActivity.kt @@ -1,5 +1,48 @@ package com.exptech.dpip +import android.content.Intent +import android.os.Bundle import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel -class MainActivity : FlutterActivity() +class MainActivity : FlutterActivity() { + + private val CHANNEL = "com.exptech.dpip/shortcut" + + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) + .setMethodCallHandler { call, result -> + if (call.method == "getInitialShortcut") { + val shortcut = getSharedPreferences("shortcut", MODE_PRIVATE) + .getString("initialShortcut", null) + result.success(shortcut) + } else { + result.notImplemented() + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + handleIntent(intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + handleIntent(intent) + } + + private fun handleIntent(intent: Intent?) { + val shortcut = intent?.getStringExtra("shortcut") + if (shortcut != null) { + getSharedPreferences("shortcut", MODE_PRIVATE) + .edit() + .putString("initialShortcut", shortcut) + .apply() + } + } +} diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 69de199fa..756f65700 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,3 +1,5 @@ DPIP + 強震監視器 + 打開強震監視器 \ No newline at end of file diff --git a/android/app/src/main/res/xml/shortcuts.xml b/android/app/src/main/res/xml/shortcuts.xml new file mode 100644 index 000000000..ecdc3046a --- /dev/null +++ b/android/app/src/main/res/xml/shortcuts.xml @@ -0,0 +1,17 @@ + + + + + + + From d3072b6a0cd8565b67b4e722379ecb7e8f22077d Mon Sep 17 00:00:00 2001 From: lowrt Date: Wed, 31 Dec 2025 22:43:20 +0800 Subject: [PATCH 04/13] Fix: Repeatedly opening the same page --- android/app/src/main/AndroidManifest.xml | 6 ----- .../kotlin/com/exptech/dpip/MainActivity.kt | 24 +++++++++---------- android/app/src/main/res/xml/shortcuts.xml | 6 ++--- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e54bd9adb..7ce8f64cf 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -42,12 +42,6 @@ android:screenOrientation="portrait" android:showWhenLocked="true" android:turnScreenOn="true"> - - - - - - diff --git a/android/app/src/main/kotlin/com/exptech/dpip/MainActivity.kt b/android/app/src/main/kotlin/com/exptech/dpip/MainActivity.kt index 92fdaf9f1..dbae82c6e 100644 --- a/android/app/src/main/kotlin/com/exptech/dpip/MainActivity.kt +++ b/android/app/src/main/kotlin/com/exptech/dpip/MainActivity.kt @@ -9,18 +9,19 @@ import io.flutter.plugin.common.MethodChannel class MainActivity : FlutterActivity() { private val CHANNEL = "com.exptech.dpip/shortcut" + private var initialShortcut: String? = null override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) .setMethodCallHandler { call, result -> - if (call.method == "getInitialShortcut") { - val shortcut = getSharedPreferences("shortcut", MODE_PRIVATE) - .getString("initialShortcut", null) - result.success(shortcut) - } else { - result.notImplemented() + when (call.method) { + "getInitialShortcut" -> { + result.success(initialShortcut) + initialShortcut = null + } + else -> result.notImplemented() } } } @@ -37,12 +38,11 @@ class MainActivity : FlutterActivity() { } private fun handleIntent(intent: Intent?) { - val shortcut = intent?.getStringExtra("shortcut") - if (shortcut != null) { - getSharedPreferences("shortcut", MODE_PRIVATE) - .edit() - .putString("initialShortcut", shortcut) - .apply() + intent ?: return + val shortcutId = intent.getStringExtra("android.intent.extra.shortcut.ID") + ?: intent.getStringExtra("shortcut") + if (!shortcutId.isNullOrEmpty()) { + initialShortcut = shortcutId } } } diff --git a/android/app/src/main/res/xml/shortcuts.xml b/android/app/src/main/res/xml/shortcuts.xml index ecdc3046a..60d916fb1 100644 --- a/android/app/src/main/res/xml/shortcuts.xml +++ b/android/app/src/main/res/xml/shortcuts.xml @@ -8,10 +8,8 @@ - + android:targetClass="com.exptech.dpip.MainActivity" > + From 81746483e2a73aee18459ca43ff7c465b9df293e Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 1 Jan 2026 11:42:33 +0800 Subject: [PATCH 05/13] fix: Ios shortcut --- ios/Runner.xcodeproj/project.pbxproj | 7 +++ ios/Runner/AppDelegate.swift | 22 +++++++- ios/Runner/Info.plist | 7 ++- ios/Runner/Monitor.intentdefinition | 79 +++++++++++++++++++++++++++ ios/Runner/Runner.entitlements | 2 - ios/Runner/RunnerProfile.entitlements | 2 - lib/app.dart | 56 ++++++++++++++++++- 7 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 ios/Runner/Monitor.intentdefinition diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index ebf9ed5df..e07279c23 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 5285489F2F06028F0027C627 /* Monitor.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 5285489E2F06028F0027C627 /* Monitor.intentdefinition */; }; 529C27C82C93F7B200AAFAB6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 529C27C62C93F7B200AAFAB6 /* InfoPlist.strings */; }; 52FA5E152DC8A9EA0008FEB0 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52FA5E142DC8A9EA0008FEB0 /* StoreKit.framework */; }; 6037C7C000DE0752E32B9E54 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6838AADB908B55100C5691B /* Pods_Runner.framework */; }; @@ -66,6 +67,8 @@ 4B4531DD011F7A688ACAA691 /* 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 = ""; }; 5228AD5A2C2EE45D007635F5 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 523A5FD82EB21EC0006F93FC /* Profile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Profile.xcconfig; path = Flutter/Profile.xcconfig; sourceTree = ""; }; + 5285489E2F06028F0027C627 /* Monitor.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Monitor.intentdefinition; sourceTree = ""; }; + 528548A52F06047F0027C627 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; 529C27C92C93F7B900AAFAB6 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/InfoPlist.strings; sourceTree = ""; }; 529C27CC2C93F7BC00AAFAB6 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = ""; }; 529C27CF2C947EFB00AAFAB6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -205,6 +208,7 @@ 632125282C2EA17900A088F8 /* GoogleService-Info.plist */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 529C27C62C93F7B200AAFAB6 /* InfoPlist.strings */, + 5285489E2F06028F0027C627 /* Monitor.intentdefinition */, ); path = Runner; sourceTree = ""; @@ -215,6 +219,7 @@ 52FA5E142DC8A9EA0008FEB0 /* StoreKit.framework */, C6838AADB908B55100C5691B /* Pods_Runner.framework */, 8A29CCFC3290C53FDF724288 /* Pods_RunnerTests.framework */, + 528548A52F06047F0027C627 /* Intents.framework */, ); name = Frameworks; sourceTree = ""; @@ -271,6 +276,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 2610; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -470,6 +476,7 @@ files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 5285489F2F06028F0027C627 /* Monitor.intentdefinition in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 253ddab63..38abd20e1 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -51,16 +51,32 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { private func handleShortcut(_ shortcutItem: UIApplicationShortcutItem) { UserDefaults.standard.set(shortcutItem.type, forKey: "initialShortcut") + notifyFlutterShortcut(shortcutItem.type) } override func application( _ application: UIApplication, continue userActivity: NSUserActivity, - restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { - if userActivity.activityType == "com.exptech.dpip.monitor" { + restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void + ) -> Bool { + if userActivity.activityType == "com.exptech.dpip.monitor" || + userActivity.activityType == "OpenMonitorIntentIntent" { UserDefaults.standard.set("monitor", forKey: "initialShortcut") + notifyFlutterShortcut("monitor") } - return true + return super.application(application, continue: userActivity, restorationHandler: restorationHandler) + } + + private func notifyFlutterShortcut(_ value: String) { + guard let controller = + window?.rootViewController as? FlutterViewController + else { return } + + let channel = FlutterMethodChannel( + name: "com.exptech.dpip/shortcut", + binaryMessenger: controller.binaryMessenger + ) + channel.invokeMethod("onShortcut", arguments: value) } override func applicationDidEnterBackground(_ application: UIApplication) { diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index ed30c27c4..d19f8f31a 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -55,8 +55,11 @@ 用於儲存地震報告圖片。 NSPhotoLibraryUsageDescription 用於儲存地震報告圖片至您的相簿。 - NSSiriUsageDescription - 用於打開捷徑。 + NSUserActivityTypes + + OpenMonitorIntentIntent + com.exptech.dpip.monitor + UIApplicationShortcutItems diff --git a/ios/Runner/Monitor.intentdefinition b/ios/Runner/Monitor.intentdefinition new file mode 100644 index 000000000..974080470 --- /dev/null +++ b/ios/Runner/Monitor.intentdefinition @@ -0,0 +1,79 @@ + + + + + INEnums + + INIntentDefinitionModelVersion + 1.2 + INIntentDefinitionNamespace + tuhqJ3 + INIntentDefinitionSystemVersion + 25B78 + INIntentDefinitionToolsBuildVersion + 17B100 + INIntentDefinitionToolsVersion + 26.1.1 + INIntents + + + INIntentCategory + information + INIntentConfigurable + + INIntentDescription + 此捷徑直接開啟強震監視器畫面 + INIntentDescriptionID + l2uH53 + INIntentManagedParameterCombinations + + + + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationUpdatesLinked + + + + INIntentName + OpenMonitorIntent + INIntentParameterCombinations + + + + INIntentParameterCombinationIsPrimary + + INIntentParameterCombinationSupportsBackgroundExecution + + + + INIntentResponse + + INIntentResponseCodes + + + INIntentResponseCodeName + success + INIntentResponseCodeSuccess + + + + INIntentResponseCodeName + failure + + + + INIntentTitle + 開啟強震監視器 + INIntentTitleID + qno9IY + INIntentType + Custom + INIntentVerb + View + + + INTypes + + + diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements index 1e44dd1d2..be4adcbe1 100644 --- a/ios/Runner/Runner.entitlements +++ b/ios/Runner/Runner.entitlements @@ -4,8 +4,6 @@ aps-environment development - com.apple.developer.siri - com.apple.developer.usernotifications.critical-alerts diff --git a/ios/Runner/RunnerProfile.entitlements b/ios/Runner/RunnerProfile.entitlements index 1e44dd1d2..be4adcbe1 100644 --- a/ios/Runner/RunnerProfile.entitlements +++ b/ios/Runner/RunnerProfile.entitlements @@ -4,8 +4,6 @@ aps-environment development - com.apple.developer.siri - com.apple.developer.usernotifications.critical-alerts diff --git a/lib/app.dart b/lib/app.dart index 720bc5306..07b224c35 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -12,6 +12,7 @@ import 'package:dpip/utils/log.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:i18n_extension/i18n_extension.dart'; @@ -39,6 +40,7 @@ class DpipApp extends StatefulWidget { class _DpipAppState extends State with WidgetsBindingObserver { bool _hasHandledPendingNotification = false; bool _hasHandledInitialShortcut = false; + static const _shortcutChannel = MethodChannel('com.exptech.dpip/shortcut'); Future _checkUpdate() async { try { @@ -117,6 +119,35 @@ class _DpipAppState extends State with WidgetsBindingObserver { } } + void _onEnterMonitorPage() { + if (Platform.isIOS) { + _shortcutChannel.invokeMethod('donateMonitor'); + } + } + + void _handleShortcutNavigation(String shortcut) { + final ctx = router.routerDelegate.navigatorKey.currentContext; + if (ctx == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _handleShortcutNavigation(shortcut); + }); + return; + } + + switch (shortcut) { + case 'monitor': + ctx.go( + MapPage.route( + options: MapPageOptions( + initialLayers: {MapLayer.monitor}, + ), + ), + ); + break; + } + } + + @override void didChangeAppLifecycleState(AppLifecycleState state) { GlobalProviders.data.onAppLifecycleStateChanged(state); @@ -130,12 +161,35 @@ class _DpipAppState extends State with WidgetsBindingObserver { WidgetsBinding.instance.addObserver(this); GlobalProviders.data.startFetching(); _checkNotificationPermission(); + _onEnterMonitorPage(); + + _shortcutChannel.setMethodCallHandler((call) async { + if (call.method == 'onShortcut') { + final value = call.arguments as String?; + if (value != null && mounted) { + _handleShortcutNavigation(value); + } + } + }); WidgetsBinding.instance.addPostFrameCallback((_) { - _tryHandleStartupNavigation(); + _handleInitialShortcut(); }); } + void _handleInitialShortcut() { + if (_hasHandledInitialShortcut) return; + _hasHandledInitialShortcut = true; + + final shortcut = widget.initialShortcut; + if (shortcut != null && shortcut.isNotEmpty) { + _handleShortcutNavigation(shortcut); + return; + } + + _tryHandleStartupNavigation(); + } + @override Widget build(BuildContext context) { return DynamicColorBuilder( From b3b3e34c0f5343b9e0ee98346932c17216b1518f Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 1 Jan 2026 11:51:53 +0800 Subject: [PATCH 06/13] fix: Siri --- ios/Runner/AppDelegate.swift | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 38abd20e1..6afac8e9a 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -25,7 +25,6 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { GeneratedPluginRegistrant.register(with: self) setupFlutterChannels() setupLocationManager() - setupSiriShortcut() if let locationKey = launchOptions?[ UIApplication.LaunchOptionsKey.location] as? NSNumber, @@ -39,7 +38,8 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { return super.application( application, didFinishLaunchingWithOptions: launchOptions) } - + + // MARK: - Quick Action override func application( _ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, @@ -54,6 +54,7 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { notifyFlutterShortcut(shortcutItem.type) } + // MARK: - NSUserActivity override func application( _ application: UIApplication, continue userActivity: NSUserActivity, @@ -79,6 +80,7 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { channel.invokeMethod("onShortcut", arguments: value) } + // MARK: - Background Handling override func applicationDidEnterBackground(_ application: UIApplication) { startBackgroundTask() } @@ -125,18 +127,6 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { forKey: "locationSendingEnabled") } - func setupSiriShortcut() { - let activity = NSUserActivity(activityType: "com.exptech.dpip.monitor") - activity.title = "強震監視器" - activity.isEligibleForSearch = true - activity.isEligibleForPrediction = true - activity.persistentIdentifier = NSUserActivityPersistentIdentifier("monitor") - activity.suggestedInvocationPhrase = "強震監視器" - - self.userActivity = activity - activity.becomeCurrent() - } - // MARK: - APNS Token Handling override func application( From 62da593902becc60bf8512bd34d4b29cfc591e33 Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 1 Jan 2026 12:10:58 +0800 Subject: [PATCH 07/13] style: unused code --- lib/app.dart | 56 +--------------------------------------------------- 1 file changed, 1 insertion(+), 55 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 07b224c35..720bc5306 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -12,7 +12,6 @@ import 'package:dpip/utils/log.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:i18n_extension/i18n_extension.dart'; @@ -40,7 +39,6 @@ class DpipApp extends StatefulWidget { class _DpipAppState extends State with WidgetsBindingObserver { bool _hasHandledPendingNotification = false; bool _hasHandledInitialShortcut = false; - static const _shortcutChannel = MethodChannel('com.exptech.dpip/shortcut'); Future _checkUpdate() async { try { @@ -119,35 +117,6 @@ class _DpipAppState extends State with WidgetsBindingObserver { } } - void _onEnterMonitorPage() { - if (Platform.isIOS) { - _shortcutChannel.invokeMethod('donateMonitor'); - } - } - - void _handleShortcutNavigation(String shortcut) { - final ctx = router.routerDelegate.navigatorKey.currentContext; - if (ctx == null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - _handleShortcutNavigation(shortcut); - }); - return; - } - - switch (shortcut) { - case 'monitor': - ctx.go( - MapPage.route( - options: MapPageOptions( - initialLayers: {MapLayer.monitor}, - ), - ), - ); - break; - } - } - - @override void didChangeAppLifecycleState(AppLifecycleState state) { GlobalProviders.data.onAppLifecycleStateChanged(state); @@ -161,35 +130,12 @@ class _DpipAppState extends State with WidgetsBindingObserver { WidgetsBinding.instance.addObserver(this); GlobalProviders.data.startFetching(); _checkNotificationPermission(); - _onEnterMonitorPage(); - - _shortcutChannel.setMethodCallHandler((call) async { - if (call.method == 'onShortcut') { - final value = call.arguments as String?; - if (value != null && mounted) { - _handleShortcutNavigation(value); - } - } - }); WidgetsBinding.instance.addPostFrameCallback((_) { - _handleInitialShortcut(); + _tryHandleStartupNavigation(); }); } - void _handleInitialShortcut() { - if (_hasHandledInitialShortcut) return; - _hasHandledInitialShortcut = true; - - final shortcut = widget.initialShortcut; - if (shortcut != null && shortcut.isNotEmpty) { - _handleShortcutNavigation(shortcut); - return; - } - - _tryHandleStartupNavigation(); - } - @override Widget build(BuildContext context) { return DynamicColorBuilder( From 5f153839730cb3fc66e11e899e8f0443edd0013f Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 1 Jan 2026 12:11:08 +0800 Subject: [PATCH 08/13] fix: log --- lib/main.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 3b2002656..46310e5c1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -180,8 +180,8 @@ Future getInitialShortcut() async { try { final result = await platform.invokeMethod('getInitialShortcut'); return result; - } on PlatformException catch (e) { - print('Failed to get initial shortcut: $e'); + } on PlatformException catch (e, st) { + talker.error('Failed to get initial shortcut', e, st); return null; } } From c7d8337a073c5eab6afc1c79c4233380ae15553b Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 1 Jan 2026 14:36:57 +0800 Subject: [PATCH 09/13] i18n --- .../app/src/main/res/values-ja/strings.xml | 2 ++ .../app/src/main/res/values-ko/strings.xml | 2 ++ .../src/main/res/values-zh-rTW/strings.xml | 2 ++ .../app/src/main/res/values-zh/strings.xml | 2 ++ android/app/src/main/res/values/strings.xml | 4 ++-- ios/Runner.xcodeproj/project.pbxproj | 22 +++++++++++++++---- ios/Runner/en.lproj/Monitor.strings | 4 ++++ ios/Runner/ja.lproj/Monitor.strings | 4 ++++ ios/Runner/ko.lproj/Monitor.strings | 4 ++++ .../Monitor.intentdefinition | 2 +- 10 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 ios/Runner/en.lproj/Monitor.strings create mode 100644 ios/Runner/ja.lproj/Monitor.strings create mode 100644 ios/Runner/ko.lproj/Monitor.strings rename ios/Runner/{ => zh_TW.lproj}/Monitor.intentdefinition (98%) diff --git a/android/app/src/main/res/values-ja/strings.xml b/android/app/src/main/res/values-ja/strings.xml index bc44ee5a7..d6cd064cf 100644 --- a/android/app/src/main/res/values-ja/strings.xml +++ b/android/app/src/main/res/values-ja/strings.xml @@ -1,3 +1,5 @@ DPIP防災 + 強震モニター + 強震モニターを開く \ No newline at end of file diff --git a/android/app/src/main/res/values-ko/strings.xml b/android/app/src/main/res/values-ko/strings.xml index 2b2caaa2c..51e818be0 100644 --- a/android/app/src/main/res/values-ko/strings.xml +++ b/android/app/src/main/res/values-ko/strings.xml @@ -1,3 +1,5 @@ DPIP 재해 + 강진 모니터 + 강진 모니터 열기 \ No newline at end of file diff --git a/android/app/src/main/res/values-zh-rTW/strings.xml b/android/app/src/main/res/values-zh-rTW/strings.xml index 4ea4c6215..f3cff630d 100644 --- a/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/android/app/src/main/res/values-zh-rTW/strings.xml @@ -1,3 +1,5 @@ DPIP 防災 + 強震監視器 + 打開強震監視器 \ No newline at end of file diff --git a/android/app/src/main/res/values-zh/strings.xml b/android/app/src/main/res/values-zh/strings.xml index 4ea4c6215..f3cff630d 100644 --- a/android/app/src/main/res/values-zh/strings.xml +++ b/android/app/src/main/res/values-zh/strings.xml @@ -1,3 +1,5 @@ DPIP 防災 + 強震監視器 + 打開強震監視器 \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 756f65700..d853d60f9 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ DPIP - 強震監視器 - 打開強震監視器 + Earthquake Monitor + Open Earthquake Monitor \ No newline at end of file diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e07279c23..bdbfb9fde 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -10,7 +10,7 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 5285489F2F06028F0027C627 /* Monitor.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 5285489E2F06028F0027C627 /* Monitor.intentdefinition */; }; + 528548E22F06357A0027C627 /* Monitor.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 528548E12F06357A0027C627 /* Monitor.intentdefinition */; }; 529C27C82C93F7B200AAFAB6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 529C27C62C93F7B200AAFAB6 /* InfoPlist.strings */; }; 52FA5E152DC8A9EA0008FEB0 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52FA5E142DC8A9EA0008FEB0 /* StoreKit.framework */; }; 6037C7C000DE0752E32B9E54 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6838AADB908B55100C5691B /* Pods_Runner.framework */; }; @@ -67,7 +67,10 @@ 4B4531DD011F7A688ACAA691 /* 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 = ""; }; 5228AD5A2C2EE45D007635F5 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 523A5FD82EB21EC0006F93FC /* Profile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Profile.xcconfig; path = Flutter/Profile.xcconfig; sourceTree = ""; }; - 5285489E2F06028F0027C627 /* Monitor.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Monitor.intentdefinition; sourceTree = ""; }; + 526767E42F064A19003CE2E4 /* zh_TW */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = zh_TW; path = zh_TW.lproj/Monitor.intentdefinition; sourceTree = ""; }; + 526767E62F064A3B003CE2E4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Monitor.strings; sourceTree = ""; }; + 526767E82F064A3E003CE2E4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Monitor.strings; sourceTree = ""; }; + 526767EA2F064A47003CE2E4 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Monitor.strings; sourceTree = ""; }; 528548A52F06047F0027C627 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; 529C27C92C93F7B900AAFAB6 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/InfoPlist.strings; sourceTree = ""; }; 529C27CC2C93F7BC00AAFAB6 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -208,7 +211,7 @@ 632125282C2EA17900A088F8 /* GoogleService-Info.plist */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 529C27C62C93F7B200AAFAB6 /* InfoPlist.strings */, - 5285489E2F06028F0027C627 /* Monitor.intentdefinition */, + 528548E12F06357A0027C627 /* Monitor.intentdefinition */, ); path = Runner; sourceTree = ""; @@ -476,7 +479,7 @@ files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - 5285489F2F06028F0027C627 /* Monitor.intentdefinition in Sources */, + 528548E22F06357A0027C627 /* Monitor.intentdefinition in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -491,6 +494,17 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 528548E12F06357A0027C627 /* Monitor.intentdefinition */ = { + isa = PBXVariantGroup; + children = ( + 526767E42F064A19003CE2E4 /* zh_TW */, + 526767E62F064A3B003CE2E4 /* en */, + 526767E82F064A3E003CE2E4 /* ja */, + 526767EA2F064A47003CE2E4 /* ko */, + ); + name = Monitor.intentdefinition; + sourceTree = ""; + }; 529C27C62C93F7B200AAFAB6 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( diff --git a/ios/Runner/en.lproj/Monitor.strings b/ios/Runner/en.lproj/Monitor.strings new file mode 100644 index 000000000..0e3211aaf --- /dev/null +++ b/ios/Runner/en.lproj/Monitor.strings @@ -0,0 +1,4 @@ +"l2uH53" = "This shortcut opens the Earthquake Monitor screen directly"; + +"qno9IY" = "Open Earthquake Monitor"; + diff --git a/ios/Runner/ja.lproj/Monitor.strings b/ios/Runner/ja.lproj/Monitor.strings new file mode 100644 index 000000000..70cf9d093 --- /dev/null +++ b/ios/Runner/ja.lproj/Monitor.strings @@ -0,0 +1,4 @@ +"l2uH53" = "このショートカットは強震モニター画面を直接開きます"; + +"qno9IY" = "強震モニターを開く"; + diff --git a/ios/Runner/ko.lproj/Monitor.strings b/ios/Runner/ko.lproj/Monitor.strings new file mode 100644 index 000000000..30f3cab11 --- /dev/null +++ b/ios/Runner/ko.lproj/Monitor.strings @@ -0,0 +1,4 @@ +"l2uH53" = "이 단축키는 강진 모니터 화면을 직접 엽니다"; + +"qno9IY" = "강진 모니터 열기"; + diff --git a/ios/Runner/Monitor.intentdefinition b/ios/Runner/zh_TW.lproj/Monitor.intentdefinition similarity index 98% rename from ios/Runner/Monitor.intentdefinition rename to ios/Runner/zh_TW.lproj/Monitor.intentdefinition index 974080470..2c126811c 100644 --- a/ios/Runner/Monitor.intentdefinition +++ b/ios/Runner/zh_TW.lproj/Monitor.intentdefinition @@ -64,7 +64,7 @@ INIntentTitle - 開啟強震監視器 + 強震監視器 INIntentTitleID qno9IY INIntentType From acabdbe9fa7712514806baa91d25ef5abf100491 Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 1 Jan 2026 15:40:24 +0800 Subject: [PATCH 10/13] fix: go Notification --- lib/app.dart | 51 ++++++++++++++++++++------------------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 720bc5306..cfed817a6 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'dart:io'; import 'package:dpip/app/welcome/4-permissions/page.dart'; +import 'package:dpip/app/map/_lib/utils.dart'; +import 'package:dpip/app/map/page.dart'; import 'package:dpip/core/notify.dart'; import 'package:dpip/core/preference.dart'; import 'package:dpip/core/providers.dart'; @@ -19,8 +21,6 @@ import 'package:in_app_update/in_app_update.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'app/map/_lib/utils.dart'; -import 'app/map/page.dart'; import 'main.dart'; /// The root widget of the application. @@ -37,7 +37,6 @@ class DpipApp extends StatefulWidget { } class _DpipAppState extends State with WidgetsBindingObserver { - bool _hasHandledPendingNotification = false; bool _hasHandledInitialShortcut = false; Future _checkUpdate() async { @@ -82,38 +81,25 @@ class _DpipAppState extends State with WidgetsBindingObserver { } } - void _tryHandleStartupNavigation() { - if (!mounted) return; + void _tryHandleInitialShortcut() { + if (_hasHandledInitialShortcut) return; + if (widget.initialShortcut == null) return; final ctx = router.routerDelegate.navigatorKey.currentContext; - if (ctx == null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - _tryHandleStartupNavigation(); - }); - return; - } + if (ctx == null) return; - if (!_hasHandledInitialShortcut && widget.initialShortcut != null) { - _hasHandledInitialShortcut = true; + _hasHandledInitialShortcut = true; - switch (widget.initialShortcut) { - case 'monitor': - ctx.push( - MapPage.route( - options: MapPageOptions( - initialLayers: {MapLayer.monitor}, - ), + switch (widget.initialShortcut) { + case 'monitor': + ctx.go( + MapPage.route( + options: MapPageOptions( + initialLayers: {MapLayer.monitor}, ), - ); - return; - } - } - - if (!_hasHandledPendingNotification) { - _hasHandledPendingNotification = true; - Future.delayed(const Duration(milliseconds: 500), () { - handlePendingNotificationNavigation(ctx); - }); + ), + ); + break; } } @@ -132,7 +118,10 @@ class _DpipAppState extends State with WidgetsBindingObserver { _checkNotificationPermission(); WidgetsBinding.instance.addPostFrameCallback((_) { - _tryHandleStartupNavigation(); + Future.delayed(const Duration(milliseconds: 500), () { + handlePendingNotificationNavigation(context); + }); + _tryHandleInitialShortcut(); }); } From 66435c02d953d3e19b8b7a53dc1d2141f4adbd35 Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 1 Jan 2026 15:42:35 +0800 Subject: [PATCH 11/13] build: 300104014 --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 344813c67..4b2af89b7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -49,7 +49,7 @@ android { applicationId 'com.exptech.dpip' minSdkVersion 26 targetSdkVersion 36 - versionCode 300104013 + versionCode 300104014 versionName flutterVersionName multiDexEnabled true resConfigs "en", "ko", "zh-rTW", "ja", "zh-rCN" From dfdd12aab5cf735a8076d320470e475d5491d76c Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 1 Jan 2026 16:27:19 +0800 Subject: [PATCH 12/13] fix: route --- lib/app.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app.dart b/lib/app.dart index cfed817a6..035556217 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -92,7 +92,7 @@ class _DpipAppState extends State with WidgetsBindingObserver { switch (widget.initialShortcut) { case 'monitor': - ctx.go( + ctx.push( MapPage.route( options: MapPageOptions( initialLayers: {MapLayer.monitor}, From 9e62abd857e7f51ba149e1a57ef53d08ca316140 Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 1 Jan 2026 16:27:43 +0800 Subject: [PATCH 13/13] build: 300104015 --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 4b2af89b7..cfd0c0644 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -49,7 +49,7 @@ android { applicationId 'com.exptech.dpip' minSdkVersion 26 targetSdkVersion 36 - versionCode 300104014 + versionCode 300104015 versionName flutterVersionName multiDexEnabled true resConfigs "en", "ko", "zh-rTW", "ja", "zh-rCN"