fix: Reset fallback sharing state

This commit is contained in:
Janez T
2026-04-07 14:04:45 +02:00
parent e5514b4ef9
commit 44a668df93
64 changed files with 12741 additions and 8677 deletions

View File

@@ -272,7 +272,7 @@ class _AddContactScreenState extends State<AddContactScreen> {
},
decoration: InputDecoration(
labelText: 'Contact advert',
hintText: 'Paste a share link or hex advert',
hintText: AppLocalizations.of(context)!.pasteShareLinkOrHexAdvert,
alignLabelWithHint: true,
border: const OutlineInputBorder(),
errorText: _validationError,

View File

@@ -534,6 +534,7 @@ class _ContactsTabState extends State<ContactsTab> {
final result = await context.read<AppProvider>().setChannelLocationSharingEnabled(
channelIdx,
enabled,
l10n: AppLocalizations.of(context),
);
if (!context.mounted) return;
ToastLogger.success(context, result.message);
@@ -1442,7 +1443,7 @@ class _ContactsTabState extends State<ContactsTab> {
height: 1.1,
),
decoration: InputDecoration(
hintText: 'Search this section',
hintText: AppLocalizations.of(context)!.searchThisSection,
hintStyle: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant.withValues(
alpha: 0.85,
@@ -1601,7 +1602,7 @@ class _ContactsTabState extends State<ContactsTab> {
final availableModes = _availableSortModes(section);
return PopupMenuButton<ContactSortMode>(
tooltip: 'Sort',
tooltip: AppLocalizations.of(context)!.sort,
initialValue: selectedMode,
onSelected: (sortMode) {
setState(() {
@@ -1862,7 +1863,7 @@ class _InferredContactGroupCard extends StatelessWidget {
if (onDelete != null) ...[
const SizedBox(width: 2),
IconButton(
tooltip: 'Delete group',
tooltip: AppLocalizations.of(context)!.deleteGroup,
onPressed: onDelete,
visualDensity: VisualDensity.compact,
constraints: const BoxConstraints.tightFor(

View File

@@ -854,7 +854,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
if (!mounted) return;
setState(() => _buzzerLoading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to set buzzer mode: $e')),
SnackBar(content: Text(AppLocalizations.of(context)!.failedToSetBuzzerMode(e.toString()))),
);
}
}
@@ -874,7 +874,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
setState(() => _gpsLoading = false);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Failed to set GPS mode: $e')));
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.failedToSetGpsMode(e.toString()))));
}
}
@@ -980,8 +980,8 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Device time synced to this phone.'),
SnackBar(
content: Text(AppLocalizations.of(context)!.deviceTimeSynced),
backgroundColor: Colors.green,
),
);
@@ -989,7 +989,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to sync device time: $e'),
content: Text(AppLocalizations.of(context)!.failedToSyncDeviceTime(e.toString())),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
@@ -1055,7 +1055,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
if (!mounted) return;
messenger.showSnackBar(
SnackBar(
content: Text('Failed to wipe device data: $e'),
content: Text(AppLocalizations.of(context)!.failedToWipeDeviceData(e.toString())),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
@@ -1146,7 +1146,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
if (!mounted) return;
messenger.showSnackBar(
SnackBar(
content: Text('Failed to clear contacts: $e'),
content: Text(AppLocalizations.of(context)!.failedToClearContacts(e.toString())),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
@@ -1252,7 +1252,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
if (!mounted) return;
messenger.showSnackBar(
SnackBar(
content: Text('Failed to clear channels: $e'),
content: Text(AppLocalizations.of(context)!.failedToClearChannels(e.toString())),
backgroundColor: Theme.of(context).colorScheme.error,
),
);
@@ -1273,7 +1273,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
final locationSet = _advertLocationPolicy != 0;
return Scaffold(
appBar: AppBar(title: const Text('Device Settings')),
appBar: AppBar(title: Text(AppLocalizations.of(context)!.deviceSettings)),
body: ColoredBox(
color: colorScheme.surface,
child: SafeArea(
@@ -1303,7 +1303,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
),
SizedBox(height: 12),
_ConfigSectionCard(
title: 'Device info',
title: AppLocalizations.of(context)!.deviceInfo,
subtitle:
'Capabilities, storage, and maintenance tools.',
icon: Icons.info_outline_rounded,
@@ -1323,7 +1323,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
SizedBox(
width: cardWidth,
child: _StorageStat(
label: 'BLE PIN',
label: AppLocalizations.of(context)!.blePin,
value: _formatBlePin(deviceInfo.blePin),
compact: true,
),
@@ -1356,7 +1356,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
SizedBox(
width: cardWidth,
child: _StorageStat(
label: 'Path hash',
label: AppLocalizations.of(context)!.pathHash,
value: _pathHashModeLabel(
deviceInfo.pathHashMode!,
),
@@ -1416,7 +1416,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
icon: _buzzerEnabled!
? Icons.volume_up_rounded
: Icons.volume_off_rounded,
title: 'Buzzer alerts',
title: AppLocalizations.of(context)!.buzzerAlerts,
description: 'Onboard buzzer for radio alerts',
accentColor: _buzzerEnabled!
? colorScheme.primary
@@ -1440,7 +1440,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
icon: _multiAcksEnabled
? Icons.mark_email_read_outlined
: Icons.mark_email_unread_outlined,
title: 'Multi-ACK mode',
title: AppLocalizations.of(context)!.multiAckMode,
description: 'Request extra acknowledgements',
accentColor: _multiAcksEnabled
? colorScheme.primary
@@ -1463,7 +1463,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
SizedBox(height: 12),
_ConfigSectionCard(
title: AppLocalizations.of(context)!.publicInfo,
subtitle: 'Name and telemetry shared with other devices.',
subtitle: AppLocalizations.of(context)!.nameAndTelemetryShared,
icon: Icons.public_rounded,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -1484,15 +1484,15 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
),
const SizedBox(height: 10),
_ConfigDropdownField(
label: 'Base telemetry',
label: AppLocalizations.of(context)!.baseTelemetry,
value: _baseTelemetryMode,
items: const [
DropdownMenuItem(value: 0, child: Text('Deny')),
items: [
DropdownMenuItem(value: 0, child: Text(AppLocalizations.of(context)!.deny)),
DropdownMenuItem(
value: 1,
child: Text('Use contact flags'),
child: Text(AppLocalizations.of(context)!.useContactFlags),
),
DropdownMenuItem(value: 2, child: Text('Allow all')),
DropdownMenuItem(value: 2, child: Text(AppLocalizations.of(context)!.allowAll)),
],
onChanged: (value) {
if (value == null) return;
@@ -1504,15 +1504,15 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
),
const SizedBox(height: 10),
_ConfigDropdownField(
label: 'Location telemetry',
label: AppLocalizations.of(context)!.locationTelemetry,
value: _locationTelemetryMode,
items: const [
DropdownMenuItem(value: 0, child: Text('Deny')),
items: [
DropdownMenuItem(value: 0, child: Text(AppLocalizations.of(context)!.deny)),
DropdownMenuItem(
value: 1,
child: Text('Use contact flags'),
child: Text(AppLocalizations.of(context)!.useContactFlags),
),
DropdownMenuItem(value: 2, child: Text('Allow all')),
DropdownMenuItem(value: 2, child: Text(AppLocalizations.of(context)!.allowAll)),
],
onChanged: (value) {
if (value == null) return;
@@ -1524,15 +1524,15 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
),
const SizedBox(height: 10),
_ConfigDropdownField(
label: 'Environmental telemetry',
label: AppLocalizations.of(context)!.environmentalTelemetry,
value: _environmentTelemetryMode,
items: const [
DropdownMenuItem(value: 0, child: Text('Deny')),
items: [
DropdownMenuItem(value: 0, child: Text(AppLocalizations.of(context)!.deny)),
DropdownMenuItem(
value: 1,
child: Text('Use contact flags'),
child: Text(AppLocalizations.of(context)!.useContactFlags),
),
DropdownMenuItem(value: 2, child: Text('Allow all')),
DropdownMenuItem(value: 2, child: Text(AppLocalizations.of(context)!.allowAll)),
],
onChanged: (value) {
if (value == null) return;
@@ -1566,24 +1566,24 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
),
SizedBox(height: 12),
_ConfigSectionCard(
title: 'GPS',
subtitle: 'Location sharing, hardware, and update interval.',
title: AppLocalizations.of(context)!.gpsSection,
subtitle: AppLocalizations.of(context)!.locationSharingHardwareAndUpdateInterval,
icon: Icons.gps_fixed,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_ConfigDropdownField(
label: 'GPS advert policy',
label: AppLocalizations.of(context)!.gpsAdvertPolicy,
value: _advertLocationPolicy,
items: const [
DropdownMenuItem(value: 0, child: Text('Hidden')),
items: [
DropdownMenuItem(value: 0, child: Text(AppLocalizations.of(context)!.hidden)),
DropdownMenuItem(
value: 1,
child: Text('Share live GPS'),
child: Text(AppLocalizations.of(context)!.shareLiveGps),
),
DropdownMenuItem(
value: 2,
child: Text('Use saved coordinates'),
child: Text(AppLocalizations.of(context)!.useSavedCoordinates),
),
],
onChanged: (value) {
@@ -1641,20 +1641,20 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
fillColor: colorScheme.surfaceContainerLowest,
),
isExpanded: true,
items: const [
DropdownMenuItem(value: null, child: Text('Not set')),
DropdownMenuItem(value: 0, child: Text('Off (0 s)')),
DropdownMenuItem(value: 5, child: Text('5 seconds')),
DropdownMenuItem(value: 10, child: Text('10 seconds')),
DropdownMenuItem(value: 15, child: Text('15 seconds')),
DropdownMenuItem(value: 30, child: Text('30 seconds')),
DropdownMenuItem(value: 60, child: Text('1 minute')),
DropdownMenuItem(value: 120, child: Text('2 minutes')),
DropdownMenuItem(value: 300, child: Text('5 minutes')),
DropdownMenuItem(value: 600, child: Text('10 minutes')),
DropdownMenuItem(value: 900, child: Text('15 minutes')),
DropdownMenuItem(value: 1800, child: Text('30 minutes')),
DropdownMenuItem(value: 3600, child: Text('1 hour')),
items: [
DropdownMenuItem(value: null, child: Text(AppLocalizations.of(context)!.notSet)),
DropdownMenuItem(value: 0, child: Text(AppLocalizations.of(context)!.offZeroSeconds)),
DropdownMenuItem(value: 5, child: Text(AppLocalizations.of(context)!.fiveSeconds)),
DropdownMenuItem(value: 10, child: Text(AppLocalizations.of(context)!.tenSeconds)),
DropdownMenuItem(value: 15, child: Text(AppLocalizations.of(context)!.fifteenSeconds)),
DropdownMenuItem(value: 30, child: Text(AppLocalizations.of(context)!.thirtySeconds)),
DropdownMenuItem(value: 60, child: Text(AppLocalizations.of(context)!.oneMinute)),
DropdownMenuItem(value: 120, child: Text(AppLocalizations.of(context)!.twoMinutes)),
DropdownMenuItem(value: 300, child: Text(AppLocalizations.of(context)!.fiveMinutes)),
DropdownMenuItem(value: 600, child: Text(AppLocalizations.of(context)!.tenMinutes)),
DropdownMenuItem(value: 900, child: Text(AppLocalizations.of(context)!.fifteenMinutes)),
DropdownMenuItem(value: 1800, child: Text(AppLocalizations.of(context)!.thirtyMinutes)),
DropdownMenuItem(value: 3600, child: Text(AppLocalizations.of(context)!.oneHour)),
],
onChanged: (value) {
setState(() {
@@ -1687,7 +1687,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
Expanded(
child: _CompactCoordinateField(
controller: _latController,
label: 'Latitude',
label: AppLocalizations.of(context)!.latitude,
onChanged: (_) => _markPublicInfoDirty(),
),
),
@@ -1695,7 +1695,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
Expanded(
child: _CompactCoordinateField(
controller: _lonController,
label: 'Longitude',
label: AppLocalizations.of(context)!.longitude,
onChanged: (_) => _markPublicInfoDirty(),
),
),
@@ -1994,18 +1994,18 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
helperText:
'Hash size used in adverts and flood paths',
),
items: const [
items: [
DropdownMenuItem(
value: 0,
child: Text('1 byte (mode 0)'),
child: Text(AppLocalizations.of(context)!.oneByteMode0),
),
DropdownMenuItem(
value: 1,
child: Text('2 bytes (mode 1)'),
child: Text(AppLocalizations.of(context)!.twoBytesMode1),
),
DropdownMenuItem(
value: 2,
child: Text('3 bytes (mode 2)'),
child: Text(AppLocalizations.of(context)!.threeBytesMode2),
),
],
onChanged: (int? newValue) {
@@ -2070,7 +2070,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
SizedBox(height: 12),
_ConfigSectionCard(
title: AppLocalizations.of(context)!.autoDiscovery,
subtitle: 'How the radio auto-adds discovered nodes.',
subtitle: AppLocalizations.of(context)!.howTheRadioAutoAddsDiscoveredNodes,
icon: Icons.person_search_rounded,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -2754,14 +2754,14 @@ class _SaveActionButton extends StatelessWidget {
? Row(
key: const ValueKey('saving'),
mainAxisSize: MainAxisSize.min,
children: const [
SizedBox(
children: [
const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2.2),
),
SizedBox(width: 10),
Text('Saving...'),
const SizedBox(width: 10),
Text(AppLocalizations.of(context)!.saving),
],
)
: Row(
@@ -2881,14 +2881,14 @@ class _GpsDiagnosticsCard extends StatelessWidget {
],
),
const SizedBox(height: 6),
_GpsDiagnosticsRow(label: 'Fix', value: fixValue),
_GpsDiagnosticsRow(label: AppLocalizations.of(context)!.fix, value: fixValue),
const SizedBox(height: 4),
_GpsDiagnosticsRow(label: 'Satellites', value: satellitesValue),
_GpsDiagnosticsRow(label: AppLocalizations.of(context)!.satellites, value: satellitesValue),
const SizedBox(height: 4),
_GpsDiagnosticsRow(label: 'Last fix', value: lastFixValue),
_GpsDiagnosticsRow(label: AppLocalizations.of(context)!.lastFix, value: lastFixValue),
const SizedBox(height: 4),
_GpsDiagnosticsRow(
label: 'Location',
label: AppLocalizations.of(context)!.location,
value: locationValue,
maxLines: 2,
),

View File

@@ -69,7 +69,7 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(false),
child: const Text('Cancel'),
child: Text(AppLocalizations.of(context)!.cancel),
),
FilledButton(
onPressed: () => Navigator.of(dialogContext).pop(true),
@@ -256,7 +256,7 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
if (addError != null) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to add contact: $addError')),
SnackBar(content: Text(AppLocalizations.of(context)!.failedToAddContact(addError.toString()))),
);
}
return false;
@@ -775,7 +775,7 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
IconButton(
visualDensity: VisualDensity.compact,
icon: const Icon(Icons.person_add_alt_1),
tooltip: 'Resolve contact',
tooltip: AppLocalizations.of(context)!.resolveContact,
onPressed: isConnected
? () => _resolveAdvert(advert)
: null,
@@ -848,7 +848,7 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
height: 1.1,
),
decoration: InputDecoration(
hintText: 'Search discovered nodes',
hintText: AppLocalizations.of(context)!.searchDiscoveredNodes,
hintStyle: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant.withValues(
alpha: 0.85,

View File

@@ -155,17 +155,17 @@ class _LiveTrafficScreenState extends State<LiveTrafficScreen> {
if (widget.openPacketLogs != null)
IconButton(
onPressed: widget.openPacketLogs,
tooltip: 'Open packet logs',
tooltip: AppLocalizations.of(context)!.openPacketLogs,
icon: const Icon(Icons.list_alt_rounded),
),
IconButton(
onPressed: _openStatsDashboard,
tooltip: 'View public stats',
tooltip: AppLocalizations.of(context)!.viewPublicStats,
icon: const Icon(Icons.open_in_new),
),
IconButton(
onPressed: () => _showPacketTypeHelpSheet(context),
tooltip: 'Packet type help',
tooltip: AppLocalizations.of(context)!.packetTypeHelp,
icon: const Icon(Icons.help_outline),
),
IconButton(
@@ -174,7 +174,7 @@ class _LiveTrafficScreenState extends State<LiveTrafficScreen> {
_clearedAt = widget.now();
});
},
tooltip: 'Clear live view',
tooltip: AppLocalizations.of(context)!.clearLiveView,
icon: const Icon(Icons.cleaning_services_outlined),
),
],
@@ -986,7 +986,7 @@ class _LiveTrafficCard extends StatelessWidget {
style: TextStyle(color: scheme.onSurfaceVariant),
),
const SizedBox(height: 16),
Text('Hex', style: Theme.of(context).textTheme.titleSmall),
Text(AppLocalizations.of(context)!.hex, style: Theme.of(context).textTheme.titleSmall),
const SizedBox(height: 8),
Container(
width: double.infinity,
@@ -1006,7 +1006,7 @@ class _LiveTrafficCard extends StatelessWidget {
),
),
const SizedBox(height: 16),
Text('ASCII', style: Theme.of(context).textTheme.titleSmall),
Text(AppLocalizations.of(context)!.ascii, style: Theme.of(context).textTheme.titleSmall),
const SizedBox(height: 8),
Container(
width: double.infinity,

View File

@@ -1881,7 +1881,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to send SAR marker: $e'),
content: Text(AppLocalizations.of(context)!.failedToSendSarMarker(e.toString())),
backgroundColor: Colors.red,
),
);

View File

@@ -1691,7 +1691,7 @@ class _MessagesTabState extends State<MessagesTab> {
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(false),
child: const Text('Cancel'),
child: Text(AppLocalizations.of(dialogContext)!.cancel),
),
FilledButton(
onPressed: () => Navigator.of(dialogContext).pop(true),
@@ -1776,6 +1776,7 @@ class _MessagesTabState extends State<MessagesTab> {
final result = await context.read<AppProvider>().setChannelLocationSharingEnabled(
channelIdx,
enabled,
l10n: AppLocalizations.of(context),
);
if (!mounted) return;
ToastLogger.success(context, result.message);
@@ -2059,7 +2060,7 @@ class _MessagesTabState extends State<MessagesTab> {
controller: searchController,
autofocus: true,
decoration: InputDecoration(
hintText: 'Search in current filter',
hintText: AppLocalizations.of(context)!.searchInCurrentFilter,
prefixIcon: const Icon(Icons.search),
suffixIcon: searchController.text.isEmpty
? null

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../l10n/app_localizations.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:provider/provider.dart';
@@ -42,7 +43,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Offline Maps'),
title: Text(AppLocalizations.of(context)!.offlineMaps),
actions: [
Consumer<OfflineTilesProvider>(
builder: (context, provider, _) {
@@ -67,7 +68,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
builder: (context, provider, _) {
return PopupMenuButton<MapLayer>(
icon: const Icon(Icons.layers),
tooltip: 'Map Style',
tooltip: AppLocalizations.of(context)!.mapStyle,
onSelected: (layer) => provider.setSelectedLayer(layer),
itemBuilder: (_) => [
for (final layer in MapLayer.allLayers)
@@ -195,7 +196,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
border: OutlineInputBorder(),
),
isExpanded: true,
hint: const Text('Load a saved region'),
hint: Text(AppLocalizations.of(context)!.loadASavedRegion),
items: provider.localStyles
.where((s) => s.region != null)
.map((style) => DropdownMenuItem(
@@ -224,7 +225,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
children: [
Expanded(
child: _ZoomSelector(
label: 'Min Zoom',
label: AppLocalizations.of(context)!.minZoom,
value: provider.minZoom,
onChanged: provider.setMinZoom,
),
@@ -232,7 +233,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
const SizedBox(width: 16),
Expanded(
child: _ZoomSelector(
label: 'Max Zoom',
label: AppLocalizations.of(context)!.maxZoom,
value: provider.maxZoom,
onChanged: provider.setMaxZoom,
),
@@ -305,7 +306,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
? () => provider.startDownload()
: null,
icon: const Icon(Icons.download),
label: const Text('Download'),
label: Text(AppLocalizations.of(context)!.download),
),
),
if (provider.tileOverlays.isNotEmpty) ...[
@@ -313,7 +314,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
IconButton(
onPressed: provider.clearOverlays,
icon: const Icon(Icons.layers_clear),
tooltip: 'Clear overlay',
tooltip: AppLocalizations.of(context)!.clearOverlay,
),
],
if (provider.cacheSizeBytes > 0) ...[
@@ -321,7 +322,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
IconButton(
onPressed: () => _confirmClearCache(provider),
icon: const Icon(Icons.delete_forever),
tooltip: 'Clear cache',
tooltip: AppLocalizations.of(context)!.clearCache,
),
],
],
@@ -330,7 +331,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
child: OutlinedButton.icon(
onPressed: () => provider.cancelDownload(),
icon: const Icon(Icons.cancel),
label: const Text('Cancel'),
label: Text(AppLocalizations.of(context)!.cancel),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.red,
),
@@ -390,7 +391,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
// Server toggle
SwitchListTile(
title: const Text('Share my tiles'),
title: Text(AppLocalizations.of(context)!.shareMyTiles),
subtitle: Text(
provider.isServerRunning
? 'Other devices can fetch tiles from this device'
@@ -449,7 +450,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
IconButton(
icon: const Icon(Icons.delete_outline,
size: 20),
tooltip: 'Delete',
tooltip: AppLocalizations.of(context)!.delete,
onPressed: () => _confirmDeleteStyle(
context, provider, style),
),
@@ -481,13 +482,13 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
else
IconButton(
icon: const Icon(Icons.refresh, size: 20),
tooltip: 'Refresh',
tooltip: AppLocalizations.of(context)!.refresh,
onPressed: () =>
provider.refreshPeerCatalogs(),
),
IconButton(
icon: const Icon(Icons.add, size: 20),
tooltip: 'Add peer manually',
tooltip: AppLocalizations.of(context)!.addPeerManually,
onPressed: () =>
_showAddPeerDialog(context, provider),
),
@@ -524,7 +525,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
.map((peer) => ListTile(
leading: const Icon(Icons.devices),
title: Text(peer.ipAddress),
subtitle: const Text('Fetching catalog...'),
subtitle: Text(AppLocalizations.of(context)!.fetchingCatalog),
trailing: IconButton(
icon: const Icon(
Icons.remove_circle_outline,
@@ -556,7 +557,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
if (provider.isSyncing)
TextButton(
onPressed: () => provider.cancelSync(),
child: const Text('Cancel'),
child: Text(AppLocalizations.of(context)!.cancel),
),
],
),
@@ -691,7 +692,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Add Peer'),
title: Text(AppLocalizations.of(context)!.addPeer),
content: TextField(
controller: controller,
decoration: const InputDecoration(
@@ -710,7 +711,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
child: Text(AppLocalizations.of(context)!.cancel),
),
TextButton(
onPressed: () {
@@ -720,7 +721,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
Navigator.pop(context);
}
},
child: const Text('Add'),
child: Text(AppLocalizations.of(context)!.add),
),
],
),
@@ -735,7 +736,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text('Delete ${style.displayName}?'),
title: Text(AppLocalizations.of(context)!.deleteStyleConfirm(style.displayName)),
content: Text(
'${_formatNumber(style.tileCount)} tiles, '
'${_formatBytes(style.sizeBytes)} will be deleted.',
@@ -743,7 +744,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text('Cancel'),
child: Text(AppLocalizations.of(context)!.cancel),
),
TextButton(
onPressed: () {
@@ -751,7 +752,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
Navigator.pop(dialogContext);
},
child:
const Text('Delete', style: TextStyle(color: Colors.red)),
Text(AppLocalizations.of(context)!.delete, style: const TextStyle(color: Colors.red)),
),
],
),
@@ -762,13 +763,13 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Clear offline cache?'),
title: Text(AppLocalizations.of(context)!.clearOfflineCache),
content: Text(
'This will delete ${_formatBytes(provider.cacheSizeBytes)} of cached tiles.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
child: Text(AppLocalizations.of(context)!.cancel),
),
TextButton(
onPressed: () {
@@ -776,7 +777,7 @@ class _OfflineMapScreenState extends State<OfflineMapScreen> {
Navigator.pop(context);
},
child:
const Text('Delete', style: TextStyle(color: Colors.red)),
Text(AppLocalizations.of(context)!.delete, style: const TextStyle(color: Colors.red)),
),
],
),

View File

@@ -96,7 +96,7 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Export failed: $e')));
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.exportFailed(e.toString()))));
}
}
}
@@ -147,7 +147,7 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Export failed: $e')));
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.exportFailed(e.toString()))));
}
}
}
@@ -222,7 +222,7 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
? Icons.arrow_downward
: Icons.arrow_upward,
),
tooltip: 'Filter by direction',
tooltip: AppLocalizations.of(context)!.filterByDirection,
onSelected: (direction) {
setState(() {
_filterDirection = direction;
@@ -314,7 +314,7 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
// Export menu
PopupMenuButton(
icon: const Icon(Icons.share),
tooltip: 'Export logs',
tooltip: AppLocalizations.of(context)!.exportLogs,
itemBuilder: (context) => [
PopupMenuItem(
value: 'csv',
@@ -348,7 +348,7 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
// Clear logs
IconButton(
icon: const Icon(Icons.delete_outline),
tooltip: 'Clear logs',
tooltip: AppLocalizations.of(context)!.clearLogs,
onPressed: () => _clearLogs(context),
),
],
@@ -360,7 +360,7 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: InputDecoration(
hintText: 'Search logs...',
hintText: AppLocalizations.of(context)!.searchLogs,
prefixIcon: const Icon(Icons.search),
suffixIcon: _searchQuery.isNotEmpty
? IconButton(
@@ -615,7 +615,7 @@ class _PacketLogCard extends StatelessWidget {
const Spacer(),
IconButton(
onPressed: onCopy,
tooltip: 'Copy full hex',
tooltip: AppLocalizations.of(context)!.copyFullHex,
icon: const Icon(Icons.copy_all_rounded, size: 18),
visualDensity: VisualDensity.compact,
),

View File

@@ -25,7 +25,7 @@ class ProfilesScreen extends StatelessWidget {
.importProfileFromFile();
},
icon: const Icon(Icons.file_open),
tooltip: 'Import profile',
tooltip: AppLocalizations.of(context)!.importProfile,
),
],
),
@@ -143,9 +143,9 @@ class ProfilesScreen extends StatelessWidget {
child: Text(AppLocalizations.of(context)!.rename),
),
if (!profile.isDefault)
const PopupMenuItem(
PopupMenuItem(
value: 'delete',
child: Text('Delete'),
child: Text(AppLocalizations.of(context)!.delete),
),
],
),
@@ -197,7 +197,7 @@ class ProfilesScreen extends StatelessWidget {
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
child: Text(AppLocalizations.of(context)!.cancel),
),
FilledButton(
onPressed: () => Navigator.of(context).pop(controller.text.trim()),
@@ -231,11 +231,11 @@ class ProfilesScreen extends StatelessWidget {
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
child: Text(AppLocalizations.of(context)!.cancel),
),
FilledButton(
onPressed: () => Navigator.of(context).pop(controller.text.trim()),
child: const Text('Save'),
child: Text(AppLocalizations.of(context)!.save),
),
],
),

View File

@@ -269,12 +269,12 @@ class _RepeatersMapScreenState extends State<RepeatersMapScreen> {
await connectionProvider.getContact(_hexToBytes(repeater.publicKey));
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${repeater.name} added to contacts')),
SnackBar(content: Text(AppLocalizations.of(context)!.addedToContacts(repeater.name))),
);
} catch (error) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to add ${repeater.name}: $error')),
SnackBar(content: Text(AppLocalizations.of(context)!.failedToAddName(repeater.name, error.toString()))),
);
} finally {
if (mounted) {
@@ -374,7 +374,7 @@ class _RepeatersMapScreenState extends State<RepeatersMapScreen> {
? null
: () => _loadRepeaters(forceRefresh: true),
icon: const Icon(Icons.refresh),
tooltip: 'Refresh',
tooltip: AppLocalizations.of(context)!.refresh,
),
],
),
@@ -491,7 +491,7 @@ class _RepeatersMapScreenState extends State<RepeatersMapScreen> {
),
floatingActionButton: FloatingActionButton(
onPressed: _jumpToMyLocation,
tooltip: 'My location',
tooltip: AppLocalizations.of(context)!.myLocation,
child: const Icon(Icons.my_location),
),
);

View File

@@ -108,7 +108,7 @@ class _SensorsTabState extends State<SensorsTab> {
if (!sheetContext.mounted) return;
Navigator.of(sheetContext).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${contact.displayName} added to Sensors')),
SnackBar(content: Text(AppLocalizations.of(context)!.addedToSensors(contact.displayName))),
);
},
),
@@ -183,14 +183,14 @@ class _SensorsTabState extends State<SensorsTab> {
actions: [
if (sensorsProvider.labelOverrideFor(publicKeyHex, option.key) !=
null)
TextButton(onPressed: controller.clear, child: const Text('Reset')),
TextButton(onPressed: controller.clear, child: Text(AppLocalizations.of(dialogContext)!.reset)),
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(false),
child: const Text('Cancel'),
child: Text(AppLocalizations.of(dialogContext)!.cancel),
),
FilledButton(
onPressed: () => Navigator.of(dialogContext).pop(true),
child: const Text('Save'),
child: Text(AppLocalizations.of(dialogContext)!.save),
),
],
),
@@ -543,10 +543,10 @@ class _AddSensorSheetState extends State<AddSensorSheet> {
child: TextField(
controller: _searchController,
onChanged: (_) => setState(() {}),
decoration: const InputDecoration(
prefixIcon: Icon(Icons.search),
hintText: 'Search sensors',
border: OutlineInputBorder(),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search),
hintText: AppLocalizations.of(context)!.searchSensors,
border: const OutlineInputBorder(),
),
),
),
@@ -655,7 +655,7 @@ class SensorCustomizeView extends StatelessWidget {
return Scaffold(
appBar: AppBar(
title: Text('Customize ${contact?.displayName ?? 'Sensor'}'),
title: Text(AppLocalizations.of(context)!.customizeSensor(contact?.displayName ?? AppLocalizations.of(context)!.sensor)),
),
body: ListView(
padding: EdgeInsets.fromLTRB(16, 16, 16, 24),
@@ -1094,12 +1094,12 @@ class SensorMetricSelectorItem extends StatelessWidget {
),
const Spacer(),
IconButton(
tooltip: 'Move up',
tooltip: AppLocalizations.of(context)!.moveUp,
onPressed: canMoveUp ? onMoveUp : null,
icon: const Icon(Icons.arrow_upward),
),
IconButton(
tooltip: 'Move down',
tooltip: AppLocalizations.of(context)!.moveDown,
onPressed: canMoveDown ? onMoveDown : null,
icon: const Icon(Icons.arrow_downward),
),
@@ -1259,7 +1259,7 @@ class _SelfOnlySensorsCta extends StatelessWidget {
child: FilledButton.icon(
onPressed: onAddSensor,
icon: const Icon(Icons.add, size: 18),
label: const Text('Choose'),
label: Text(AppLocalizations.of(context)!.choose),
),
),
],

View File

@@ -453,7 +453,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
if (parsed == null) return;
Navigator.pop(context, parsed.clamp(10.0, 1000.0));
},
child: const Text('Save'),
child: Text(AppLocalizations.of(context)!.save),
),
],
),
@@ -494,7 +494,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
if (parsed == null) return;
Navigator.pop(context, parsed.clamp(60, 60));
},
child: const Text('Save'),
child: Text(AppLocalizations.of(context)!.save),
),
],
),
@@ -559,7 +559,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
children: [
ListTile(
leading: const Icon(Icons.block),
title: const Text('Disable fast GPS publishing'),
title: Text(AppLocalizations.of(context)!.disableFastGpsPublishing),
trailing: _fastLocationChannelIdx == null
? const Icon(Icons.check)
: null,
@@ -663,7 +663,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to load preview image: $e'),
content: Text(AppLocalizations.of(context)!.failedToLoadPreviewImage(e.toString())),
backgroundColor: Colors.red,
),
);
@@ -816,7 +816,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error checking for updates: $e'),
content: Text(AppLocalizations.of(context)!.errorCheckingForUpdates(e.toString())),
backgroundColor: Colors.red,
),
);
@@ -1009,7 +1009,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
debugPrint('Error handling location permission: $e');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e'), backgroundColor: Colors.red),
SnackBar(content: Text(AppLocalizations.of(context)!.errorGeneric(e.toString())), backgroundColor: Colors.red),
);
}
}
@@ -1233,8 +1233,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
// ── Appearance ──
_buildSection(
icon: Icons.palette_rounded,
title: 'Appearance',
subtitle: 'Theme, language, and display preferences',
title: AppLocalizations.of(context)!.appearance,
subtitle: AppLocalizations.of(context)!.themeLanguageAndDisplayPreferences,
children: [
ListTile(
dense: true,
@@ -1304,8 +1304,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
// ── Notifications ──
_buildSection(
icon: Icons.notifications_outlined,
title: 'Notifications',
subtitle: 'Control which alerts you receive',
title: AppLocalizations.of(context)!.notifications,
subtitle: AppLocalizations.of(context)!.controlWhichAlertsYouReceive,
children: [
SwitchListTile(
dense: true,
@@ -1357,7 +1357,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
dense: true,
secondary: const Icon(Icons.visibility_off_outlined, size: 20),
title: Text(l10n.muteWhileAppIsOpen),
subtitle: const Text('Suppress notifications while in foreground'),
subtitle: Text(AppLocalizations.of(context)!.suppressNotificationsWhileInForeground),
value: _muteForegroundNotifications,
onChanged: (value) async {
setState(() => _muteForegroundNotifications = value);
@@ -1373,8 +1373,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
Consumer<AppProvider>(
builder: (context, appProvider, child) => _buildSection(
icon: Icons.dashboard_customize_rounded,
title: 'Tabs & Navigation',
subtitle: 'Choose which tabs and contact sections to show',
title: AppLocalizations.of(context)!.tabsAndNavigation,
subtitle: AppLocalizations.of(context)!.chooseWhichTabsAndContactSectionsToShow,
children: [
SwitchListTile(
dense: true,
@@ -1485,8 +1485,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
// ── Messaging ──
_buildSection(
icon: Icons.chat_rounded,
title: 'Messaging',
subtitle: 'Routing, retries, and destination lock',
title: AppLocalizations.of(context)!.messaging,
subtitle: AppLocalizations.of(context)!.routingRetriesAndDestinationLock,
children: [
ListTile(
dense: true,
@@ -1509,7 +1509,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
dense: true,
secondary: const Icon(Icons.route, size: 20),
title: Text(l10n.nearestRepeaterFallback),
subtitle: const Text('Resend through nearest repeater on failure'),
subtitle: Text(AppLocalizations.of(context)!.resendThroughNearestRepeaterOnFailure),
value: appProvider.nearestRelayFallbackEnabled,
onChanged: (value) async {
await appProvider.toggleNearestRelayFallbackEnabled(value);
@@ -1521,7 +1521,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
dense: true,
secondary: const Icon(Icons.cleaning_services, size: 20),
title: Text(l10n.clearPathOnMaxRetry),
subtitle: const Text('Clear route only after all retries fail'),
subtitle: Text(AppLocalizations.of(context)!.clearRouteOnlyAfterAllRetriesFail),
value: appProvider.clearPathOnMaxRetry,
onChanged: (value) async {
await appProvider.toggleClearPathOnMaxRetry(value);
@@ -1532,7 +1532,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
SwitchListTile(
dense: true,
secondary: const Icon(Icons.lock_outline, size: 20),
title: const Text('Lock destination'),
title: Text(AppLocalizations.of(context)!.lockDestination),
subtitle: const Text(
'Fix Messages tab to one channel or room',
),
@@ -1617,8 +1617,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
// ── Map & Tracing ──
_buildSection(
icon: Icons.map_rounded,
title: 'Map',
subtitle: 'Display, markers, and trace database',
title: AppLocalizations.of(context)!.map,
subtitle: AppLocalizations.of(context)!.displayMarkersAndTraceDatabase,
children: [
SwitchListTile(
dense: true,
@@ -1701,8 +1701,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
// ── GPS & Location ──
_buildSection(
icon: Icons.gps_fixed,
title: 'GPS & Location',
subtitle: 'Fast updates, thresholds, and permissions',
title: AppLocalizations.of(context)!.gpsAndLocation,
subtitle: AppLocalizations.of(context)!.fastUpdatesThresholdsAndPermissions,
children: [
SwitchListTile(
dense: true,
@@ -1749,7 +1749,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
ListTile(
dense: true,
leading: const Icon(Icons.forum, size: 20),
title: const Text('Fast GPS target channel'),
title: Text(AppLocalizations.of(context)!.fastGpsTargetChannel),
subtitle: Text(
_describeFastLocationChannel(
context.watch<ContactsProvider>().channels,
@@ -1761,7 +1761,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
ListTile(
dense: true,
leading: const Icon(Icons.send, size: 20),
title: const Text('Test send update'),
title: Text(AppLocalizations.of(context)!.testSendUpdate),
trailing: const Icon(Icons.chevron_right),
onTap: _sendTestFastLocationUpdate,
),
@@ -1831,8 +1831,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
_buildSection(
icon: Icons.mic_rounded,
title: 'Voice',
subtitle: 'Codec, bitrate, and audio processing',
title: AppLocalizations.of(context)!.voice,
subtitle: AppLocalizations.of(context)!.codecBitrateAndAudioProcessing,
children: [
ListTile(
dense: true,
@@ -1933,8 +1933,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
// ── Images ──
_buildSection(
icon: Icons.image_rounded,
title: 'Images',
subtitle: 'Size, compression, and preview',
title: AppLocalizations.of(context)!.images,
subtitle: AppLocalizations.of(context)!.sizeCompressionAndPreview,
children: [
ListTile(
dense: true,
@@ -1970,7 +1970,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
dense: true,
secondary: const Icon(Icons.invert_colors, size: 20),
title: Text(l10n.grayscale),
subtitle: const Text('Smaller file size'),
subtitle: Text(AppLocalizations.of(context)!.smallerFileSize),
value: _imageGrayscale,
onChanged: (value) async {
await ImagePreferences.setGrayscale(value);
@@ -1982,7 +1982,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
dense: true,
secondary: const Icon(Icons.compress, size: 20),
title: Text(l10n.ultraMode),
subtitle: const Text('Extra-aggressive AVIF compression'),
subtitle: Text(AppLocalizations.of(context)!.extraAggressiveAvifCompression),
value: _imageUltraMode,
onChanged: (value) async {
await ImagePreferences.setUltraMode(value);
@@ -2000,8 +2000,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
// ── Profiles ──
_buildSection(
icon: Icons.layers_outlined,
title: 'Profiles',
subtitle: 'Multi-device workspace management',
title: AppLocalizations.of(context)!.profiles,
subtitle: AppLocalizations.of(context)!.multiDeviceWorkspaceManagement,
children: [
SwitchListTile(
dense: true,
@@ -2043,8 +2043,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
// ── Help ──
_buildSection(
icon: Icons.help_outline_rounded,
title: 'Help',
subtitle: 'Templates and tutorials',
title: AppLocalizations.of(context)!.help,
subtitle: AppLocalizations.of(context)!.templatesAndTutorials,
children: [
ListTile(
dense: true,
@@ -2125,8 +2125,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
// ── Data & Developer ──
_buildSection(
icon: Icons.storage_rounded,
title: 'Data',
subtitle: 'Traffic stats, message history, and developer tools',
title: AppLocalizations.of(context)!.data,
subtitle: AppLocalizations.of(context)!.trafficStatsMessageHistoryAndDeveloperTools,
children: [
Consumer<AppProvider>(
builder: (context, appProvider, child) => ListenableBuilder(