feat(mobile): check for update on app resume from background

- Mix in WidgetsBindingObserver to detect foreground/background transitions
- On resumed: run a silent version check (no dialog) and only show the
  update dialog if a new version is actually available
- Throttle resume checks to once per 2 minutes to avoid excessive API calls
- Once the update dialog has been shown, skip further checks for the rest
  of the session; user won't be re-prompted until next cold start

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-07 09:29:46 -08:00
parent d174b74764
commit cc966fb022
1 changed files with 65 additions and 2 deletions

View File

@ -18,10 +18,24 @@ class MainShell extends StatefulWidget {
State<MainShell> createState() => _MainShellState(); State<MainShell> createState() => _MainShellState();
} }
class _MainShellState extends State<MainShell> { /// WidgetsBindingObserver App
class _MainShellState extends State<MainShell> with WidgetsBindingObserver {
int _currentIndex = 0; int _currentIndex = 0;
/// Shell didChangeDependencies
bool _updateChecked = false; bool _updateChecked = false;
///
DateTime? _lastUpdateCheck;
/// App
///
///
/// - true
/// "稍后再说"
/// - App
bool _updatePromptShown = false;
final _pages = const [ final _pages = const [
HomePage(), HomePage(),
MarketPage(), MarketPage(),
@ -29,19 +43,68 @@ class _MainShellState extends State<MainShell> {
ProfilePage(), ProfilePage(),
]; ];
@override
void initState() {
super.initState();
// 便 didChangeAppLifecycleState
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
// 3
if (!_updateChecked) { if (!_updateChecked) {
_updateChecked = true; _updateChecked = true;
_checkForUpdate(); _checkForUpdate();
} }
} }
/// App
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// session "稍后"
// App
if (_updatePromptShown) return;
final now = DateTime.now();
// 2
if (_lastUpdateCheck == null ||
now.difference(_lastUpdateCheck!) > const Duration(minutes: 2)) {
_checkForUpdate();
}
}
}
///
///
///
/// 1.
/// 2. 3
/// 3. silentCheck()
/// 4. _updatePromptShown = true checkForUpdate()
/// 2
Future<void> _checkForUpdate() async { Future<void> _checkForUpdate() async {
_lastUpdateCheck = DateTime.now();
await Future.delayed(const Duration(seconds: 3)); await Future.delayed(const Duration(seconds: 3));
if (!mounted) return; if (!mounted) return;
await UpdateService().checkForUpdate(context);
final updateService = UpdateService();
final versionInfo = await updateService.silentCheck();
if (!mounted) return;
if (versionInfo != null) {
// session
_updatePromptShown = true;
await updateService.checkForUpdate(context);
}
} }
@override @override