diff --git a/lib/screens/appearance_screen.dart b/lib/screens/appearance_screen.dart new file mode 100644 index 0000000..032a0b0 --- /dev/null +++ b/lib/screens/appearance_screen.dart @@ -0,0 +1,129 @@ +import 'package:florid/providers/settings_provider.dart'; +import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../widgets/m_list.dart'; + +class AppearanceScreen extends StatelessWidget { + const AppearanceScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, settings, _) { + return Scaffold( + appBar: AppBar(title: const Text('Appearance')), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16, + children: [ + Column( + spacing: 4, + children: [ + MListHeader(title: 'Theme Mode'), + MRadioListView( + items: [ + MRadioListItemData( + title: 'Follow system theme', + subtitle: '', + value: ThemeMode.system, + suffix: Icon(Symbols.settings_suggest), + ), + MRadioListItemData( + title: 'Light theme', + subtitle: '', + value: ThemeMode.light, + suffix: Icon(Symbols.light_mode_rounded), + ), + MRadioListItemData( + title: 'Dark theme', + subtitle: '', + value: ThemeMode.dark, + suffix: Icon(Symbols.dark_mode_rounded), + ), + ], + groupValue: settings.themeMode, + onChanged: (mode) { + settings.setThemeMode(mode); + }, + ), + ], + ), + Column( + spacing: 4, + children: [ + MListHeader(title: 'Theme Style'), + MRadioListView( + items: [ + MRadioListItemData( + title: 'Material style', + subtitle: '', + value: ThemeStyle.material, + ), + MRadioListItemData( + title: 'Florid style', + subtitle: '', + suffix: Container( + margin: const EdgeInsets.only(right: 8.0), + child: Material( + color: Theme.of(context).colorScheme.secondary, + borderRadius: BorderRadius.circular(99.0), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 2.0, + ), + child: Text( + 'Beta', + style: TextStyle( + color: Theme.of( + context, + ).colorScheme.onSecondary, + ), + ), + ), + ), + ), + value: ThemeStyle.florid, + ), + ], + groupValue: settings.themeStyle, + onChanged: (style) { + settings.setThemeStyle(style); + }, + ), + MListView( + items: [ + MListItemData( + leading: Icon(Symbols.feedback), + title: 'Fedback on Florid theme', + subtitle: + 'Help improve the Florid theme by providing feedback', + onTap: () { + // Keep the same URL used in Settings. + launchUrl( + Uri.parse( + 'https://github.com/Nandanrmenon/florid/discussions/5', + ), + ); + }, + suffix: Icon(Symbols.open_in_new), + ), + ], + ), + ], + ), + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 9b1f6db..329c6da 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -1,14 +1,10 @@ import 'dart:convert'; import 'dart:io'; -import 'package:android_intent_plus/android_intent.dart'; import 'package:file_picker/file_picker.dart'; import 'package:florid/l10n/app_localizations.dart'; import 'package:florid/widgets/m_list.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart' as p; @@ -18,9 +14,11 @@ import 'package:share_plus/share_plus.dart'; import 'package:url_launcher/url_launcher.dart'; import '../providers/app_provider.dart'; -import '../providers/download_provider.dart'; import '../providers/settings_provider.dart'; +import '../screens/appearance_screen.dart'; import '../screens/repositories_screen.dart'; +import '../screens/troubleshooting_screen.dart'; +import '../screens/update_settings_screen.dart'; import '../services/fdroid_api_service.dart'; import '../services/update_check_service.dart'; @@ -34,17 +32,12 @@ class SettingsScreen extends StatefulWidget { enum _FavoritesImportAction { merge, replace } class _SettingsScreenState extends State { - static const MethodChannel _batteryChannel = MethodChannel( - 'florid/battery_optimizations', - ); String _appVersion = ''; - bool? _isIgnoringBatteryOptimizations; @override void initState() { super.initState(); _loadVersion(); - _loadBatteryOptimizationStatus(); } Future _loadVersion() async { @@ -55,70 +48,6 @@ class _SettingsScreenState extends State { }); } - Future _loadBatteryOptimizationStatus() async { - if (!Platform.isAndroid) return; - try { - final isIgnoring = await _batteryChannel.invokeMethod( - 'isIgnoringBatteryOptimizations', - ); - if (!mounted) return; - setState(() { - _isIgnoringBatteryOptimizations = isIgnoring ?? false; - }); - } on PlatformException { - if (!mounted) return; - setState(() { - _isIgnoringBatteryOptimizations = null; - }); - } - } - - Future _clearRepoCache(BuildContext context) async { - final api = context.read(); - await api.clearRepositoryCache(); - if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Repository cache cleared'))); - } - - Future _clearImageCache(BuildContext context) async { - await DefaultCacheManager().emptyCache(); - imageCache.clear(); - imageCache.clearLiveImages(); - if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Image cache cleared'))); - } - - Future _clearApkDownloads(BuildContext context) async { - final deleted = await context.read().clearAllDownloads(); - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - deleted > 0 - ? 'Deleted $deleted APK file${deleted == 1 ? '' : 's'}' - : 'No APK downloads to delete', - ), - ), - ); - } - - Future _requestDisableBatteryOptimizations() async { - if (!Platform.isAndroid) return; - final info = await PackageInfo.fromPlatform(); - - final intent = AndroidIntent( - action: 'android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS', - data: 'package:${info.packageName}', - ); - await intent.launch(); - await Future.delayed(const Duration(seconds: 1)); - await _loadBatteryOptimizationStatus(); - } - Future _exportFavorites(BuildContext context) async { final appProvider = context.read(); final favorites = appProvider.favoritePackages.toList()..sort(); @@ -375,96 +304,27 @@ class _SettingsScreenState extends State { Column( spacing: 4, children: [ - MListHeader(title: 'Theme Mode'), - MRadioListView( - items: [ - MRadioListItemData( - title: 'Follow system theme', - subtitle: '', - value: ThemeMode.system, - ), - MRadioListItemData( - title: 'Light theme', - subtitle: '', - value: ThemeMode.light, - ), - MRadioListItemData( - title: 'Dark theme', - subtitle: '', - value: ThemeMode.dark, - ), - ], - groupValue: settings.themeMode, - onChanged: (mode) { - settings.setThemeMode(mode); - }, - ), - MListHeader(title: 'Theme Style'), - MRadioListView( - items: [ - MRadioListItemData( - title: 'Material style', - subtitle: '', - value: ThemeStyle.material, - ), - MRadioListItemData( - title: 'Florid style', - subtitle: '', - suffix: Container( - margin: const EdgeInsets.only(right: 8.0), - child: Material( - color: Theme.of(context).colorScheme.secondary, - borderRadius: BorderRadius.circular(99.0), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 2.0, - ), - child: Text( - 'Beta', - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onSecondary, - ), - ), - ), - ), - ), - value: ThemeStyle.florid, - ), - ], - groupValue: settings.themeStyle, - onChanged: (style) { - settings.setThemeStyle(style); - }, + MListHeader( + title: 'General Settings', + icon: Symbols.settings, ), MListView( items: [ MListItemData( - leading: Icon(Symbols.feedback), - title: 'Fedback on Florid theme', - subtitle: - 'Help improve the Florid theme by providing feedback', + leading: Icon(Symbols.palette), + title: 'Appearance', + subtitle: 'Theme mode and style', onTap: () { - launchUrl( - Uri.parse( - 'https://github.com/Nandanrmenon/florid/discussions/5', + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const AppearanceScreen(), ), ); }, - suffix: Icon(Symbols.open_in_new), + suffix: Icon(Symbols.chevron_right), ), - ], - ), - ], - ), - Column( - spacing: 4, - children: [ - MListHeader(title: 'General Settings'), - MListView( - items: [ MListItemData( leading: Icon(Symbols.language), title: 'App content language', @@ -489,187 +349,65 @@ class _SettingsScreenState extends State { subtitle: 'Add or remove F-Droid repositories', suffix: Icon(Symbols.chevron_right), ), + MListItemData( + leading: Icon(Symbols.update), + title: 'Update settings', + subtitle: 'Manage background updates', + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const UpdateSettingsScreen(), + ), + ); + }, + suffix: Icon(Symbols.chevron_right), + ), ], ), ], ), Column( - spacing: 4, + spacing: 4.0, children: [ - MListHeader(title: 'Favourites'), + MListHeader( + title: 'Miscellaneous', + icon: Symbols.more_horiz, + ), MListView( items: [ MListItemData( - leading: Icon(Symbols.upload), + leading: Icon(Symbols.file_upload), title: 'Export favourites', subtitle: 'Save a JSON file to Downloads', onTap: () => _exportFavorites(context), ), MListItemData( - leading: Icon(Symbols.download), + leading: Icon(Symbols.file_download), title: 'Import favourites', subtitle: 'Import favourites from a JSON file', onTap: () => _importFavorites(context), ), ], ), - ], - ), - Column( - spacing: 4, - children: [ - MListHeader(title: 'Background updates'), MListView( items: [ MListItemData( - leading: Icon(Symbols.notifications), - title: 'Check for updates in background', - subtitle: 'Notify when updates are available', - onTap: () async { - await settings.setBackgroundUpdatesEnabled( - !settings.backgroundUpdatesEnabled, + leading: Icon(Symbols.build), + title: 'Troubleshooting', + subtitle: 'Storage, cache, and downloads', + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const TroubleshootingScreen(), + ), ); - await UpdateCheckService.scheduleFromPrefs(); }, - suffix: Switch( - value: settings.backgroundUpdatesEnabled, - onChanged: (value) async { - await settings.setBackgroundUpdatesEnabled( - value, - ); - await UpdateCheckService.scheduleFromPrefs(); - }, - ), - ), - MListItemData( - leading: Icon(Symbols.network_check), - title: 'Update network', - subtitle: _updateNetworkPolicyLabel( - settings.updateNetworkPolicy, - ), - onTap: () => _showUpdateNetworkPolicyDialog( - context, - settings, - ), suffix: Icon(Symbols.chevron_right), ), - MListItemData( - leading: Icon(Symbols.schedule), - title: 'Update interval', - subtitle: _updateIntervalLabel( - settings.updateIntervalHours, - ), - onTap: () => - _showUpdateIntervalDialog(context, settings), - suffix: Icon(Symbols.chevron_right), - ), - if (_isIgnoringBatteryOptimizations == false) - MListItemData( - selected: true, - leading: Icon( - Symbols.battery_saver, - fill: 1, - color: Theme.of(context).colorScheme.error, - ), - title: 'Disable battery optimization', - subtitle: - 'Allow background checks to run reliably', - onTap: _requestDisableBatteryOptimizations, - ), - if (kDebugMode) - MListItemData( - leading: Icon(Symbols.bolt), - title: 'Run debug check in 10s', - subtitle: - 'Shows a test notification and runs after 10s', - onTap: () async { - await UpdateCheckService.showDebugNotificationNow( - 'Debug check scheduled', - ); - await UpdateCheckService.runDebugInApp(); - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Debug update check will run in 10 seconds', - ), - ), - ); - }, - ), - ], - ), - ], - ), - - Column( - spacing: 4, - children: [ - MListHeader(title: 'Downloads & Storage'), - MListView( - items: [ - MListItemData( - title: 'Auto-install after download', - onTap: () { - settings.setAutoInstallApk( - !settings.autoInstallApk, - ); - }, - subtitle: - 'Install APKs automatically once download finishes', - suffix: Switch( - value: settings.autoInstallApk, - onChanged: (value) { - settings.setAutoInstallApk(value); - }, - ), - ), - MListItemData( - title: 'Delete APK after install', - onTap: () { - settings.setAutoInstallApk( - !settings.autoInstallApk, - ); - }, - subtitle: - 'Remove installer files after successful installation', - suffix: Switch( - value: settings.autoDeleteApk, - onChanged: (value) { - settings.setAutoDeleteApk(value); - }, - ), - ), - ], - ), - MListView( - items: [ - MListItemData( - leading: Icon(Symbols.cleaning_services), - title: 'Clear repository cache', - onTap: () { - _clearRepoCache(context); - }, - subtitle: - 'Refresh app list and metadata on next load', - ), - MListItemData( - leading: Icon(Symbols.delete_sweep), - title: 'Clear APK downloads', - onTap: () { - _clearApkDownloads(context); - }, - subtitle: - 'Remove downloaded installer files from storage', - ), - MListItemData( - leading: Icon(Symbols.image_not_supported), - title: 'Clear image cache', - onTap: () { - _clearImageCache(context); - }, - subtitle: 'Remove cached icons and screenshots', - ), ], ), ], @@ -678,7 +416,7 @@ class _SettingsScreenState extends State { Column( spacing: 4, children: [ - MListHeader(title: 'About'), + MListHeader(title: 'About', icon: Symbols.android), MListView( items: [ MListItemData( diff --git a/lib/screens/troubleshooting_screen.dart b/lib/screens/troubleshooting_screen.dart new file mode 100644 index 0000000..102a01e --- /dev/null +++ b/lib/screens/troubleshooting_screen.dart @@ -0,0 +1,133 @@ +import 'package:florid/providers/download_provider.dart'; +import 'package:florid/providers/settings_provider.dart'; +import 'package:florid/services/fdroid_api_service.dart'; +import 'package:florid/widgets/m_list.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:provider/provider.dart'; + +class TroubleshootingScreen extends StatelessWidget { + const TroubleshootingScreen({super.key}); + + Future _clearRepoCache(BuildContext context) async { + final api = context.read(); + await api.clearRepositoryCache(); + if (!context.mounted) return; + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Repository cache cleared'))); + } + + Future _clearImageCache(BuildContext context) async { + await DefaultCacheManager().emptyCache(); + imageCache.clear(); + imageCache.clearLiveImages(); + if (!context.mounted) return; + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Image cache cleared'))); + } + + Future _clearApkDownloads(BuildContext context) async { + final deleted = await context.read().clearAllDownloads(); + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + deleted > 0 + ? 'Deleted $deleted APK file${deleted == 1 ? '' : 's'}' + : 'No APK downloads to delete', + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, settings, _) { + return Scaffold( + appBar: AppBar(title: const Text('Troubleshooting')), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16, + children: [ + Column( + spacing: 4, + children: [ + MListHeader(title: 'Downloads & Storage'), + MListView( + items: [ + MListItemData( + title: 'Auto-install after download', + onTap: () { + settings.setAutoInstallApk( + !settings.autoInstallApk, + ); + }, + subtitle: + 'Install APKs automatically once download finishes', + suffix: Switch( + value: settings.autoInstallApk, + onChanged: (value) { + settings.setAutoInstallApk(value); + }, + ), + ), + MListItemData( + title: 'Delete APK after install', + onTap: () { + settings.setAutoInstallApk( + !settings.autoInstallApk, + ); + }, + subtitle: + 'Remove installer files after successful installation', + suffix: Switch( + value: settings.autoDeleteApk, + onChanged: (value) { + settings.setAutoDeleteApk(value); + }, + ), + ), + ], + ), + MListView( + items: [ + MListItemData( + leading: Icon(Symbols.cleaning_services), + title: 'Clear repository cache', + onTap: () => _clearRepoCache(context), + subtitle: + 'Refresh app list and metadata on next load', + ), + MListItemData( + leading: Icon(Symbols.delete_sweep), + title: 'Clear APK downloads', + onTap: () => _clearApkDownloads(context), + subtitle: + 'Remove downloaded installer files from storage', + ), + MListItemData( + leading: Icon(Symbols.image_not_supported), + title: 'Clear image cache', + onTap: () => _clearImageCache(context), + subtitle: 'Remove cached icons and screenshots', + ), + ], + ), + ], + ), + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/screens/update_settings_screen.dart b/lib/screens/update_settings_screen.dart new file mode 100644 index 0000000..7351cb2 --- /dev/null +++ b/lib/screens/update_settings_screen.dart @@ -0,0 +1,290 @@ +import 'dart:io'; + +import 'package:android_intent_plus/android_intent.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:provider/provider.dart'; + +import '../providers/settings_provider.dart'; +import '../services/update_check_service.dart'; +import '../widgets/m_list.dart'; + +class UpdateSettingsScreen extends StatefulWidget { + const UpdateSettingsScreen({super.key}); + + @override + State createState() => _UpdateSettingsScreenState(); +} + +class _UpdateSettingsScreenState extends State { + static const MethodChannel _batteryChannel = MethodChannel( + 'florid/battery_optimizations', + ); + + bool? _isIgnoringBatteryOptimizations; + + @override + void initState() { + super.initState(); + _loadBatteryOptimizationStatus(); + } + + Future _loadBatteryOptimizationStatus() async { + if (!Platform.isAndroid) return; + try { + final isIgnoring = await _batteryChannel.invokeMethod( + 'isIgnoringBatteryOptimizations', + ); + if (!mounted) return; + setState(() { + _isIgnoringBatteryOptimizations = isIgnoring ?? false; + }); + } on PlatformException { + if (!mounted) return; + setState(() { + _isIgnoringBatteryOptimizations = null; + }); + } + } + + Future _requestDisableBatteryOptimizations() async { + if (!Platform.isAndroid) return; + final info = await PackageInfo.fromPlatform(); + + final intent = AndroidIntent( + action: 'android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS', + data: 'package:${info.packageName}', + ); + await intent.launch(); + await Future.delayed(const Duration(seconds: 1)); + await _loadBatteryOptimizationStatus(); + } + + String _updateNetworkPolicyLabel(UpdateNetworkPolicy policy) { + switch (policy) { + case UpdateNetworkPolicy.wifiOnly: + return 'Wi-Fi only'; + case UpdateNetworkPolicy.wifiAndCharging: + return 'Wi-Fi + charging'; + case UpdateNetworkPolicy.any: + return 'Mobile data or Wi-Fi'; + } + } + + String _updateIntervalLabel(int hours) { + switch (hours) { + case 1: + return 'Every 1 hour'; + case 2: + return 'Every 2 hours'; + case 3: + return 'Every 3 hours'; + case 6: + return 'Every 6 hours'; + case 12: + return 'Every 12 hours'; + case 24: + return 'Daily'; + default: + return 'Every $hours hours'; + } + } + + Future _showUpdateNetworkPolicyDialog( + BuildContext context, + SettingsProvider settings, + ) async { + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Update network'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: UpdateNetworkPolicy.values + .map( + (policy) => RadioListTile( + value: policy, + groupValue: settings.updateNetworkPolicy, + title: Text(_updateNetworkPolicyLabel(policy)), + onChanged: (value) async { + if (value == null) return; + await settings.setUpdateNetworkPolicy(value); + await UpdateCheckService.scheduleFromPrefs(); + if (!context.mounted) return; + Navigator.of(context).pop(); + }, + ), + ) + .toList(), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Close'), + ), + ], + ), + ); + } + + Future _showUpdateIntervalDialog( + BuildContext context, + SettingsProvider settings, + ) async { + const intervals = [1, 2, 3, 6, 12, 24]; + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Update interval'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: intervals + .map( + (hours) => RadioListTile( + value: hours, + groupValue: settings.updateIntervalHours, + title: Text(_updateIntervalLabel(hours)), + onChanged: (value) async { + if (value == null) return; + await settings.setUpdateIntervalHours(value); + await UpdateCheckService.scheduleFromPrefs(); + if (!context.mounted) return; + Navigator.of(context).pop(); + }, + ), + ) + .toList(), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Close'), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, settings, _) { + return Scaffold( + appBar: AppBar(title: const Text('Update settings')), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16, + children: [ + Column( + spacing: 4, + children: [ + MListHeader(title: 'Background updates'), + MListView( + items: [ + MListItemData( + leading: Icon(Symbols.notifications), + title: 'Check for updates in background', + subtitle: 'Notify when updates are available', + onTap: () async { + await settings.setBackgroundUpdatesEnabled( + !settings.backgroundUpdatesEnabled, + ); + await UpdateCheckService.scheduleFromPrefs(); + }, + suffix: Switch( + value: settings.backgroundUpdatesEnabled, + onChanged: (value) async { + await settings.setBackgroundUpdatesEnabled( + value, + ); + await UpdateCheckService.scheduleFromPrefs(); + }, + ), + ), + MListItemData( + leading: Icon(Symbols.network_check), + title: 'Update network', + subtitle: _updateNetworkPolicyLabel( + settings.updateNetworkPolicy, + ), + onTap: () => _showUpdateNetworkPolicyDialog( + context, + settings, + ), + suffix: Icon(Symbols.chevron_right), + ), + MListItemData( + leading: Icon(Symbols.schedule), + title: 'Update interval', + subtitle: _updateIntervalLabel( + settings.updateIntervalHours, + ), + onTap: () => + _showUpdateIntervalDialog(context, settings), + suffix: Icon(Symbols.chevron_right), + ), + ], + ), + ], + ), + Column( + spacing: 4, + children: [ + if (settings.backgroundUpdatesEnabled && + _isIgnoringBatteryOptimizations == false) + MListHeader(title: 'Reliability'), + MListView( + items: [ + if (settings.backgroundUpdatesEnabled && + _isIgnoringBatteryOptimizations == false) + MListItemData( + leading: Icon( + Symbols.battery_saver, + fill: 1, + color: Theme.of(context).colorScheme.error, + ), + title: 'Disable battery optimization', + subtitle: + 'Allow background checks to run reliably', + onTap: _requestDisableBatteryOptimizations, + ), + if (kDebugMode) + MListItemData( + leading: Icon(Symbols.bolt), + title: 'Run debug check in 10s', + subtitle: + 'Shows a test notification and runs after 10s', + onTap: () async { + await UpdateCheckService.showDebugNotificationNow( + 'Debug check scheduled', + ); + await UpdateCheckService.runDebugInApp(); + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Debug update check will run in 10 seconds', + ), + ), + ); + }, + ), + ], + ), + ], + ), + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/widgets/m_list.dart b/lib/widgets/m_list.dart index e6ae4b9..c7b8c03 100644 --- a/lib/widgets/m_list.dart +++ b/lib/widgets/m_list.dart @@ -248,7 +248,7 @@ class MRadioListView extends StatelessWidget { color: Theme.of(context).colorScheme.surfaceContainer, clipBehavior: Clip.antiAlias, child: RadioListTile( - contentPadding: EdgeInsets.only(left: 16.0, right: 4.0), + contentPadding: EdgeInsets.only(left: 16.0, right: 18.0), title: Text(items[index].title), subtitle: items[index].subtitle.isNotEmpty ? Text(items[index].subtitle)