From a42ad80b3baa20ca534a4a3cd65dd4850942db23 Mon Sep 17 00:00:00 2001 From: AIsouler Date: Mon, 20 Jul 2026 15:31:46 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9Afuckmi=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E9=A1=B5=E9=9D=A2=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/home.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 35eb89dd4..1862d6dbf 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -378,9 +378,11 @@ class HomeBackScope extends ConsumerWidget { globalState.appController.toPage(PageLabel.dashboard); return; } - final canPop = Navigator.canPop(context); - if (canPop) { - Navigator.pop(context); + // Use navigator key for reliable route checking (avoids stale + // context after display mode changes on Xiaomi HyperOS). + final navigatorState = globalState.navigatorKey.currentState; + if (navigatorState != null && navigatorState.canPop()) { + navigatorState.pop(); } else { await globalState.appController.handleBackOrExit(); } From 0bad0c72f40ac242d652105c2ef55479fc16b909 Mon Sep 17 00:00:00 2001 From: AIsouler Date: Mon, 20 Jul 2026 18:25:43 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9Afuckmi=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E8=BF=9B=E5=85=A5=E8=BD=AF=E4=BB=B6=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/manager/app_manager.dart | 1 + lib/pages/home.dart | 4 ++++ lib/state.dart | 17 +++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/lib/manager/app_manager.dart b/lib/manager/app_manager.dart index e104426c5..6baff9132 100644 --- a/lib/manager/app_manager.dart +++ b/lib/manager/app_manager.dart @@ -162,6 +162,7 @@ class _AppStateManagerState extends ConsumerState globalState.appController.savePreferences(); await globalState.handleBackground(); } else if (state == AppLifecycleState.resumed) { + globalState.markResumed(); globalState.handleForeground(); render?.resume(); await globalState.resumeForegroundUpdates(); diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 1862d6dbf..a1a44ace6 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -374,6 +374,10 @@ class HomeBackScope extends ConsumerWidget { canPop: false, onPopInvokedWithResult: (didPop, _) async { if (didPop || backBlock) return; + // Ignore back events right after the app resumes — Xiaomi HyperOS + // may dispatch a phantom back event during the resume transition + // (e.g. when the user returns via the system gesture navigation). + if (globalState.hasResumedRecently()) return; if (!isCurrentRootPage) { globalState.appController.toPage(PageLabel.dashboard); return; diff --git a/lib/state.dart b/lib/state.dart index b6b06b1ec..4e0f29862 100644 --- a/lib/state.dart +++ b/lib/state.dart @@ -59,6 +59,23 @@ class GlobalState { bool _needsTaskRestart = false; Timer? _backgroundCleanupTimer; final Lock _scriptEvaluateLock = Lock(); + DateTime? _lastResumeTime; + + /// Marks the moment the app returned to the foreground. + void markResumed() { + _lastResumeTime = DateTime.now(); + } + + /// Returns true if the app resumed less than [duration] ago. + /// Used to ignore phantom back events dispatched by Xiaomi HyperOS + /// during the resume transition. + bool hasResumedRecently({ + Duration duration = const Duration(milliseconds: 300), + }) { + final lastResume = _lastResumeTime; + if (lastResume == null) return false; + return DateTime.now().difference(lastResume) < duration; + } bool isInit = false; From 86554dc60931799aea69271661fbeac3fcab2419 Mon Sep 17 00:00:00 2001 From: AIsouler Date: Tue, 21 Jul 2026 14:36:24 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/navigator.dart | 1 + lib/controller.dart | 47 ++++++++++++++++++++++++++---------- lib/manager/app_manager.dart | 1 - lib/pages/home.dart | 19 +++++++++------ lib/state.dart | 23 +++++++----------- 5 files changed, 55 insertions(+), 36 deletions(-) diff --git a/lib/common/navigator.dart b/lib/common/navigator.dart index 68c82099e..0c18b0df1 100644 --- a/lib/common/navigator.dart +++ b/lib/common/navigator.dart @@ -5,6 +5,7 @@ import 'package:flutter/cupertino.dart'; class BaseNavigator { static Future push(BuildContext context, Widget child) async { + globalState.markRoutePushed(); if (globalState.appState.viewMode != ViewMode.mobile) { return await Navigator.of( context, diff --git a/lib/controller.dart b/lib/controller.dart index 91a3d1c2e..61e2ce20c 100644 --- a/lib/controller.dart +++ b/lib/controller.dart @@ -152,10 +152,10 @@ class AppController { } if (wasRunning) { - await globalState.handleStart( - [updateRunTime, updateTraffic], - !keepVpnService, - ); + await globalState.handleStart([ + updateRunTime, + updateTraffic, + ], !keepVpnService); _scheduleCheckIpRefresh(); _backgroundLoad(); } @@ -1167,9 +1167,13 @@ class AppController { await _handlePreference(); await _handlerDisclaimer(); if (system.isWindows) { - unawaited(setProcessPriority(_ref.read(appSettingProvider).enableHighPriority).catchError((e) { - commonPrint.log('Failed to set initial process priority: $e'); - })); + unawaited( + setProcessPriority( + _ref.read(appSettingProvider).enableHighPriority, + ).catchError((e) { + commonPrint.log('Failed to set initial process priority: $e'); + }), + ); } _ref.read(initProvider.notifier).value = true; } @@ -1197,7 +1201,8 @@ class AppController { } } final hasProfile = _ref.read(currentProfileProvider) != null; - final shouldStart = hasProfile && + final shouldStart = + hasProfile && (globalState.isStart || _ref.read(appSettingProvider).autoRun); if (shouldStart) { @@ -1261,6 +1266,7 @@ class AppController { } void toPage(PageLabel pageLabel) { + globalState.markRoutePushed(); final context = globalState.navigatorKey.currentState?.context; if (context != null && context.mounted) { Navigator.of( @@ -1351,7 +1357,9 @@ class AppController { ageSecretKey: ageSecretKey, ).update(); if (globalState.navigatorKey.currentState?.canPop() ?? false) { - globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst); + globalState.navigatorKey.currentState?.popUntil( + (route) => route.isFirst, + ); } toProfiles(); await addProfile(profile); @@ -1376,7 +1384,9 @@ class AppController { _ref.read(loadingProvider.notifier).value = true; try { await Future.delayed(const Duration(milliseconds: 500)); - final profile = await Profile.normal(label: platformFile?.name).saveFile(bytes); + final profile = await Profile.normal( + label: platformFile?.name, + ).saveFile(bytes); globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst); toProfiles(); await addProfile(profile); @@ -1704,9 +1714,18 @@ class AppController { }); } - Future updateTray([bool focus = false, bool silent = false, bool force = false]) async { + Future updateTray([ + bool focus = false, + bool silent = false, + bool force = false, + ]) async { final trayState = _ref.read(trayStateProvider); - await tray.update(trayState: trayState, focus: focus, silent: silent, force: force); + await tray.update( + trayState: trayState, + focus: focus, + silent: silent, + force: force, + ); } Future _processRecoveryArchive( @@ -2244,7 +2263,9 @@ class AppController { // Use default widgets if merged is empty return mergedWidgets.isNotEmpty ? mergedWidgets - : (system.isAndroid ? defaultAndroidDashboardWidgets : defaultDashboardWidgets); + : (system.isAndroid + ? defaultAndroidDashboardWidgets + : defaultDashboardWidgets); } Future safeRun( diff --git a/lib/manager/app_manager.dart b/lib/manager/app_manager.dart index 6baff9132..e104426c5 100644 --- a/lib/manager/app_manager.dart +++ b/lib/manager/app_manager.dart @@ -162,7 +162,6 @@ class _AppStateManagerState extends ConsumerState globalState.appController.savePreferences(); await globalState.handleBackground(); } else if (state == AppLifecycleState.resumed) { - globalState.markResumed(); globalState.handleForeground(); render?.resume(); await globalState.resumeForegroundUpdates(); diff --git a/lib/pages/home.dart b/lib/pages/home.dart index a1a44ace6..3e0877dd9 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -374,22 +374,25 @@ class HomeBackScope extends ConsumerWidget { canPop: false, onPopInvokedWithResult: (didPop, _) async { if (didPop || backBlock) return; - // Ignore back events right after the app resumes — Xiaomi HyperOS - // may dispatch a phantom back event during the resume transition - // (e.g. when the user returns via the system gesture navigation). - if (globalState.hasResumedRecently()) return; if (!isCurrentRootPage) { globalState.appController.toPage(PageLabel.dashboard); return; } - // Use navigator key for reliable route checking (avoids stale - // context after display mode changes on Xiaomi HyperOS). + // Use the global navigator key rather than the local build + // context — the latter may become stale after display-mode + // changes on Xiaomi HyperOS (e.g. enabling "Show refresh rate"). final navigatorState = globalState.navigatorKey.currentState; if (navigatorState != null && navigatorState.canPop()) { navigatorState.pop(); - } else { - await globalState.appController.handleBackOrExit(); + return; } + // If no route has ever been pushed this session we are in a + // fresh (cold-start) session. Do not send the app to the + // background on the first back press — the user may have + // returned from a killed process and still expects to be on + // a secondary page. + if (!globalState.hasEverPushedRoute) return; + await globalState.appController.handleBackOrExit(); }, child: child, ); diff --git a/lib/state.dart b/lib/state.dart index 4e0f29862..515e233eb 100644 --- a/lib/state.dart +++ b/lib/state.dart @@ -59,22 +59,17 @@ class GlobalState { bool _needsTaskRestart = false; Timer? _backgroundCleanupTimer; final Lock _scriptEvaluateLock = Lock(); - DateTime? _lastResumeTime; + bool _hasEverPushedRoute = false; - /// Marks the moment the app returned to the foreground. - void markResumed() { - _lastResumeTime = DateTime.now(); - } + /// Whether any route has been pushed onto the Navigator during this + /// session. Used to distinguish a fresh (cold-start) session — where + /// back should not immediately send the app to the background — from a + /// session with real navigation history. + bool get hasEverPushedRoute => _hasEverPushedRoute; - /// Returns true if the app resumed less than [duration] ago. - /// Used to ignore phantom back events dispatched by Xiaomi HyperOS - /// during the resume transition. - bool hasResumedRecently({ - Duration duration = const Duration(milliseconds: 300), - }) { - final lastResume = _lastResumeTime; - if (lastResume == null) return false; - return DateTime.now().difference(lastResume) < duration; + /// Marks that the user has navigated beyond the home page. + void markRoutePushed() { + _hasEverPushedRoute = true; } bool isInit = false; From 9e83f7c9b193025810b9c182a920dc5baa7e37f5 Mon Sep 17 00:00:00 2001 From: AIsouler Date: Wed, 22 Jul 2026 12:38:01 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/src/main/AndroidManifest.xml | 5 +++-- .../kotlin/com/appshub/bettbox/MainActivity.kt | 17 ++++++++++++++++- lib/common/navigator.dart | 1 - lib/controller.dart | 1 - lib/pages/home.dart | 6 ------ lib/state.dart | 13 ------------- 6 files changed, 19 insertions(+), 24 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 728c69538..934f20ae5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -29,10 +29,11 @@ android:icon="@mipmap/ic_launcher" android:label="Bettbox" android:banner="@drawable/tv_banner" - android:enableOnBackInvokedCallback="true"> + android:enableOnBackInvokedCallback="false"> push(BuildContext context, Widget child) async { - globalState.markRoutePushed(); if (globalState.appState.viewMode != ViewMode.mobile) { return await Navigator.of( context, diff --git a/lib/controller.dart b/lib/controller.dart index 61e2ce20c..87477b70f 100644 --- a/lib/controller.dart +++ b/lib/controller.dart @@ -1266,7 +1266,6 @@ class AppController { } void toPage(PageLabel pageLabel) { - globalState.markRoutePushed(); final context = globalState.navigatorKey.currentState?.context; if (context != null && context.mounted) { Navigator.of( diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 3e0877dd9..df2f35c86 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -386,12 +386,6 @@ class HomeBackScope extends ConsumerWidget { navigatorState.pop(); return; } - // If no route has ever been pushed this session we are in a - // fresh (cold-start) session. Do not send the app to the - // background on the first back press — the user may have - // returned from a killed process and still expects to be on - // a secondary page. - if (!globalState.hasEverPushedRoute) return; await globalState.appController.handleBackOrExit(); }, child: child, diff --git a/lib/state.dart b/lib/state.dart index 515e233eb..eedcc0c3f 100644 --- a/lib/state.dart +++ b/lib/state.dart @@ -59,19 +59,6 @@ class GlobalState { bool _needsTaskRestart = false; Timer? _backgroundCleanupTimer; final Lock _scriptEvaluateLock = Lock(); - bool _hasEverPushedRoute = false; - - /// Whether any route has been pushed onto the Navigator during this - /// session. Used to distinguish a fresh (cold-start) session — where - /// back should not immediately send the app to the background — from a - /// session with real navigation history. - bool get hasEverPushedRoute => _hasEverPushedRoute; - - /// Marks that the user has navigated beyond the home page. - void markRoutePushed() { - _hasEverPushedRoute = true; - } - bool isInit = false; bool get isStart => startTime != null && startTime!.isBeforeNow;