localizations, dark mode touchups

This commit is contained in:
stopflock
2025-08-31 11:16:20 -05:00
parent 8381388ffa
commit fa6b6ffcda
17 changed files with 598 additions and 191 deletions
+35 -23
View File
@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import '../app_state.dart';
import '../dev_config.dart';
import '../widgets/map_view.dart';
import '../services/localization_service.dart';
import '../widgets/add_node_sheet.dart';
import '../widgets/edit_node_sheet.dart';
@@ -44,13 +45,14 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
}
String _getFollowMeTooltip(FollowMeMode mode) {
final locService = LocalizationService.instance;
switch (mode) {
case FollowMeMode.off:
return 'Enable follow-me (north up)';
return locService.t('followMe.off');
case FollowMeMode.northUp:
return 'Enable follow-me (rotating)';
return locService.t('followMe.northUp');
case FollowMeMode.rotating:
return 'Disable follow-me';
return locService.t('followMe.rotating');
}
}
@@ -172,9 +174,13 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
}
},
),
IconButton(
icon: const Icon(Icons.settings),
onPressed: () => Navigator.pushNamed(context, '/settings'),
AnimatedBuilder(
animation: LocalizationService.instance,
builder: (context, child) => IconButton(
tooltip: LocalizationService.instance.settings,
icon: const Icon(Icons.settings),
onPressed: () => Navigator.pushNamed(context, '/settings'),
),
),
],
),
@@ -216,28 +222,34 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
child: Row(
children: [
Expanded(
child: ElevatedButton.icon(
icon: Icon(Icons.add_location_alt),
label: Text('Tag Node'),
onPressed: _openAddNodeSheet,
style: ElevatedButton.styleFrom(
minimumSize: Size(0, 48),
textStyle: TextStyle(fontSize: 16),
child: AnimatedBuilder(
animation: LocalizationService.instance,
builder: (context, child) => ElevatedButton.icon(
icon: Icon(Icons.add_location_alt),
label: Text(LocalizationService.instance.tagNode),
onPressed: _openAddNodeSheet,
style: ElevatedButton.styleFrom(
minimumSize: Size(0, 48),
textStyle: TextStyle(fontSize: 16),
),
),
),
),
SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
icon: Icon(Icons.download_for_offline),
label: Text('Download'),
onPressed: () => showDialog(
context: context,
builder: (ctx) => DownloadAreaDialog(controller: _mapController.mapController),
),
style: ElevatedButton.styleFrom(
minimumSize: Size(0, 48),
textStyle: TextStyle(fontSize: 16),
child: AnimatedBuilder(
animation: LocalizationService.instance,
builder: (context, child) => ElevatedButton.icon(
icon: Icon(Icons.download_for_offline),
label: Text(LocalizationService.instance.download),
onPressed: () => showDialog(
context: context,
builder: (ctx) => DownloadAreaDialog(controller: _mapController.mapController),
),
style: ElevatedButton.styleFrom(
minimumSize: Size(0, 48),
textStyle: TextStyle(fontSize: 16),
),
),
),
),
+32 -25
View File
@@ -9,37 +9,44 @@ import 'settings_screen_sections/offline_mode_section.dart';
import 'settings_screen_sections/about_section.dart';
import 'settings_screen_sections/max_nodes_section.dart';
import 'settings_screen_sections/tile_provider_section.dart';
import 'settings_screen_sections/language_section.dart';
import '../services/localization_service.dart';
class SettingsScreen extends StatelessWidget {
const SettingsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: ListView(
padding: const EdgeInsets.all(16),
children: const [
UploadModeSection(),
Divider(),
AuthSection(),
Divider(),
QueueSection(),
Divider(),
ProfileListSection(),
Divider(),
OperatorProfileListSection(),
Divider(),
MaxNodesSection(),
Divider(),
TileProviderSection(),
Divider(),
OfflineModeSection(),
Divider(),
OfflineAreasSection(),
Divider(),
AboutSection(),
],
return AnimatedBuilder(
animation: LocalizationService.instance,
builder: (context, child) => Scaffold(
appBar: AppBar(title: Text(LocalizationService.instance.t('settings.title'))),
body: ListView(
padding: const EdgeInsets.all(16),
children: const [
UploadModeSection(),
Divider(),
AuthSection(),
Divider(),
QueueSection(),
Divider(),
ProfileListSection(),
Divider(),
OperatorProfileListSection(),
Divider(),
MaxNodesSection(),
Divider(),
TileProviderSection(),
Divider(),
OfflineModeSection(),
Divider(),
OfflineAreasSection(),
Divider(),
LanguageSection(),
Divider(),
AboutSection(),
],
),
),
);
}
@@ -1,35 +1,42 @@
import 'package:flutter/material.dart';
import '../../services/localization_service.dart';
class AboutSection extends StatelessWidget {
const AboutSection({super.key});
@override
Widget build(BuildContext context) {
return ListTile(
leading: const Icon(Icons.info_outline),
title: const Text('About / Info'),
onTap: () async {
showDialog(
context: context,
builder: (context) => FutureBuilder<String>(
future: DefaultAssetBundle.of(context).loadString('assets/info.txt'),
builder: (context, snapshot) => AlertDialog(
title: const Text('About This App'),
content: SingleChildScrollView(
child: Text(
snapshot.connectionState == ConnectionState.done
? (snapshot.data ?? 'No info available.')
: 'Loading...',
return AnimatedBuilder(
animation: LocalizationService.instance,
builder: (context, child) {
final locService = LocalizationService.instance;
return ListTile(
leading: const Icon(Icons.info_outline),
title: Text(locService.t('settings.aboutInfo')),
onTap: () async {
showDialog(
context: context,
builder: (context) => FutureBuilder<String>(
future: DefaultAssetBundle.of(context).loadString('assets/info.txt'),
builder: (context, snapshot) => AlertDialog(
title: Text(locService.t('settings.aboutThisApp')),
content: SingleChildScrollView(
child: Text(
snapshot.connectionState == ConnectionState.done
? (snapshot.data ?? 'No info available.')
: 'Loading...',
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(locService.ok),
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
),
);
},
);
},
);
@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../services/localization_service.dart';
class LanguageSection extends StatefulWidget {
const LanguageSection({super.key});
@override
State<LanguageSection> createState() => _LanguageSectionState();
}
class _LanguageSectionState extends State<LanguageSection> {
String? _selectedLanguage;
Map<String, String> _languageNames = {};
@override
void initState() {
super.initState();
_loadSelectedLanguage();
_loadLanguageNames();
}
_loadSelectedLanguage() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = prefs.getString('language_code');
});
}
_loadLanguageNames() async {
final locService = LocalizationService.instance;
final Map<String, String> names = {};
for (String langCode in locService.availableLanguages) {
names[langCode] = await locService.getLanguageDisplayName(langCode);
}
setState(() {
_languageNames = names;
});
}
_setLanguage(String? languageCode) async {
await LocalizationService.instance.setLanguage(languageCode);
setState(() {
_selectedLanguage = languageCode;
});
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: LocalizationService.instance,
builder: (context, child) {
final locService = LocalizationService.instance;
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,
),
),
),
// System Default option
RadioListTile<String?>(
title: Text(locService.t('settings.systemDefault')),
value: null,
groupValue: _selectedLanguage,
onChanged: _setLanguage,
),
// Dynamic language options
...locService.availableLanguages.map((langCode) =>
RadioListTile<String>(
title: Text(_languageNames[langCode] ?? langCode.toUpperCase()),
value: langCode,
groupValue: _selectedLanguage,
onChanged: _setLanguage,
),
),
],
);
},
);
}
}
@@ -42,7 +42,7 @@ class UploadModeSection extends StatelessWidget {
builder: (context) {
switch (appState.uploadMode) {
case UploadMode.production:
return const Text('Upload to the live OSM database (visible to all users)', style: TextStyle(fontSize: 12, color: Colors.black87));
return Text('Upload to the live OSM database (visible to all users)', style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7)));
case UploadMode.sandbox:
return Column(
crossAxisAlignment: CrossAxisAlignment.start,