From b3a87fc56acd2aabd92bc4db5adf48e60118ca89 Mon Sep 17 00:00:00 2001 From: stopflock Date: Mon, 29 Sep 2025 23:56:28 -0500 Subject: [PATCH] Move settings around part 2 --- lib/dev_config.dart | 2 +- lib/localizations/en.json | 3 +- lib/localizations/es.json | 3 +- lib/localizations/fr.json | 3 +- lib/screens/advanced_settings_screen.dart | 3 + lib/screens/settings_screen.dart | 3 - .../language_section.dart | 12 +- .../operator_profile_list_section.dart | 5 +- .../profile_list_section.dart | 5 +- .../proximity_alerts_section.dart | 8 +- .../tile_provider_section.dart | 170 ++++++++++++++++-- 11 files changed, 178 insertions(+), 39 deletions(-) diff --git a/lib/dev_config.dart b/lib/dev_config.dart index 70d53b1..e597807 100644 --- a/lib/dev_config.dart +++ b/lib/dev_config.dart @@ -39,7 +39,7 @@ const String kClientName = 'DeFlock'; const String kClientVersion = '0.9.13'; // Development/testing features - set to false for production builds -const bool kEnableDevelopmentModes = false; // Set to false to hide sandbox/simulate modes and force production mode +const bool kEnableDevelopmentModes = true; // Set to false to hide sandbox/simulate modes and force production mode // Marker/node interaction const int kCameraMinZoomLevel = 10; // Minimum zoom to show nodes (Overpass) diff --git a/lib/localizations/en.json b/lib/localizations/en.json index e36c5d6..67d7aec 100644 --- a/lib/localizations/en.json +++ b/lib/localizations/en.json @@ -42,7 +42,8 @@ "offlineSettings": "Offline Settings", "offlineSettingsSubtitle": "Manage offline mode and downloaded areas", "advancedSettings": "Advanced Settings", - "advancedSettingsSubtitle": "Performance and tile provider settings" + "advancedSettingsSubtitle": "Performance, alerts, and tile provider settings", + "proximityAlerts": "Proximity Alerts" }, "node": { "title": "Node #{}", diff --git a/lib/localizations/es.json b/lib/localizations/es.json index 1b52824..016ecd7 100644 --- a/lib/localizations/es.json +++ b/lib/localizations/es.json @@ -42,7 +42,8 @@ "offlineSettings": "Configuración Sin Conexión", "offlineSettingsSubtitle": "Gestionar modo sin conexión y áreas descargadas", "advancedSettings": "Configuración Avanzada", - "advancedSettingsSubtitle": "Configuración de rendimiento y proveedores de teselas" + "advancedSettingsSubtitle": "Configuración de rendimiento, alertas y proveedores de teselas", + "proximityAlerts": "Alertas de Proximidad" }, "node": { "title": "Nodo #{}", diff --git a/lib/localizations/fr.json b/lib/localizations/fr.json index c34b846..46017e3 100644 --- a/lib/localizations/fr.json +++ b/lib/localizations/fr.json @@ -42,7 +42,8 @@ "offlineSettings": "Paramètres Hors Ligne", "offlineSettingsSubtitle": "Gérer le mode hors ligne et les zones téléchargées", "advancedSettings": "Paramètres Avancés", - "advancedSettingsSubtitle": "Paramètres de performance et de fournisseurs de tuiles" + "advancedSettingsSubtitle": "Paramètres de performance, alertes et fournisseurs de tuiles", + "proximityAlerts": "Alertes de Proximité" }, "node": { "title": "Nœud #{}", diff --git a/lib/screens/advanced_settings_screen.dart b/lib/screens/advanced_settings_screen.dart index e1e50a1..ed63e85 100644 --- a/lib/screens/advanced_settings_screen.dart +++ b/lib/screens/advanced_settings_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'settings_screen_sections/max_nodes_section.dart'; +import 'settings_screen_sections/proximity_alerts_section.dart'; import 'settings_screen_sections/tile_provider_section.dart'; import '../services/localization_service.dart'; @@ -21,6 +22,8 @@ class AdvancedSettingsScreen extends StatelessWidget { children: const [ MaxNodesSection(), Divider(), + ProximityAlertsSection(), + Divider(), TileProviderSection(), ], ), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index b59f3f5..fffb668 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -3,7 +3,6 @@ import 'settings_screen_sections/auth_section.dart'; import 'settings_screen_sections/upload_mode_section.dart'; import 'settings_screen_sections/queue_section.dart'; import 'settings_screen_sections/about_section.dart'; -import 'settings_screen_sections/proximity_alerts_section.dart'; import 'settings_screen_sections/language_section.dart'; import '../services/localization_service.dart'; import '../dev_config.dart'; @@ -31,8 +30,6 @@ class SettingsScreen extends StatelessWidget { const Divider(), const QueueSection(), const Divider(), - const ProximityAlertsSection(), - const Divider(), // Navigation to sub-pages _buildNavigationTile( diff --git a/lib/screens/settings_screen_sections/language_section.dart b/lib/screens/settings_screen_sections/language_section.dart index 345ed33..3bd058a 100644 --- a/lib/screens/settings_screen_sections/language_section.dart +++ b/lib/screens/settings_screen_sections/language_section.dart @@ -57,15 +57,11 @@ class _LanguageSectionState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), - child: Text( - locService.t('settings.language'), - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), - ), + Text( + locService.t('settings.language'), + style: Theme.of(context).textTheme.titleMedium, ), + const SizedBox(height: 8), // System Default option RadioListTile( title: Text(locService.t('settings.systemDefault')), diff --git a/lib/screens/settings_screen_sections/operator_profile_list_section.dart b/lib/screens/settings_screen_sections/operator_profile_list_section.dart index 927d8e3..6d95335 100644 --- a/lib/screens/settings_screen_sections/operator_profile_list_section.dart +++ b/lib/screens/settings_screen_sections/operator_profile_list_section.dart @@ -22,7 +22,10 @@ class OperatorProfileListSection extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(locService.t('operatorProfiles.title'), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + Text( + locService.t('operatorProfiles.title'), + style: Theme.of(context).textTheme.titleMedium, + ), TextButton.icon( onPressed: () => Navigator.push( context, diff --git a/lib/screens/settings_screen_sections/profile_list_section.dart b/lib/screens/settings_screen_sections/profile_list_section.dart index 3cd3d24..989d179 100644 --- a/lib/screens/settings_screen_sections/profile_list_section.dart +++ b/lib/screens/settings_screen_sections/profile_list_section.dart @@ -22,7 +22,10 @@ class ProfileListSection extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(locService.t('profiles.nodeProfiles'), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + Text( + locService.t('profiles.nodeProfiles'), + style: Theme.of(context).textTheme.titleMedium, + ), TextButton.icon( onPressed: () => Navigator.push( context, diff --git a/lib/screens/settings_screen_sections/proximity_alerts_section.dart b/lib/screens/settings_screen_sections/proximity_alerts_section.dart index 437147c..0f7919c 100644 --- a/lib/screens/settings_screen_sections/proximity_alerts_section.dart +++ b/lib/screens/settings_screen_sections/proximity_alerts_section.dart @@ -82,14 +82,14 @@ class _ProximityAlertsSectionState extends State { Widget build(BuildContext context) { return Consumer( builder: (context, appState, child) { + final locService = LocalizationService.instance; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Proximity Alerts', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), + locService.t('settings.proximityAlerts'), + style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 8), diff --git a/lib/screens/settings_screen_sections/tile_provider_section.dart b/lib/screens/settings_screen_sections/tile_provider_section.dart index a73310b..895658d 100644 --- a/lib/screens/settings_screen_sections/tile_provider_section.dart +++ b/lib/screens/settings_screen_sections/tile_provider_section.dart @@ -4,7 +4,7 @@ import 'package:provider/provider.dart'; import '../../app_state.dart'; import '../../models/tile_provider.dart'; import '../../services/localization_service.dart'; -import '../tile_provider_management_screen.dart'; +import '../tile_provider_editor_screen.dart'; class TileProviderSection extends StatelessWidget { const TileProviderSection({super.key}); @@ -15,32 +15,166 @@ class TileProviderSection extends StatelessWidget { animation: LocalizationService.instance, builder: (context, child) { final locService = LocalizationService.instance; + final appState = context.watch(); + final providers = appState.tileProviders; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - locService.t('mapTiles.title'), - style: Theme.of(context).textTheme.titleMedium, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + locService.t('mapTiles.title'), + style: Theme.of(context).textTheme.titleMedium, + ), + TextButton.icon( + onPressed: () => _addProvider(context), + icon: const Icon(Icons.add), + label: Text(locService.t('tileProviders.addProvider')), + ), + ], ), const SizedBox(height: 8), - SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const TileProviderManagementScreen(), - ), - ); - }, - icon: const Icon(Icons.settings), - label: Text(locService.t('mapTiles.manageProviders')), - ), - ), + if (providers.isEmpty) + Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Text(locService.t('tileProviders.noProvidersConfigured')), + ), + ) + else + ...providers.map((provider) => _buildProviderTile(context, provider, appState)).toList(), ], ); }, ); } + + Widget _buildProviderTile(BuildContext context, TileProvider provider, AppState appState) { + final locService = LocalizationService.instance; + final isSelected = appState.selectedTileProvider?.id == provider.id; + + return Card( + margin: const EdgeInsets.symmetric(vertical: 4), + child: ListTile( + title: Text( + provider.name, + style: TextStyle( + fontWeight: isSelected ? FontWeight.bold : null, + ), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(locService.t('tileProviders.tileTypesCount', params: [provider.tileTypes.length.toString()])), + if (provider.apiKey?.isNotEmpty == true) + Text( + locService.t('tileProviders.apiKeyConfigured'), + style: const TextStyle( + fontStyle: FontStyle.italic, + fontSize: 12, + ), + ), + if (!provider.isUsable) + Text( + locService.t('tileProviders.needsApiKey'), + style: TextStyle( + color: Theme.of(context).colorScheme.error, + fontSize: 12, + ), + ), + ], + ), + leading: CircleAvatar( + backgroundColor: isSelected + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.surfaceVariant, + child: Icon( + Icons.map, + color: isSelected + ? Theme.of(context).colorScheme.onPrimary + : Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + trailing: appState.tileProviders.length > 1 + ? PopupMenuButton( + onSelected: (action) { + switch (action) { + case 'edit': + _editProvider(context, provider); + break; + case 'delete': + _deleteProvider(context, provider); + break; + } + }, + itemBuilder: (context) => [ + PopupMenuItem( + value: 'edit', + child: Row( + children: [ + const Icon(Icons.edit), + const SizedBox(width: 8), + Text(locService.t('actions.edit')), + ], + ), + ), + PopupMenuItem( + value: 'delete', + child: Row( + children: [ + const Icon(Icons.delete), + const SizedBox(width: 8), + Text(locService.t('tileProviders.deleteProvider')), + ], + ), + ), + ], + ) + : const Icon(Icons.lock, size: 16), // Can't delete last provider + onTap: () => _editProvider(context, provider), + ), + ); + } + + void _addProvider(BuildContext context) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const TileProviderEditorScreen(), + ), + ); + } + + void _editProvider(BuildContext context, TileProvider provider) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TileProviderEditorScreen(provider: provider), + ), + ); + } + + void _deleteProvider(BuildContext context, TileProvider provider) { + final locService = LocalizationService.instance; + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(locService.t('tileProviders.deleteProvider')), + content: Text(locService.t('tileProviders.deleteProviderConfirm', params: [provider.name])), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(locService.t('actions.cancel')), + ), + TextButton( + onPressed: () { + context.read().deleteTileProvider(provider.id); + Navigator.of(context).pop(); + }, + child: Text(locService.t('tileProviders.deleteProvider')), + ), + ], + ), + ); + } } \ No newline at end of file