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"
},
"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": {
"description": "Beschriftung der Spracheinstellung"

View File

@@ -217,6 +217,16 @@
"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": {
"description": "Language setting label"

View File

@@ -217,6 +217,16 @@
"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": {
"description": "Etiqueta de la configuración de idioma"

View File

@@ -217,6 +217,16 @@
"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": {
"description": "Libellé du paramètre de langue"

View File

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

View File

@@ -217,6 +217,16 @@
"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": {
"description": "Etichetta impostazione lingua"

View File

@@ -342,6 +342,18 @@ abstract class AppLocalizations {
/// **'Hide non-essential information in messages and contacts'**
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
///
/// In en, this message translates to:

View File

@@ -137,6 +137,13 @@ class AppLocalizationsDe extends AppLocalizations {
String get simpleModeDescription =>
'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
String get language => 'Sprache';

View File

@@ -136,6 +136,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get simpleModeDescription =>
'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
String get language => 'Language';

View File

@@ -136,6 +136,13 @@ class AppLocalizationsEs extends AppLocalizations {
String get simpleModeDescription =>
'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
String get language => 'Idioma';

View File

@@ -137,6 +137,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get simpleModeDescription =>
'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
String get language => 'Langue';

View File

@@ -136,6 +136,12 @@ class AppLocalizationsHr extends AppLocalizations {
String get simpleModeDescription =>
'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
String get language => 'Jezik';

View File

@@ -136,6 +136,13 @@ class AppLocalizationsIt extends AppLocalizations {
String get simpleModeDescription =>
'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
String get language => 'Lingua';

View File

@@ -136,6 +136,13 @@ class AppLocalizationsSl extends AppLocalizations {
String get simpleModeDescription =>
'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
String get language => 'Jezik';

View File

@@ -79,6 +79,10 @@
"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",
"chooseLanguage": "Izberite jezik",

View File

@@ -28,6 +28,9 @@ class AppProvider with ChangeNotifier {
bool _isSimpleMode = true;
bool get isSimpleMode => _isSimpleMode;
bool _isMapEnabled = true;
bool get isMapEnabled => _isMapEnabled;
AppProvider({
required this.connectionProvider,
required this.contactsProvider,
@@ -40,6 +43,7 @@ class AppProvider with ChangeNotifier {
_initializeTileCache();
_initializeLocationTracking();
_loadSimpleMode();
_loadMapEnabled();
_syncDrawingsOnStartup(); // Sync drawings immediately after providers load
_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
Future<void> _initializeTileCache() async {
try {

View File

@@ -47,20 +47,12 @@ class _HomeScreenState extends State<HomeScreen>
int _currentIndex = 0;
bool _isMapFullscreen = false;
bool _showRxTxIndicators = true;
bool _isMapEnabled = true;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_tabController.addListener(() {
setState(() {
_currentIndex = _tabController.index;
// Exit fullscreen when switching away from map tab
if (_currentIndex != 2) {
_isMapFullscreen = false;
}
});
});
_loadMapEnabledAndInitTabs();
_loadRxTxPreference();
// 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 {
final prefs = await SharedPreferences.getInstance();
if (mounted) {
@@ -82,6 +131,7 @@ class _HomeScreenState extends State<HomeScreen>
@override
void dispose() {
_tabController.removeListener(_onTabChanged);
_tabController.dispose();
super.dispose();
}
@@ -225,8 +275,16 @@ class _HomeScreenState extends State<HomeScreen>
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)
final shouldHideUI = _isMapFullscreen && _currentIndex == 2;
final shouldHideUI = _isMapEnabled && _isMapFullscreen && _currentIndex == 2;
return Scaffold(
appBar: shouldHideUI
@@ -308,16 +366,25 @@ class _HomeScreenState extends State<HomeScreen>
body: TabBarView(
controller: _tabController,
children: [
MessagesTab(onNavigateToMap: () => _tabController.animateTo(2)),
ContactsTab(onNavigateToMap: () => _tabController.animateTo(2)),
MapTab(
onFullscreenChanged: (isFullscreen) {
setState(() {
_isMapFullscreen = isFullscreen;
});
},
onNavigateToMessages: () => _tabController.animateTo(0),
MessagesTab(
onNavigateToMap: _isMapEnabled
? () => _tabController.animateTo(2)
: null,
),
ContactsTab(
onNavigateToMap: _isMapEnabled
? () => _tabController.animateTo(2)
: null,
),
if (_isMapEnabled)
MapTab(
onFullscreenChanged: (isFullscreen) {
setState(() {
_isMapFullscreen = isFullscreen;
});
},
onNavigateToMessages: () => _tabController.animateTo(0),
),
],
),
bottomNavigationBar: shouldHideUI
@@ -354,10 +421,11 @@ class _HomeScreenState extends State<HomeScreen>
),
text: AppLocalizations.of(context)!.contacts,
),
Tab(
icon: const Icon(Icons.map),
text: AppLocalizations.of(context)!.map,
),
if (_isMapEnabled)
Tab(
icon: const Icon(Icons.map),
text: AppLocalizations.of(context)!.map,
),
],
),
);

View File

@@ -18,9 +18,9 @@ import '../utils/toast_logger.dart';
import '../l10n/app_localizations.dart';
class MessagesTab extends StatefulWidget {
final VoidCallback onNavigateToMap;
final VoidCallback? onNavigateToMap;
const MessagesTab({super.key, required this.onNavigateToMap});
const MessagesTab({super.key, this.onNavigateToMap});
@override
State<MessagesTab> createState() => _MessagesTabState();
@@ -780,7 +780,8 @@ class _MessagesTabState extends State<MessagesTab> {
isHighlighted: isHighlighted,
onNavigateToMap: widget.onNavigateToMap,
onTap:
message.isSarMarker &&
widget.onNavigateToMap != null &&
message.isSarMarker &&
message.sarGpsCoordinates != null
? () {
final mapProvider = context
@@ -789,9 +790,10 @@ class _MessagesTabState extends State<MessagesTab> {
location: message.sarGpsCoordinates!,
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}');
final mapProvider = context
@@ -802,7 +804,7 @@ class _MessagesTabState extends State<MessagesTab> {
message.drawingId!,
drawingProvider,
);
widget.onNavigateToMap();
widget.onNavigateToMap?.call();
}
: 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(
leading: const Icon(Icons.language),
title: Text(AppLocalizations.of(context)!.language),