Move settings around part 2

This commit is contained in:
stopflock
2025-09-29 23:56:28 -05:00
parent 82501a3131
commit b3a87fc56a
11 changed files with 178 additions and 39 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ const String kClientName = 'DeFlock';
const String kClientVersion = '0.9.13'; const String kClientVersion = '0.9.13';
// Development/testing features - set to false for production builds // 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 // Marker/node interaction
const int kCameraMinZoomLevel = 10; // Minimum zoom to show nodes (Overpass) const int kCameraMinZoomLevel = 10; // Minimum zoom to show nodes (Overpass)
+2 -1
View File
@@ -42,7 +42,8 @@
"offlineSettings": "Offline Settings", "offlineSettings": "Offline Settings",
"offlineSettingsSubtitle": "Manage offline mode and downloaded areas", "offlineSettingsSubtitle": "Manage offline mode and downloaded areas",
"advancedSettings": "Advanced Settings", "advancedSettings": "Advanced Settings",
"advancedSettingsSubtitle": "Performance and tile provider settings" "advancedSettingsSubtitle": "Performance, alerts, and tile provider settings",
"proximityAlerts": "Proximity Alerts"
}, },
"node": { "node": {
"title": "Node #{}", "title": "Node #{}",
+2 -1
View File
@@ -42,7 +42,8 @@
"offlineSettings": "Configuración Sin Conexión", "offlineSettings": "Configuración Sin Conexión",
"offlineSettingsSubtitle": "Gestionar modo sin conexión y áreas descargadas", "offlineSettingsSubtitle": "Gestionar modo sin conexión y áreas descargadas",
"advancedSettings": "Configuración Avanzada", "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": { "node": {
"title": "Nodo #{}", "title": "Nodo #{}",
+2 -1
View File
@@ -42,7 +42,8 @@
"offlineSettings": "Paramètres Hors Ligne", "offlineSettings": "Paramètres Hors Ligne",
"offlineSettingsSubtitle": "Gérer le mode hors ligne et les zones téléchargées", "offlineSettingsSubtitle": "Gérer le mode hors ligne et les zones téléchargées",
"advancedSettings": "Paramètres Avancés", "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": { "node": {
"title": "Nœud #{}", "title": "Nœud #{}",
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'settings_screen_sections/max_nodes_section.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 'settings_screen_sections/tile_provider_section.dart';
import '../services/localization_service.dart'; import '../services/localization_service.dart';
@@ -21,6 +22,8 @@ class AdvancedSettingsScreen extends StatelessWidget {
children: const [ children: const [
MaxNodesSection(), MaxNodesSection(),
Divider(), Divider(),
ProximityAlertsSection(),
Divider(),
TileProviderSection(), TileProviderSection(),
], ],
), ),
-3
View File
@@ -3,7 +3,6 @@ import 'settings_screen_sections/auth_section.dart';
import 'settings_screen_sections/upload_mode_section.dart'; import 'settings_screen_sections/upload_mode_section.dart';
import 'settings_screen_sections/queue_section.dart'; import 'settings_screen_sections/queue_section.dart';
import 'settings_screen_sections/about_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 'settings_screen_sections/language_section.dart';
import '../services/localization_service.dart'; import '../services/localization_service.dart';
import '../dev_config.dart'; import '../dev_config.dart';
@@ -31,8 +30,6 @@ class SettingsScreen extends StatelessWidget {
const Divider(), const Divider(),
const QueueSection(), const QueueSection(),
const Divider(), const Divider(),
const ProximityAlertsSection(),
const Divider(),
// Navigation to sub-pages // Navigation to sub-pages
_buildNavigationTile( _buildNavigationTile(
@@ -57,15 +57,11 @@ class _LanguageSectionState extends State<LanguageSection> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Text(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), locService.t('settings.language'),
child: Text( style: Theme.of(context).textTheme.titleMedium,
locService.t('settings.language'),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
),
), ),
const SizedBox(height: 8),
// System Default option // System Default option
RadioListTile<String?>( RadioListTile<String?>(
title: Text(locService.t('settings.systemDefault')), title: Text(locService.t('settings.systemDefault')),
@@ -22,7 +22,10 @@ class OperatorProfileListSection extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ 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( TextButton.icon(
onPressed: () => Navigator.push( onPressed: () => Navigator.push(
context, context,
@@ -22,7 +22,10 @@ class ProfileListSection extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ 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( TextButton.icon(
onPressed: () => Navigator.push( onPressed: () => Navigator.push(
context, context,
@@ -82,14 +82,14 @@ class _ProximityAlertsSectionState extends State<ProximityAlertsSection> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<AppState>( return Consumer<AppState>(
builder: (context, appState, child) { builder: (context, appState, child) {
final locService = LocalizationService.instance;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Proximity Alerts', locService.t('settings.proximityAlerts'),
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: Theme.of(context).textTheme.titleMedium,
fontWeight: FontWeight.bold,
),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
import '../../app_state.dart'; import '../../app_state.dart';
import '../../models/tile_provider.dart'; import '../../models/tile_provider.dart';
import '../../services/localization_service.dart'; import '../../services/localization_service.dart';
import '../tile_provider_management_screen.dart'; import '../tile_provider_editor_screen.dart';
class TileProviderSection extends StatelessWidget { class TileProviderSection extends StatelessWidget {
const TileProviderSection({super.key}); const TileProviderSection({super.key});
@@ -15,32 +15,166 @@ class TileProviderSection extends StatelessWidget {
animation: LocalizationService.instance, animation: LocalizationService.instance,
builder: (context, child) { builder: (context, child) {
final locService = LocalizationService.instance; final locService = LocalizationService.instance;
final appState = context.watch<AppState>();
final providers = appState.tileProviders;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Row(
locService.t('mapTiles.title'), mainAxisAlignment: MainAxisAlignment.spaceBetween,
style: Theme.of(context).textTheme.titleMedium, 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), const SizedBox(height: 8),
SizedBox( if (providers.isEmpty)
width: double.infinity, Center(
child: ElevatedButton.icon( child: Padding(
onPressed: () { padding: const EdgeInsets.all(16),
Navigator.of(context).push( child: Text(locService.t('tileProviders.noProvidersConfigured')),
MaterialPageRoute( ),
builder: (context) => const TileProviderManagementScreen(), )
), else
); ...providers.map((provider) => _buildProviderTile(context, provider, appState)).toList(),
},
icon: const Icon(Icons.settings),
label: Text(locService.t('mapTiles.manageProviders')),
),
),
], ],
); );
}, },
); );
} }
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<String>(
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<AppState>().deleteTileProvider(provider.id);
Navigator.of(context).pop();
},
child: Text(locService.t('tileProviders.deleteProvider')),
),
],
),
);
}
} }