From 844cf6980039cca7ec3812d5b647d3fd442e4db9 Mon Sep 17 00:00:00 2001 From: Strycher Date: Thu, 30 Jul 2026 00:35:22 -0400 Subject: [PATCH] fix(#443): guard battery-optimization platform calls (Gemini review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gemini flagged two unhandled PlatformException paths (SAFELANE §6): - isIgnoringBatteryOptimizations could throw -> banner silently never shows; now caught + logged, leaving the banner hidden on an unknown status. - openIgnoreBatteryOptimizationSettings could throw -> button did nothing with no feedback; now caught + logged + a snackbar tells the user it failed. Co-Authored-By: Claude Opus 4.8 --- lib/l10n/app_en.arb | 4 +++ lib/l10n/app_localizations.dart | 6 ++++ lib/l10n/app_localizations_bg.dart | 3 ++ lib/l10n/app_localizations_de.dart | 3 ++ lib/l10n/app_localizations_en.dart | 3 ++ lib/l10n/app_localizations_es.dart | 3 ++ lib/l10n/app_localizations_fr.dart | 3 ++ lib/l10n/app_localizations_hu.dart | 3 ++ lib/l10n/app_localizations_it.dart | 3 ++ lib/l10n/app_localizations_ja.dart | 3 ++ lib/l10n/app_localizations_ko.dart | 3 ++ lib/l10n/app_localizations_nl.dart | 3 ++ lib/l10n/app_localizations_pl.dart | 3 ++ lib/l10n/app_localizations_pt.dart | 3 ++ lib/l10n/app_localizations_ru.dart | 3 ++ lib/l10n/app_localizations_sk.dart | 3 ++ lib/l10n/app_localizations_sl.dart | 3 ++ lib/l10n/app_localizations_sv.dart | 3 ++ lib/l10n/app_localizations_uk.dart | 3 ++ lib/l10n/app_localizations_zh.dart | 3 ++ lib/widgets/battery_optimization_banner.dart | 34 ++++++++++++++++---- 21 files changed, 92 insertions(+), 6 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1241b55..811ce0b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -2683,5 +2683,9 @@ "batteryOptimizationDismiss": "Dismiss", "@batteryOptimizationDismiss": { "description": "Tooltip for the button that hides the battery-optimization banner for this session (#443)." + }, + "batteryOptimizationOpenFailed": "Couldn't open battery settings", + "@batteryOptimizationOpenFailed": { + "description": "Snackbar shown when the battery-optimization settings screen could not be opened (#443)." } } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 08e0b0a..83862ef 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -8079,6 +8079,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Dismiss'** String get batteryOptimizationDismiss; + + /// Snackbar shown when the battery-optimization settings screen could not be opened (#443). + /// + /// In en, this message translates to: + /// **'Couldn\'t open battery settings'** + String get batteryOptimizationOpenFailed; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index d08670a..ffc9bc6 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -4726,4 +4726,7 @@ class AppLocalizationsBg extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index b68a11f..a07c13a 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -4743,4 +4743,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index e706230..075d136 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -4648,4 +4648,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 46906cc..421c4c2 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -4731,4 +4731,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index ee66217..801e065 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -4759,4 +4759,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 36766ff..b1d7a1a 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -4748,4 +4748,7 @@ class AppLocalizationsHu extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index b408870..8c4f29b 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -4736,4 +4736,7 @@ class AppLocalizationsIt extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index d837617..42d5f4c 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -4502,4 +4502,7 @@ class AppLocalizationsJa extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index f439bab..fe553db 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -4503,4 +4503,7 @@ class AppLocalizationsKo extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index e48faa8..77ed752 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -4711,4 +4711,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index e3fe08d..902380c 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -4748,4 +4748,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index d84e212..ff2f5fb 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -4724,4 +4724,7 @@ class AppLocalizationsPt extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 34d80fd..435b8a8 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -4742,4 +4742,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index e476531..241c175 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -4708,4 +4708,7 @@ class AppLocalizationsSk extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index c0e6b59..5c2cea4 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -4706,4 +4706,7 @@ class AppLocalizationsSl extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 639bcfb..140e4d6 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -4681,4 +4681,7 @@ class AppLocalizationsSv extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 3da7ced..6475e37 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -4743,4 +4743,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index ce94b12..4183304 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -4376,4 +4376,7 @@ class AppLocalizationsZh extends AppLocalizations { @override String get batteryOptimizationDismiss => 'Dismiss'; + + @override + String get batteryOptimizationOpenFailed => 'Couldn\'t open battery settings'; } diff --git a/lib/widgets/battery_optimization_banner.dart b/lib/widgets/battery_optimization_banner.dart index 29241d7..c6e54ec 100644 --- a/lib/widgets/battery_optimization_banner.dart +++ b/lib/widgets/battery_optimization_banner.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import '../l10n/l10n.dart'; +import '../utils/app_logger.dart'; import '../utils/platform_info.dart'; /// Warns the user when the app is NOT exempt from Android battery optimization @@ -58,15 +59,36 @@ class _BatteryOptimizationBannerState extends State Future _check() async { if (!PlatformInfo.isAndroid) return; - final ignoring = await FlutterForegroundTask.isIgnoringBatteryOptimizations; - if (!mounted) return; - setState(() => _ignoring = ignoring); + try { + final ignoring = + await FlutterForegroundTask.isIgnoringBatteryOptimizations; + if (!mounted) return; + setState(() => _ignoring = ignoring); + } catch (e) { + // Can't confirm the status — leave the banner hidden rather than risk a + // false warning, but surface the failure to the log (SAFELANE §6). + appLogger.warn( + 'Battery-optimization status check failed: $e', + tag: 'BatteryBanner', + ); + } } Future _openSettings() async { - await FlutterForegroundTask.openIgnoreBatteryOptimizationSettings(); - // The user returns via resume, which triggers _check() and clears the - // banner if they applied the change. + try { + await FlutterForegroundTask.openIgnoreBatteryOptimizationSettings(); + // The user returns via resume, which triggers _check() and clears the + // banner if they applied the change. + } catch (e) { + appLogger.warn( + 'Failed to open battery-optimization settings: $e', + tag: 'BatteryBanner', + ); + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.batteryOptimizationOpenFailed)), + ); + } } @override