feat: Add setting to disable map tab for battery savings

Add a new "Disable Map" toggle in settings that allows users to hide
the map tab entirely. This is useful for scenarios where the map is
not needed and battery conservation is a priority.

Changes:
- Add disableMap and disableMapDescription localized strings (all languages)
- Add isMapEnabled state to AppProvider with persistence
- Update HomeScreen to dynamically adjust tab count based on setting
- Make onNavigateToMap optional in MessagesTab and ContactsTab
- Add toggle switch in Settings screen under Interface section
This commit is contained in:
Janez Troha
2025-12-04 18:33:00 +01:00
parent 53aa8e57e2
commit 3e2e4aa4d9
19 changed files with 258 additions and 30 deletions

View File

@@ -217,6 +217,16 @@
"description": "Beschreibung für die Einstellung des einfachen Modus" "description": "Beschreibung für die Einstellung des einfachen Modus"
}, },
"disableMap": "Karte deaktivieren",
"@disableMap": {
"description": "Einstellung zum Deaktivieren des Karten-Tabs"
},
"disableMapDescription": "Karten-Tab ausblenden, um Akku zu sparen",
"@disableMapDescription": {
"description": "Beschreibung für die Einstellung zum Deaktivieren der Karte"
},
"language": "Sprache", "language": "Sprache",
"@language": { "@language": {
"description": "Beschriftung der Spracheinstellung" "description": "Beschriftung der Spracheinstellung"

View File

@@ -217,6 +217,16 @@
"description": "Description for simple mode setting" "description": "Description for simple mode setting"
}, },
"disableMap": "Disable Map",
"@disableMap": {
"description": "Setting to disable the map tab"
},
"disableMapDescription": "Hide the map tab to reduce battery usage",
"@disableMapDescription": {
"description": "Description for disable map setting"
},
"language": "Language", "language": "Language",
"@language": { "@language": {
"description": "Language setting label" "description": "Language setting label"

View File

@@ -217,6 +217,16 @@
"description": "Descripción de la configuración del modo simple" "description": "Descripción de la configuración del modo simple"
}, },
"disableMap": "Desactivar mapa",
"@disableMap": {
"description": "Configuración para desactivar la pestaña del mapa"
},
"disableMapDescription": "Ocultar la pestaña del mapa para reducir el uso de batería",
"@disableMapDescription": {
"description": "Descripción de la configuración para desactivar el mapa"
},
"language": "Idioma", "language": "Idioma",
"@language": { "@language": {
"description": "Etiqueta de la configuración de idioma" "description": "Etiqueta de la configuración de idioma"

View File

@@ -217,6 +217,16 @@
"description": "Description du paramètre du mode simple" "description": "Description du paramètre du mode simple"
}, },
"disableMap": "Désactiver la carte",
"@disableMap": {
"description": "Paramètre pour désactiver l'onglet carte"
},
"disableMapDescription": "Masquer l'onglet carte pour réduire la consommation de batterie",
"@disableMapDescription": {
"description": "Description du paramètre pour désactiver la carte"
},
"language": "Langue", "language": "Langue",
"@language": { "@language": {
"description": "Libellé du paramètre de langue" "description": "Libellé du paramètre de langue"

View File

@@ -79,6 +79,10 @@
"simpleModeDescription": "Sakrij nevažne informacije u porukama i kontaktima", "simpleModeDescription": "Sakrij nevažne informacije u porukama i kontaktima",
"disableMap": "Onemogući kartu",
"disableMapDescription": "Sakrij karticu karte za uštedu baterije",
"language": "Jezik", "language": "Jezik",
"chooseLanguage": "Odaberite jezik", "chooseLanguage": "Odaberite jezik",

View File

@@ -217,6 +217,16 @@
"description": "Descrizione per l'impostazione della modalità semplice" "description": "Descrizione per l'impostazione della modalità semplice"
}, },
"disableMap": "Disabilita mappa",
"@disableMap": {
"description": "Impostazione per disabilitare la scheda mappa"
},
"disableMapDescription": "Nascondi la scheda mappa per ridurre il consumo della batteria",
"@disableMapDescription": {
"description": "Descrizione per l'impostazione di disabilitazione mappa"
},
"language": "Lingua", "language": "Lingua",
"@language": { "@language": {
"description": "Etichetta impostazione lingua" "description": "Etichetta impostazione lingua"

View File

@@ -342,6 +342,18 @@ abstract class AppLocalizations {
/// **'Hide non-essential information in messages and contacts'** /// **'Hide non-essential information in messages and contacts'**
String get simpleModeDescription; String get simpleModeDescription;
/// Setting to disable the map tab
///
/// In en, this message translates to:
/// **'Disable Map'**
String get disableMap;
/// Description for disable map setting
///
/// In en, this message translates to:
/// **'Hide the map tab to reduce battery usage'**
String get disableMapDescription;
/// Language setting label /// Language setting label
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View File

@@ -137,6 +137,13 @@ class AppLocalizationsDe extends AppLocalizations {
String get simpleModeDescription => String get simpleModeDescription =>
'Nicht wesentliche Informationen in Nachrichten und Kontakten ausblenden'; 'Nicht wesentliche Informationen in Nachrichten und Kontakten ausblenden';
@override
String get disableMap => 'Karte deaktivieren';
@override
String get disableMapDescription =>
'Karten-Tab ausblenden, um Akku zu sparen';
@override @override
String get language => 'Sprache'; String get language => 'Sprache';

View File

@@ -136,6 +136,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get simpleModeDescription => String get simpleModeDescription =>
'Hide non-essential information in messages and contacts'; 'Hide non-essential information in messages and contacts';
@override
String get disableMap => 'Disable Map';
@override
String get disableMapDescription =>
'Hide the map tab to reduce battery usage';
@override @override
String get language => 'Language'; String get language => 'Language';

View File

@@ -136,6 +136,13 @@ class AppLocalizationsEs extends AppLocalizations {
String get simpleModeDescription => String get simpleModeDescription =>
'Ocultar información no esencial en mensajes y contactos'; 'Ocultar información no esencial en mensajes y contactos';
@override
String get disableMap => 'Desactivar mapa';
@override
String get disableMapDescription =>
'Ocultar la pestaña del mapa para reducir el uso de batería';
@override @override
String get language => 'Idioma'; String get language => 'Idioma';

View File

@@ -137,6 +137,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get simpleModeDescription => String get simpleModeDescription =>
'Masquer les informations non essentielles dans les messages et les contacts'; 'Masquer les informations non essentielles dans les messages et les contacts';
@override
String get disableMap => 'Désactiver la carte';
@override
String get disableMapDescription =>
'Masquer l\'onglet carte pour réduire la consommation de batterie';
@override @override
String get language => 'Langue'; String get language => 'Langue';

View File

@@ -136,6 +136,12 @@ class AppLocalizationsHr extends AppLocalizations {
String get simpleModeDescription => String get simpleModeDescription =>
'Sakrij nevažne informacije u porukama i kontaktima'; 'Sakrij nevažne informacije u porukama i kontaktima';
@override
String get disableMap => 'Onemogući kartu';
@override
String get disableMapDescription => 'Sakrij karticu karte za uštedu baterije';
@override @override
String get language => 'Jezik'; String get language => 'Jezik';

View File

@@ -136,6 +136,13 @@ class AppLocalizationsIt extends AppLocalizations {
String get simpleModeDescription => String get simpleModeDescription =>
'Nascondi informazioni non essenziali nei messaggi e contatti'; 'Nascondi informazioni non essenziali nei messaggi e contatti';
@override
String get disableMap => 'Disabilita mappa';
@override
String get disableMapDescription =>
'Nascondi la scheda mappa per ridurre il consumo della batteria';
@override @override
String get language => 'Lingua'; String get language => 'Lingua';

View File

@@ -136,6 +136,13 @@ class AppLocalizationsSl extends AppLocalizations {
String get simpleModeDescription => String get simpleModeDescription =>
'Skrij nepomembne informacije v sporočilih in kontaktih'; 'Skrij nepomembne informacije v sporočilih in kontaktih';
@override
String get disableMap => 'Onemogoči zemljevid';
@override
String get disableMapDescription =>
'Skrij zavihek z zemljevidom za varčevanje z baterijo';
@override @override
String get language => 'Jezik'; String get language => 'Jezik';

View File

@@ -79,6 +79,10 @@
"simpleModeDescription": "Skrij nepomembne informacije v sporočilih in kontaktih", "simpleModeDescription": "Skrij nepomembne informacije v sporočilih in kontaktih",
"disableMap": "Onemogoči zemljevid",
"disableMapDescription": "Skrij zavihek z zemljevidom za varčevanje z baterijo",
"language": "Jezik", "language": "Jezik",
"chooseLanguage": "Izberite jezik", "chooseLanguage": "Izberite jezik",

View File

@@ -28,6 +28,9 @@ class AppProvider with ChangeNotifier {
bool _isSimpleMode = true; bool _isSimpleMode = true;
bool get isSimpleMode => _isSimpleMode; bool get isSimpleMode => _isSimpleMode;
bool _isMapEnabled = true;
bool get isMapEnabled => _isMapEnabled;
AppProvider({ AppProvider({
required this.connectionProvider, required this.connectionProvider,
required this.contactsProvider, required this.contactsProvider,
@@ -40,6 +43,7 @@ class AppProvider with ChangeNotifier {
_initializeTileCache(); _initializeTileCache();
_initializeLocationTracking(); _initializeLocationTracking();
_loadSimpleMode(); _loadSimpleMode();
_loadMapEnabled();
_syncDrawingsOnStartup(); // Sync drawings immediately after providers load _syncDrawingsOnStartup(); // Sync drawings immediately after providers load
_isInitialized = true; _isInitialized = true;
} }
@@ -84,6 +88,29 @@ class AppProvider with ChangeNotifier {
} }
} }
/// Load map enabled setting from shared preferences
Future<void> _loadMapEnabled() async {
try {
final prefs = await SharedPreferences.getInstance();
_isMapEnabled = prefs.getBool('map_enabled') ?? true;
notifyListeners();
} catch (e) {
debugPrint('Error loading map enabled setting: $e');
}
}
/// Toggle map on/off
Future<void> toggleMapEnabled(bool enabled) async {
try {
_isMapEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('map_enabled', enabled);
notifyListeners();
} catch (e) {
debugPrint('Error saving map enabled setting: $e');
}
}
/// Initialize tile cache service /// Initialize tile cache service
Future<void> _initializeTileCache() async { Future<void> _initializeTileCache() async {
try { try {

View File

@@ -47,20 +47,12 @@ class _HomeScreenState extends State<HomeScreen>
int _currentIndex = 0; int _currentIndex = 0;
bool _isMapFullscreen = false; bool _isMapFullscreen = false;
bool _showRxTxIndicators = true; bool _showRxTxIndicators = true;
bool _isMapEnabled = true;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_tabController = TabController(length: 3, vsync: this); _loadMapEnabledAndInitTabs();
_tabController.addListener(() {
setState(() {
_currentIndex = _tabController.index;
// Exit fullscreen when switching away from map tab
if (_currentIndex != 2) {
_isMapFullscreen = false;
}
});
});
_loadRxTxPreference(); _loadRxTxPreference();
// Show permission dialog after the first frame if needed // Show permission dialog after the first frame if needed
@@ -71,6 +63,63 @@ class _HomeScreenState extends State<HomeScreen>
} }
} }
Future<void> _loadMapEnabledAndInitTabs() async {
final prefs = await SharedPreferences.getInstance();
final mapEnabled = prefs.getBool('map_enabled') ?? true;
if (mapEnabled != _isMapEnabled) {
_isMapEnabled = mapEnabled;
}
_initTabController();
if (mounted) {
setState(() {});
}
}
void _initTabController() {
final tabCount = _isMapEnabled ? 3 : 2;
_tabController = TabController(length: tabCount, vsync: this);
_tabController.addListener(_onTabChanged);
}
void _onTabChanged() {
setState(() {
_currentIndex = _tabController.index;
// Exit fullscreen when switching away from map tab (only if map is enabled and is tab 2)
if (_isMapEnabled && _currentIndex != 2) {
_isMapFullscreen = false;
}
});
}
void _updateTabController(bool mapEnabled) {
if (_isMapEnabled == mapEnabled) return;
// Save current index before rebuilding
final oldIndex = _tabController.index;
// Remove old listener and dispose
_tabController.removeListener(_onTabChanged);
_tabController.dispose();
// Update state
_isMapEnabled = mapEnabled;
// Create new controller
final tabCount = mapEnabled ? 3 : 2;
_tabController = TabController(length: tabCount, vsync: this);
_tabController.addListener(_onTabChanged);
// Restore index (clamp to valid range)
if (oldIndex < tabCount) {
_tabController.index = oldIndex;
_currentIndex = oldIndex;
} else {
_currentIndex = tabCount - 1;
}
setState(() {});
}
Future<void> _loadRxTxPreference() async { Future<void> _loadRxTxPreference() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
if (mounted) { if (mounted) {
@@ -82,6 +131,7 @@ class _HomeScreenState extends State<HomeScreen>
@override @override
void dispose() { void dispose() {
_tabController.removeListener(_onTabChanged);
_tabController.dispose(); _tabController.dispose();
super.dispose(); super.dispose();
} }
@@ -225,8 +275,16 @@ class _HomeScreenState extends State<HomeScreen>
messagesProvider.setLocalizations(localizations); messagesProvider.setLocalizations(localizations);
} }
// Check if map enabled setting changed and update tab controller
final appProvider = context.watch<AppProvider>();
if (_isMapEnabled != appProvider.isMapEnabled) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateTabController(appProvider.isMapEnabled);
});
}
// Determine if we should hide the UI (only in fullscreen on map tab) // Determine if we should hide the UI (only in fullscreen on map tab)
final shouldHideUI = _isMapFullscreen && _currentIndex == 2; final shouldHideUI = _isMapEnabled && _isMapFullscreen && _currentIndex == 2;
return Scaffold( return Scaffold(
appBar: shouldHideUI appBar: shouldHideUI
@@ -308,8 +366,17 @@ class _HomeScreenState extends State<HomeScreen>
body: TabBarView( body: TabBarView(
controller: _tabController, controller: _tabController,
children: [ children: [
MessagesTab(onNavigateToMap: () => _tabController.animateTo(2)), MessagesTab(
ContactsTab(onNavigateToMap: () => _tabController.animateTo(2)), onNavigateToMap: _isMapEnabled
? () => _tabController.animateTo(2)
: null,
),
ContactsTab(
onNavigateToMap: _isMapEnabled
? () => _tabController.animateTo(2)
: null,
),
if (_isMapEnabled)
MapTab( MapTab(
onFullscreenChanged: (isFullscreen) { onFullscreenChanged: (isFullscreen) {
setState(() { setState(() {
@@ -354,6 +421,7 @@ class _HomeScreenState extends State<HomeScreen>
), ),
text: AppLocalizations.of(context)!.contacts, text: AppLocalizations.of(context)!.contacts,
), ),
if (_isMapEnabled)
Tab( Tab(
icon: const Icon(Icons.map), icon: const Icon(Icons.map),
text: AppLocalizations.of(context)!.map, text: AppLocalizations.of(context)!.map,

View File

@@ -18,9 +18,9 @@ import '../utils/toast_logger.dart';
import '../l10n/app_localizations.dart'; import '../l10n/app_localizations.dart';
class MessagesTab extends StatefulWidget { class MessagesTab extends StatefulWidget {
final VoidCallback onNavigateToMap; final VoidCallback? onNavigateToMap;
const MessagesTab({super.key, required this.onNavigateToMap}); const MessagesTab({super.key, this.onNavigateToMap});
@override @override
State<MessagesTab> createState() => _MessagesTabState(); State<MessagesTab> createState() => _MessagesTabState();
@@ -780,6 +780,7 @@ class _MessagesTabState extends State<MessagesTab> {
isHighlighted: isHighlighted, isHighlighted: isHighlighted,
onNavigateToMap: widget.onNavigateToMap, onNavigateToMap: widget.onNavigateToMap,
onTap: onTap:
widget.onNavigateToMap != null &&
message.isSarMarker && message.isSarMarker &&
message.sarGpsCoordinates != null message.sarGpsCoordinates != null
? () { ? () {
@@ -789,9 +790,10 @@ class _MessagesTabState extends State<MessagesTab> {
location: message.sarGpsCoordinates!, location: message.sarGpsCoordinates!,
zoom: 15.0, zoom: 15.0,
); );
widget.onNavigateToMap(); widget.onNavigateToMap?.call();
} }
: message.isDrawing && message.drawingId != null : widget.onNavigateToMap != null &&
message.isDrawing && message.drawingId != null
? () { ? () {
debugPrint('🗺️ [MessagesTab] Drawing tapped! ID: ${message.drawingId}'); debugPrint('🗺️ [MessagesTab] Drawing tapped! ID: ${message.drawingId}');
final mapProvider = context final mapProvider = context
@@ -802,7 +804,7 @@ class _MessagesTabState extends State<MessagesTab> {
message.drawingId!, message.drawingId!,
drawingProvider, drawingProvider,
); );
widget.onNavigateToMap(); widget.onNavigateToMap?.call();
} }
: null, : null,
); );

View File

@@ -528,6 +528,19 @@ class _SettingsScreenState extends State<SettingsScreen> {
}, },
), ),
), ),
Consumer<AppProvider>(
builder: (context, appProvider, child) => SwitchListTile(
secondary: const Icon(Icons.map_outlined),
title: Text(AppLocalizations.of(context)!.disableMap),
subtitle: Text(
AppLocalizations.of(context)!.disableMapDescription,
),
value: !appProvider.isMapEnabled,
onChanged: (value) async {
await appProvider.toggleMapEnabled(!value);
},
),
),
ListTile( ListTile(
leading: const Icon(Icons.language), leading: const Icon(Icons.language),
title: Text(AppLocalizations.of(context)!.language), title: Text(AppLocalizations.of(context)!.language),