mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
i18n: Add 247 missing translation keys across all 13 languages
Replace hardcoded English strings in 32 Dart source files with l10n references. Covers settings labels, UI actions, map/SAR features, device config, notifications, profiles, spectrum scan, and status messages for sl, de, hr, pl, es, fr, it, el, ru, tr, uk, zh, pt.
This commit is contained in:
@@ -221,7 +221,7 @@ class _ConnectionDialogState extends State<ConnectionDialog>
|
||||
Icons.info_outline,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.defaultPinInfo,
|
||||
@@ -260,7 +260,7 @@ class _ConnectionDialogState extends State<ConnectionDialog>
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.noDevicesFound,
|
||||
style: TextStyle(
|
||||
@@ -271,7 +271,7 @@ class _ConnectionDialogState extends State<ConnectionDialog>
|
||||
const SizedBox(height: 8),
|
||||
TextButton.icon(
|
||||
onPressed: _refreshBleDevices,
|
||||
icon: const Icon(Icons.refresh),
|
||||
icon: Icon(Icons.refresh),
|
||||
label: Text(AppLocalizations.of(context)!.scanAgain),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -133,7 +133,7 @@ class _AddChannelSheetState extends State<AddChannelSheet> {
|
||||
onPressed: _isCreating
|
||||
? null
|
||||
: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.close),
|
||||
icon: Icon(Icons.close),
|
||||
tooltip: l10n.close,
|
||||
),
|
||||
],
|
||||
@@ -158,7 +158,7 @@ class _AddChannelSheetState extends State<AddChannelSheet> {
|
||||
size: 20,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
l10n.channelTypesInfo,
|
||||
@@ -170,7 +170,7 @@ class _AddChannelSheetState extends State<AddChannelSheet> {
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
@@ -196,7 +196,7 @@ class _AddChannelSheetState extends State<AddChannelSheet> {
|
||||
},
|
||||
),
|
||||
if (!isHashChannel) ...[
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _secretController,
|
||||
decoration: InputDecoration(
|
||||
@@ -211,7 +211,7 @@ class _AddChannelSheetState extends State<AddChannelSheet> {
|
||||
textInputAction: TextInputAction.done,
|
||||
onFieldSubmitted: (_) => _handleCreate(),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.channelSecretHelp,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
@@ -236,7 +236,7 @@ class _AddChannelSheetState extends State<AddChannelSheet> {
|
||||
size: 20,
|
||||
color: Colors.blue,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
l10n.hashChannelInfo,
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import '../../models/contact.dart';
|
||||
import '../../models/path_history.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../providers/connection_provider.dart';
|
||||
import '../../services/contact_route_resolver.dart';
|
||||
@@ -213,6 +214,16 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
||||
);
|
||||
}
|
||||
|
||||
ParsedContactRoute? get _effectiveRoute {
|
||||
if (_parsedRoute != null) {
|
||||
return _parsedRoute;
|
||||
}
|
||||
if (_errorText != null || _controller.text.trim().isNotEmpty) {
|
||||
return null;
|
||||
}
|
||||
return ContactRouteCodec.direct(hashSize: _selectedHashSize);
|
||||
}
|
||||
|
||||
void _toggleHop(Contact contact) {
|
||||
setState(() {
|
||||
if (_selectedMapHops.any(
|
||||
@@ -415,13 +426,14 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
||||
),
|
||||
trailing: FilledButton.tonal(
|
||||
onPressed: () => _applyHistoryRecord(record),
|
||||
child: const Text('Use'),
|
||||
child: Text(AppLocalizations.of(context)!.use),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPreviewSection() {
|
||||
final previewRoute = _effectiveRoute;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
@@ -435,20 +447,21 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_parsedRoute == null ? 'Route preview' : _parsedRoute!.summary,
|
||||
previewRoute == null ? 'Route preview' : previewRoute.summary,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_parsedRoute == null
|
||||
previewRoute == null
|
||||
? 'Pick relays from the list below or open manual edit if you need exact hop tokens.'
|
||||
: '${_parsedRoute!.byteLength} bytes • descriptor 0x${_parsedRoute!.encodedPathLen.toRadixString(16).padLeft(2, '0').toUpperCase()}',
|
||||
: '${previewRoute.byteLength} bytes • descriptor 0x${previewRoute.encodedPathLen.toRadixString(16).padLeft(2, '0').toUpperCase()}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
if (_parsedRoute != null) ...[
|
||||
if (previewRoute != null &&
|
||||
previewRoute.canonicalText.isNotEmpty) ...[
|
||||
const SizedBox(height: 10),
|
||||
SelectableText(
|
||||
_parsedRoute!.canonicalText,
|
||||
previewRoute.canonicalText,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(fontFamily: 'monospace'),
|
||||
@@ -469,7 +482,7 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
||||
border: Border.all(color: Theme.of(context).dividerColor),
|
||||
),
|
||||
child: Text(
|
||||
'No relays selected yet. Add repeaters below or let auto resolve build a route.',
|
||||
'No relays selected. Save now to use a direct path, or add repeaters below.',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
);
|
||||
@@ -590,7 +603,7 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
||||
_showManualEditor = expanded;
|
||||
});
|
||||
},
|
||||
title: const Text('Manual route edit'),
|
||||
title: Text(AppLocalizations.of(context)!.manualRouteEdit),
|
||||
subtitle: const Text(
|
||||
'Use this when you need to paste or tweak hop tokens directly.',
|
||||
),
|
||||
@@ -728,8 +741,8 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
||||
children: [
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: _resolvePathAutomatically,
|
||||
icon: const Icon(Icons.auto_fix_high),
|
||||
label: const Text('Auto resolve'),
|
||||
icon: Icon(Icons.auto_fix_high),
|
||||
label: Text(AppLocalizations.of(context)!.autoResolve),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _selectedMapHops.isEmpty
|
||||
@@ -740,8 +753,8 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
||||
_syncControllerFromSelectedHops();
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.clear_all),
|
||||
label: const Text('Clear relays'),
|
||||
icon: Icon(Icons.clear_all),
|
||||
label: Text(AppLocalizations.of(context)!.clearRelays),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -796,7 +809,7 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (observedRecord != null) ...[
|
||||
_buildHistoryRecordTile(observedRecord, title: 'Observed mesh route'),
|
||||
_buildHistoryRecordTile(observedRecord, title: AppLocalizations.of(context)!.observedMeshRoute),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
if (remainingRecords.isEmpty)
|
||||
@@ -822,6 +835,7 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final effectiveRoute = _effectiveRoute;
|
||||
final appProvider = context.watch<AppProvider>();
|
||||
final connectionProvider = context.watch<ConnectionProvider>();
|
||||
final selfPoint =
|
||||
@@ -939,22 +953,24 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(
|
||||
context,
|
||||
).pop(const ContactRouteDialogResult.clear()),
|
||||
child: const Text('Clear Route'),
|
||||
).pop(ContactRouteDialogResult.clear()),
|
||||
child: Text(
|
||||
'${AppLocalizations.of(context)!.clearRoute} (${AppLocalizations.of(context)!.flood})',
|
||||
),
|
||||
)
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
FilledButton(
|
||||
onPressed: _parsedRoute == null
|
||||
onPressed: effectiveRoute == null
|
||||
? null
|
||||
: () => Navigator.of(context).pop(
|
||||
ContactRouteDialogResult.setWithFallback(
|
||||
_parsedRoute!,
|
||||
effectiveRoute,
|
||||
inferredFallbackLocation:
|
||||
_buildSyntheticFallbackLocation(),
|
||||
),
|
||||
),
|
||||
child: const Text('Save Path'),
|
||||
child: Text(AppLocalizations.of(context)!.savePath),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -159,7 +159,7 @@ class ContactTile extends StatelessWidget {
|
||||
Row(children: [_buildRoutePill(context, contact)]),
|
||||
] else
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
padding: EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.noGpsData,
|
||||
style: Theme.of(
|
||||
@@ -361,8 +361,8 @@ class ContactTile extends StatelessWidget {
|
||||
onTap: () async {
|
||||
Navigator.pop(sheetContext);
|
||||
final toggled = contact.toggleFavourite();
|
||||
final connectionProvider =
|
||||
context.read<ConnectionProvider>();
|
||||
final connectionProvider = context
|
||||
.read<ConnectionProvider>();
|
||||
await connectionProvider.addOrUpdateContact(toggled);
|
||||
if (context.mounted) {
|
||||
// Refresh contact from device so the local cache is updated
|
||||
@@ -372,12 +372,12 @@ class ContactTile extends StatelessWidget {
|
||||
),
|
||||
if (!contact.isChannel)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.share_outlined),
|
||||
title: const Text('Share Contact'),
|
||||
leading: Icon(Icons.share_outlined),
|
||||
title: Text(l10n.shareContact),
|
||||
onTap: () async {
|
||||
Navigator.pop(sheetContext);
|
||||
final connectionProvider =
|
||||
context.read<ConnectionProvider>();
|
||||
final connectionProvider = context
|
||||
.read<ConnectionProvider>();
|
||||
final url = await connectionProvider.exportContactUrl(
|
||||
contact.publicKey,
|
||||
);
|
||||
@@ -385,22 +385,20 @@ class ContactTile extends StatelessWidget {
|
||||
await Clipboard.setData(ClipboardData(text: url));
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Contact link copied to clipboard'),
|
||||
SnackBar(
|
||||
content: Text(l10n.contactLinkCopiedToClipboard),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Failed to export contact'),
|
||||
),
|
||||
SnackBar(content: Text(l10n.failedToExportContact)),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.message_outlined),
|
||||
leading: Icon(Icons.message_outlined),
|
||||
title: Text(l10n.messages),
|
||||
enabled: canMessage,
|
||||
onTap: !canMessage
|
||||
@@ -412,7 +410,7 @@ class ContactTile extends StatelessWidget {
|
||||
),
|
||||
if (contact.displayLocation != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.map_outlined),
|
||||
leading: Icon(Icons.map_outlined),
|
||||
title: Text(l10n.viewOnMap),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
@@ -438,8 +436,8 @@ class ContactTile extends StatelessWidget {
|
||||
),
|
||||
if (canPreviewSensor)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.visibility_outlined),
|
||||
title: const Text('Preview'),
|
||||
leading: Icon(Icons.visibility_outlined),
|
||||
title: Text(l10n.preview),
|
||||
onTap: () async {
|
||||
Navigator.pop(sheetContext);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
@@ -467,7 +465,7 @@ class ContactTile extends StatelessWidget {
|
||||
),
|
||||
if (canSetPath)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.alt_route),
|
||||
leading: Icon(Icons.alt_route),
|
||||
title: Text(l10n.contactSetPath),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
@@ -476,8 +474,8 @@ class ContactTile extends StatelessWidget {
|
||||
),
|
||||
if (!contact.isChannel)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.route),
|
||||
title: const Text('Trace'),
|
||||
leading: Icon(Icons.route),
|
||||
title: Text(l10n.trace),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
_showTraceSheet(context, contact);
|
||||
@@ -494,8 +492,8 @@ class ContactTile extends StatelessWidget {
|
||||
),
|
||||
if (!contact.isPublicChannel)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.edit_outlined),
|
||||
title: const Text('Edit name'),
|
||||
leading: Icon(Icons.edit_outlined),
|
||||
title: Text(l10n.editName),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
_showNameOverrideDialog(context, contact);
|
||||
@@ -503,7 +501,7 @@ class ContactTile extends StatelessWidget {
|
||||
),
|
||||
if (!contact.isPublicChannel)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete, color: Colors.red),
|
||||
leading: Icon(Icons.delete, color: Colors.red),
|
||||
title: Text(
|
||||
contact.isChannel ? l10n.deleteChannel : l10n.deleteContact,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
@@ -788,12 +786,13 @@ class ContactTile extends StatelessWidget {
|
||||
paddedPathBytes: parsedRoute.paddedPathBytes,
|
||||
);
|
||||
if (context.mounted) {
|
||||
final routeLabel = parsedRoute.hopCount == 0
|
||||
? AppLocalizations.of(context)!.direct
|
||||
: parsedRoute.canonicalText;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
)!.contactRouteSet(parsedRoute.canonicalText),
|
||||
AppLocalizations.of(context)!.contactRouteSet(routeLabel),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1321,14 +1320,16 @@ class _NeighboursSheetState extends State<_NeighboursSheet> {
|
||||
final timestampOrMs = int.tryParse(parts[1]);
|
||||
final snrRaw = int.tryParse(parts[2]);
|
||||
final isMs = timestampOrMs != null && timestampOrMs < 1e9;
|
||||
parsed.add(_Neighbour(
|
||||
publicKeyHex: keyHex,
|
||||
lastSeenAt: !isMs && timestampOrMs != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(timestampOrMs * 1000)
|
||||
: null,
|
||||
lastSeenMs: isMs ? timestampOrMs : null,
|
||||
snrDb: snrRaw != null ? snrRaw / 4.0 : null,
|
||||
));
|
||||
parsed.add(
|
||||
_Neighbour(
|
||||
publicKeyHex: keyHex,
|
||||
lastSeenAt: !isMs && timestampOrMs != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(timestampOrMs * 1000)
|
||||
: null,
|
||||
lastSeenMs: isMs ? timestampOrMs : null,
|
||||
snrDb: snrRaw != null ? snrRaw / 4.0 : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1401,41 +1402,35 @@ class _NeighboursSheetState extends State<_NeighboursSheet> {
|
||||
child: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _error != null
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Text(
|
||||
_error!,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
)
|
||||
: _neighbours.isEmpty
|
||||
? const Center(child: Text('No neighbours found'))
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
itemCount: _neighbours.length,
|
||||
itemBuilder: (context, index) {
|
||||
final n = _neighbours[index];
|
||||
final name =
|
||||
_resolveNeighbourName(n.publicKeyHex);
|
||||
final parts = <String>[
|
||||
if (n.snrDb != null)
|
||||
'SNR ${n.snrDb!.toStringAsFixed(1)} dB',
|
||||
if (n.lastSeenAt != null)
|
||||
_formatAge(n.lastSeenAt!),
|
||||
if (n.lastSeenMs != null)
|
||||
'${n.lastSeenMs}ms ago',
|
||||
];
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.router_outlined),
|
||||
title: Text(name),
|
||||
subtitle: parts.isNotEmpty
|
||||
? Text(parts.join(' • '))
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Text(_error!, textAlign: TextAlign.center),
|
||||
),
|
||||
)
|
||||
: _neighbours.isEmpty
|
||||
? const Center(child: Text('No neighbours found'))
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
itemCount: _neighbours.length,
|
||||
itemBuilder: (context, index) {
|
||||
final n = _neighbours[index];
|
||||
final name = _resolveNeighbourName(n.publicKeyHex);
|
||||
final parts = <String>[
|
||||
if (n.snrDb != null)
|
||||
'SNR ${n.snrDb!.toStringAsFixed(1)} dB',
|
||||
if (n.lastSeenAt != null) _formatAge(n.lastSeenAt!),
|
||||
if (n.lastSeenMs != null) '${n.lastSeenMs}ms ago',
|
||||
];
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.router_outlined),
|
||||
title: Text(name),
|
||||
subtitle: parts.isNotEmpty
|
||||
? Text(parts.join(' • '))
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -401,7 +401,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.enterPasswordInfo,
|
||||
@@ -488,7 +488,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.login),
|
||||
: Icon(Icons.login),
|
||||
label: Text(
|
||||
_isLoggingIn
|
||||
? AppLocalizations.of(context)!.loggingInDots
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:meshcore_client/meshcore_client.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
|
||||
class SpectrumScanPanel extends StatelessWidget {
|
||||
final ThemeData theme;
|
||||
@@ -103,12 +104,12 @@ class SpectrumScanPanel extends StatelessWidget {
|
||||
children: [
|
||||
_LegendChip(
|
||||
color: theme.colorScheme.primary,
|
||||
label: 'Quiet',
|
||||
label: AppLocalizations.of(context)!.quiet,
|
||||
),
|
||||
const _LegendChip(color: Colors.orange, label: 'Moderate'),
|
||||
_LegendChip(color: Colors.orange, label: AppLocalizations.of(context)!.moderate),
|
||||
_LegendChip(
|
||||
color: theme.colorScheme.error,
|
||||
label: 'Busy',
|
||||
label: AppLocalizations.of(context)!.busy,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -211,8 +212,8 @@ class SpectrumScanPanel extends StatelessWidget {
|
||||
onPressed: selectedFrequencyKhz == null
|
||||
? null
|
||||
: onApplySelected,
|
||||
icon: const Icon(Icons.north_east),
|
||||
label: const Text('Use selected frequency'),
|
||||
icon: Icon(Icons.north_east),
|
||||
label: Text(AppLocalizations.of(context)!.useSelectedFrequency),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -100,7 +100,7 @@ class CompassContactList extends StatelessWidget {
|
||||
// Persons/Team section
|
||||
if (showContacts && persons.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, bottom: 8, top: 4),
|
||||
padding: EdgeInsets.only(left: 16, bottom: 8, top: 4),
|
||||
child: Text(
|
||||
l10n.teamMembers,
|
||||
style: Theme.of(
|
||||
@@ -135,7 +135,7 @@ class CompassContactList extends StatelessWidget {
|
||||
// Repeaters section
|
||||
if (showRepeaters && repeaters.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, bottom: 8, top: 12),
|
||||
padding: EdgeInsets.only(left: 16, bottom: 8, top: 12),
|
||||
child: Text(
|
||||
l10n.repeaters,
|
||||
style: Theme.of(
|
||||
@@ -151,7 +151,7 @@ class CompassContactList extends StatelessWidget {
|
||||
// Rooms section
|
||||
if (rooms.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, bottom: 8, top: 12),
|
||||
padding: EdgeInsets.only(left: 16, bottom: 8, top: 12),
|
||||
child: Text(
|
||||
l10n.rooms,
|
||||
style: Theme.of(
|
||||
|
||||
@@ -49,7 +49,7 @@ class _CompassFiltersState extends State<CompassFilters> {
|
||||
title: Row(
|
||||
children: [
|
||||
const Icon(Icons.filter_list, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(width: 8),
|
||||
Text(l10n.filterMarkers),
|
||||
],
|
||||
),
|
||||
@@ -62,7 +62,7 @@ class _CompassFiltersState extends State<CompassFilters> {
|
||||
_CompactFilterItem(
|
||||
icon: Icons.timeline,
|
||||
color: Colors.blue,
|
||||
label: 'Location Trail',
|
||||
label: l10n.locationTrail,
|
||||
value: mapProvider.isTrailVisible,
|
||||
onChanged: (value) {
|
||||
mapProvider.toggleTrailVisibility();
|
||||
@@ -100,7 +100,7 @@ class _CompassFiltersState extends State<CompassFilters> {
|
||||
const SizedBox(height: 4),
|
||||
// SAR Markers section
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8, bottom: 8, top: 4),
|
||||
padding: EdgeInsets.only(left: 8, bottom: 8, top: 4),
|
||||
child: Text(
|
||||
l10n.sarMarkers,
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
@@ -118,7 +118,7 @@ class _CompassFiltersState extends State<CompassFilters> {
|
||||
setDialogState(() {});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SizedBox(height: 4),
|
||||
_CompactFilterItem(
|
||||
icon: Icons.local_fire_department,
|
||||
color: Colors.red,
|
||||
@@ -129,7 +129,7 @@ class _CompassFiltersState extends State<CompassFilters> {
|
||||
setDialogState(() {});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SizedBox(height: 4),
|
||||
_CompactFilterItem(
|
||||
icon: Icons.home_work,
|
||||
color: Colors.orange,
|
||||
@@ -164,7 +164,7 @@ class _CompassFiltersState extends State<CompassFilters> {
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.filter_list),
|
||||
icon: Icon(Icons.filter_list),
|
||||
tooltip: l10n.filterMarkersTooltip,
|
||||
onPressed: () => _showFilterDialog(),
|
||||
);
|
||||
|
||||
@@ -565,7 +565,7 @@ class _LocationFormatToggleState extends State<_LocationFormatToggle> {
|
||||
Widget build(BuildContext context) {
|
||||
final position = widget.position;
|
||||
if (position == null) {
|
||||
return const SizedBox.shrink();
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
@@ -64,7 +64,7 @@ class CompassSarList extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, top: 16, bottom: 8),
|
||||
padding: EdgeInsets.only(left: 16, top: 16, bottom: 8),
|
||||
child: Text(
|
||||
l10n.sarMarkers,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
|
||||
@@ -603,7 +603,7 @@ class DrawingToolbar extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.share),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.shareDrawingsCount(
|
||||
@@ -620,7 +620,7 @@ class DrawingToolbar extends StatelessWidget {
|
||||
const Divider(),
|
||||
// Option: Send to Public Channel
|
||||
ListTile(
|
||||
leading: const Icon(Icons.public, color: Colors.blue),
|
||||
leading: Icon(Icons.public, color: Colors.blue),
|
||||
title: Text(AppLocalizations.of(context)!.publicChannel),
|
||||
subtitle: Text(AppLocalizations.of(context)!.broadcastToAll),
|
||||
onTap: () async {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
|
||||
class MapLegend extends StatelessWidget {
|
||||
final int teamMemberCount;
|
||||
@@ -35,31 +36,31 @@ class MapLegend extends StatelessWidget {
|
||||
_LegendItem(
|
||||
icon: Icons.person,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
label: 'Team',
|
||||
label: AppLocalizations.of(context)!.team,
|
||||
count: teamMemberCount,
|
||||
),
|
||||
_LegendItem(
|
||||
icon: Icons.person_pin,
|
||||
color: Colors.green,
|
||||
label: 'Found',
|
||||
label: AppLocalizations.of(context)!.found,
|
||||
count: foundPersonCount,
|
||||
),
|
||||
_LegendItem(
|
||||
icon: Icons.local_fire_department,
|
||||
color: Colors.red,
|
||||
label: 'Fire',
|
||||
label: AppLocalizations.of(context)!.fire,
|
||||
count: fireCount,
|
||||
),
|
||||
_LegendItem(
|
||||
icon: Icons.home_work,
|
||||
color: Colors.orange,
|
||||
label: 'Staging',
|
||||
label: AppLocalizations.of(context)!.staging,
|
||||
count: stagingAreaCount,
|
||||
),
|
||||
_LegendItem(
|
||||
icon: Icons.inventory_2,
|
||||
color: Colors.purple,
|
||||
label: 'Object',
|
||||
label: AppLocalizations.of(context)!.object,
|
||||
count: objectCount,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -98,7 +98,7 @@ class _MapMessageOverlayState extends State<MapMessageOverlay> {
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.recentMessages,
|
||||
|
||||
@@ -39,7 +39,7 @@ class TrailControls extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.timeline, size: 24),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(width: 12),
|
||||
Text(
|
||||
l10n.locationTrail,
|
||||
style: const TextStyle(
|
||||
@@ -53,7 +53,7 @@ class TrailControls extends StatelessWidget {
|
||||
|
||||
// Trail visibility toggle
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.visibility),
|
||||
secondary: Icon(Icons.visibility),
|
||||
title: Text(l10n.showTrailOnMap),
|
||||
subtitle: Text(
|
||||
mapProvider.isTrailVisible
|
||||
@@ -75,7 +75,7 @@ class TrailControls extends StatelessWidget {
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => _importTrail(context, mapProvider),
|
||||
icon: const Icon(Icons.file_upload_outlined),
|
||||
icon: Icon(Icons.file_upload_outlined),
|
||||
label: Text(l10n.importFromClipboard),
|
||||
),
|
||||
if (mapProvider.currentTrail != null &&
|
||||
@@ -86,7 +86,7 @@ class TrailControls extends StatelessWidget {
|
||||
mapProvider.currentTrail!,
|
||||
customName: 'My Trail',
|
||||
),
|
||||
icon: const Icon(Icons.file_download_outlined),
|
||||
icon: Icon(Icons.file_download_outlined),
|
||||
label: Text(l10n.exportToClipboard),
|
||||
),
|
||||
],
|
||||
@@ -116,13 +116,13 @@ class TrailControls extends StatelessWidget {
|
||||
mapProvider.totalTrailDistance,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(height: 8),
|
||||
_buildStatRow(
|
||||
icon: Icons.access_time,
|
||||
label: l10n.duration,
|
||||
value: _formatDuration(mapProvider.trailDuration),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(height: 8),
|
||||
_buildStatRow(
|
||||
icon: Icons.place,
|
||||
label: l10n.points,
|
||||
@@ -139,7 +139,7 @@ class TrailControls extends StatelessWidget {
|
||||
onPressed: () {
|
||||
_showClearConfirmation(context, mapProvider, l10n);
|
||||
},
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
icon: Icon(Icons.delete_outline),
|
||||
label: Text(l10n.clearTrail),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
@@ -161,7 +161,7 @@ class TrailControls extends StatelessWidget {
|
||||
size: 48,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.noTrailRecorded,
|
||||
style: const TextStyle(
|
||||
@@ -169,7 +169,7 @@ class TrailControls extends StatelessWidget {
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.startTrackingToRecord,
|
||||
style: const TextStyle(
|
||||
@@ -191,7 +191,7 @@ class TrailControls extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.people, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.contactTrails,
|
||||
style: const TextStyle(
|
||||
@@ -205,7 +205,7 @@ class TrailControls extends StatelessWidget {
|
||||
|
||||
// Show All Contact Trails toggle
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.route),
|
||||
secondary: Icon(Icons.route),
|
||||
title: Text(l10n.showAllContactTrails),
|
||||
subtitle: Text(
|
||||
contactsWithTrails.isEmpty
|
||||
|
||||
@@ -200,7 +200,7 @@ class _CustomMapSarUpdateSheetState extends State<CustomMapSarUpdateSheet> {
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(height: 24),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.sendTo,
|
||||
style: TextStyle(
|
||||
@@ -231,7 +231,7 @@ class _CustomMapSarUpdateSheetState extends State<CustomMapSarUpdateSheet> {
|
||||
color: Colors.red.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: const Text('No destinations available'),
|
||||
child: Text(AppLocalizations.of(context)!.noDestinationsAvailableLabel),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -426,7 +426,7 @@ class _CustomMapSarUpdateSheetState extends State<CustomMapSarUpdateSheet> {
|
||||
(!_sendToAllContacts && _selectedContact == null)) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Select marker type and destination'),
|
||||
content: Text(AppLocalizations.of(context)!.selectMarkerTypeAndDestination),
|
||||
),
|
||||
);
|
||||
return;
|
||||
@@ -449,8 +449,8 @@ class _CustomMapSarUpdateSheetState extends State<CustomMapSarUpdateSheet> {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.send),
|
||||
label: const Text('Send'),
|
||||
icon: Icon(Icons.send),
|
||||
label: Text(AppLocalizations.of(context)!.send),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -288,7 +288,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
children: [
|
||||
if (!isOwnMessage && widget.onReply != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.reply),
|
||||
leading: Icon(Icons.reply),
|
||||
title: Text(AppLocalizations.of(context)!.reply),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
@@ -297,7 +297,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
),
|
||||
// Copy text option
|
||||
ListTile(
|
||||
leading: const Icon(Icons.copy),
|
||||
leading: Icon(Icons.copy),
|
||||
title: Text(AppLocalizations.of(context)!.copyText),
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: widget.message.text));
|
||||
@@ -329,7 +329,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.bookmark_add),
|
||||
leading: Icon(Icons.bookmark_add),
|
||||
title: Text(AppLocalizations.of(context)!.saveAsTemplate),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
@@ -342,7 +342,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
if (widget.message.isSarMarker &&
|
||||
widget.message.sarGpsCoordinates != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.share_location),
|
||||
leading: Icon(Icons.share_location),
|
||||
title: Text(AppLocalizations.of(context)!.shareLocation),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
@@ -352,7 +352,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
// Navigate to drawing option (only for drawing messages)
|
||||
if (widget.message.isDrawing && widget.message.drawingId != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.map),
|
||||
leading: Icon(Icons.map),
|
||||
title: Text(AppLocalizations.of(context)!.navigateToDrawing),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
@@ -362,7 +362,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
// Copy coordinates option (only for drawing messages)
|
||||
if (widget.message.isDrawing && widget.message.drawingId != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.copy),
|
||||
leading: Icon(Icons.copy),
|
||||
title: Text(AppLocalizations.of(context)!.copyCoordinates),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
@@ -372,7 +372,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
// Hide from map option (only for drawing messages)
|
||||
if (widget.message.isDrawing && widget.message.drawingId != null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.visibility_off),
|
||||
leading: Icon(Icons.visibility_off),
|
||||
title: Text(AppLocalizations.of(context)!.hideFromMap),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
@@ -381,7 +381,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
),
|
||||
// Technical details option
|
||||
ListTile(
|
||||
leading: const Icon(Icons.data_object),
|
||||
leading: Icon(Icons.data_object),
|
||||
title: Text(AppLocalizations.of(context)!.technicalDetails),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
@@ -392,8 +392,8 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
widget.message.pathLen > 0 &&
|
||||
widget.message.pathLen < 255)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.route),
|
||||
title: const Text('Trace'),
|
||||
leading: Icon(Icons.route),
|
||||
title: Text(AppLocalizations.of(context)!.trace),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
_showTraceSheet(context);
|
||||
@@ -401,7 +401,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
),
|
||||
// Delete message option
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete, color: Colors.red),
|
||||
leading: Icon(Icons.delete, color: Colors.red),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.delete,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
@@ -686,7 +686,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
|
||||
void copyField(String value) {
|
||||
Clipboard.setData(ClipboardData(text: value));
|
||||
ToastLogger.success(context, l10n.textCopiedToClipboard);
|
||||
ToastLogger.success(context, AppLocalizations.of(context)!.textCopiedToClipboard);
|
||||
}
|
||||
|
||||
showModalBottomSheet(
|
||||
@@ -708,14 +708,14 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
l10n.messageTechnicalDetails,
|
||||
AppLocalizations.of(context)!.messageTechnicalDetails,
|
||||
style: Theme.of(sheetContext).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(sheetContext),
|
||||
icon: const Icon(Icons.close),
|
||||
tooltip: l10n.close,
|
||||
icon: Icon(Icons.close),
|
||||
tooltip: AppLocalizations.of(context)!.close,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -758,17 +758,17 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
if (widget.message.lastEchoRssiDbm != null ||
|
||||
snrDb != null ||
|
||||
rssiDbm != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(height: 12),
|
||||
_techSection(
|
||||
sheetContext,
|
||||
icon: Icons.network_check,
|
||||
title: l10n.linkQuality,
|
||||
title: AppLocalizations.of(context)!.linkQuality,
|
||||
child: Column(
|
||||
children: [
|
||||
if (rssiDbm != null)
|
||||
_signalRow(
|
||||
sheetContext,
|
||||
label: 'RSSI',
|
||||
label: AppLocalizations.of(context)!.rssi,
|
||||
valueLabel: '$rssiDbm dBm',
|
||||
normalized:
|
||||
((rssiDbm.toDouble() + 120.0) / 70.0)
|
||||
@@ -780,10 +780,10 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
: Colors.redAccent,
|
||||
),
|
||||
if (snrDb != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(height: 8),
|
||||
_signalRow(
|
||||
sheetContext,
|
||||
label: 'SNR',
|
||||
label: AppLocalizations.of(context)!.snr,
|
||||
valueLabel: '${snrDb.toStringAsFixed(1)} dB',
|
||||
normalized: ((snrDb + 20.0) / 40.0).clamp(
|
||||
0.0,
|
||||
@@ -800,21 +800,21 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(height: 12),
|
||||
_techSection(
|
||||
sheetContext,
|
||||
icon: Icons.tune,
|
||||
title: l10n.delivery,
|
||||
title: AppLocalizations.of(context)!.delivery,
|
||||
child: Column(
|
||||
children: [
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.status,
|
||||
label: AppLocalizations.of(context)!.status,
|
||||
value: widget.message.deliveryStatus.name,
|
||||
),
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Received (RFC3339)',
|
||||
label: AppLocalizations.of(context)!.receivedRfc3339,
|
||||
value: _formatRfc3339(widget.message.receivedAt),
|
||||
onCopy: () => copyField(
|
||||
_formatRfc3339(widget.message.receivedAt),
|
||||
@@ -823,14 +823,14 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
if (widget.message.expectedAckTag != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.expectedAckTag,
|
||||
label: AppLocalizations.of(context)!.expectedAckTag,
|
||||
value: widget.message.expectedAckTag!
|
||||
.toString(),
|
||||
),
|
||||
if (receptionDetails?.senderToReceiptMs != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Sender to receipt',
|
||||
label: AppLocalizations.of(context)!.senderToReceipt,
|
||||
value: _formatDurationMs(
|
||||
receptionDetails!.senderToReceiptMs!,
|
||||
),
|
||||
@@ -838,7 +838,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
if (receptionDetails?.estimatedTransmitMs != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Estimated tx',
|
||||
label: AppLocalizations.of(context)!.estimatedTx,
|
||||
value: _formatDurationMs(
|
||||
receptionDetails!.estimatedTransmitMs!,
|
||||
),
|
||||
@@ -846,7 +846,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
if (receptionDetails?.postTransmitDelayMs != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Post-tx delay',
|
||||
label: AppLocalizations.of(context)!.posttxDelay,
|
||||
value: _formatDurationMs(
|
||||
receptionDetails!.postTransmitDelayMs!,
|
||||
),
|
||||
@@ -854,44 +854,44 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
if (widget.receivedCopies > 1)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Received copies',
|
||||
label: AppLocalizations.of(context)!.receivedCopies,
|
||||
value: '${widget.receivedCopies}',
|
||||
),
|
||||
if (widget.message.suggestedTimeoutMs != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'ACK timeout',
|
||||
label: AppLocalizations.of(context)!.ackTimeout,
|
||||
value:
|
||||
'${widget.message.suggestedTimeoutMs} ms',
|
||||
),
|
||||
if (retryCause != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Retry cause',
|
||||
label: AppLocalizations.of(context)!.retryCause,
|
||||
value: retryCause,
|
||||
),
|
||||
if (retryMode != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Retry mode',
|
||||
label: AppLocalizations.of(context)!.retryMode,
|
||||
value: retryMode,
|
||||
),
|
||||
if (widget.message.roundTripTimeMs != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.roundTrip,
|
||||
label: AppLocalizations.of(context)!.roundTrip,
|
||||
value: '${widget.message.roundTripTimeMs} ms',
|
||||
),
|
||||
if (widget.message.retryAttempt > 0)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.retryAttempt,
|
||||
label: AppLocalizations.of(context)!.retryAttempt,
|
||||
value: '${widget.message.retryAttempt}/4',
|
||||
),
|
||||
if (widget.message.lastRetryAt != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Last retry',
|
||||
label: AppLocalizations.of(context)!.lastRetry,
|
||||
value: _formatRfc3339(
|
||||
widget.message.lastRetryAt!,
|
||||
),
|
||||
@@ -902,49 +902,49 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
if (widget.message.usedFloodFallback)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.floodFallback,
|
||||
value: l10n.yes,
|
||||
label: AppLocalizations.of(context)!.floodFallback,
|
||||
value: AppLocalizations.of(context)!.yes,
|
||||
),
|
||||
if (routeMetadata?.canonicalPath
|
||||
case final routePath?)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Selected path',
|
||||
label: AppLocalizations.of(context)!.selectedPath,
|
||||
value: routePath,
|
||||
onCopy: () => copyField(routePath),
|
||||
),
|
||||
if (retryResult != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Retry result',
|
||||
label: AppLocalizations.of(context)!.retryResult,
|
||||
value: retryResult,
|
||||
),
|
||||
if (packetPathHex != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Path bytes',
|
||||
label: AppLocalizations.of(context)!.pathBytes,
|
||||
value: packetPathHex,
|
||||
onCopy: () => copyField(packetPathHex),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(height: 12),
|
||||
_techSection(
|
||||
sheetContext,
|
||||
icon: Icons.badge,
|
||||
title: l10n.identity,
|
||||
title: AppLocalizations.of(context)!.identity,
|
||||
child: Column(
|
||||
children: [
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.messageId,
|
||||
label: AppLocalizations.of(context)!.messageId,
|
||||
value: widget.message.id,
|
||||
onCopy: () => copyField(widget.message.id),
|
||||
),
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.sender,
|
||||
label: AppLocalizations.of(context)!.sender,
|
||||
value:
|
||||
senderName ??
|
||||
widget.message.senderName ??
|
||||
@@ -953,20 +953,20 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
if (senderPrefixHex != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.senderKey,
|
||||
label: AppLocalizations.of(context)!.senderKey,
|
||||
value: senderPrefixHex,
|
||||
onCopy: () => copyField(senderPrefixHex),
|
||||
),
|
||||
if (recipientName != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.recipient,
|
||||
label: AppLocalizations.of(context)!.recipient,
|
||||
value: recipientName,
|
||||
),
|
||||
if (recipientPrefixHex != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.recipientKey,
|
||||
label: AppLocalizations.of(context)!.recipientKey,
|
||||
value: recipientPrefixHex,
|
||||
onCopy: () => copyField(recipientPrefixHex),
|
||||
),
|
||||
@@ -974,51 +974,51 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
),
|
||||
),
|
||||
if (widget.message.isVoice) ...[
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(height: 12),
|
||||
_techSection(
|
||||
sheetContext,
|
||||
icon: Icons.graphic_eq,
|
||||
title: l10n.voice,
|
||||
title: AppLocalizations.of(context)!.voice,
|
||||
child: Column(
|
||||
children: [
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.voiceId,
|
||||
label: AppLocalizations.of(context)!.voiceId,
|
||||
value: widget.message.voiceId ?? '-',
|
||||
),
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.envelope,
|
||||
label: AppLocalizations.of(context)!.envelope,
|
||||
value: envelope != null
|
||||
? 'VE3 compact'
|
||||
: l10n.unknown,
|
||||
: AppLocalizations.of(context)!.unknown,
|
||||
),
|
||||
if (voiceSession != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.sessionProgress,
|
||||
label: AppLocalizations.of(context)!.sessionProgress,
|
||||
value:
|
||||
'${voiceSession.receivedCount}/${voiceSession.total} segments',
|
||||
),
|
||||
if (voiceSession != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.complete,
|
||||
label: AppLocalizations.of(context)!.complete,
|
||||
value: voiceSession.isComplete
|
||||
? l10n.yes
|
||||
: l10n.no,
|
||||
? AppLocalizations.of(context)!.yes
|
||||
: AppLocalizations.of(context)!.no,
|
||||
),
|
||||
if (transferDetails != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Transfers',
|
||||
label: AppLocalizations.of(context)!.transfers,
|
||||
value: '${transferDetails.totalTransfers}',
|
||||
),
|
||||
if (transferDetails != null &&
|
||||
transferDetails.downloaders.isNotEmpty)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Downloaded by',
|
||||
label: AppLocalizations.of(context)!.downloadedBy,
|
||||
value: _formatDownloaderSummary(
|
||||
transferDetails,
|
||||
),
|
||||
@@ -1026,7 +1026,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
if (voiceTxEstimate > Duration.zero)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Estimated tx',
|
||||
label: AppLocalizations.of(context)!.estimatedTx,
|
||||
value: voiceTxEstimate.inSeconds < 60
|
||||
? '~${voiceTxEstimate.inSeconds}s'
|
||||
: '~${voiceTxEstimate.inMinutes}m ${voiceTxEstimate.inSeconds % 60}s',
|
||||
@@ -1036,32 +1036,32 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
),
|
||||
],
|
||||
if (imageEnvelope != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(height: 12),
|
||||
_techSection(
|
||||
sheetContext,
|
||||
icon: Icons.image_outlined,
|
||||
title: 'Image',
|
||||
title: AppLocalizations.of(context)!.image,
|
||||
child: Column(
|
||||
children: [
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.envelope,
|
||||
label: AppLocalizations.of(context)!.envelope,
|
||||
value: 'IE1',
|
||||
),
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Format',
|
||||
label: AppLocalizations.of(context)!.format,
|
||||
value: imageEnvelope.format.label,
|
||||
),
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Dimensions',
|
||||
label: AppLocalizations.of(context)!.dimensions,
|
||||
value:
|
||||
'${imageEnvelope.width}×${imageEnvelope.height}',
|
||||
),
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Segments',
|
||||
label: AppLocalizations.of(context)!.segments,
|
||||
value: imageSession != null
|
||||
? '${imageSession.receivedCount}/${imageSession.total}'
|
||||
: '${imageEnvelope.total}',
|
||||
@@ -1069,22 +1069,22 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
if (imageSession != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: l10n.complete,
|
||||
label: AppLocalizations.of(context)!.complete,
|
||||
value: imageSession.isComplete
|
||||
? l10n.yes
|
||||
: l10n.no,
|
||||
? AppLocalizations.of(context)!.yes
|
||||
: AppLocalizations.of(context)!.no,
|
||||
),
|
||||
if (transferDetails != null)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Transfers',
|
||||
label: AppLocalizations.of(context)!.transfers,
|
||||
value: '${transferDetails.totalTransfers}',
|
||||
),
|
||||
if (transferDetails != null &&
|
||||
transferDetails.downloaders.isNotEmpty)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Downloaded by',
|
||||
label: AppLocalizations.of(context)!.downloadedBy,
|
||||
value: _formatDownloaderSummary(
|
||||
transferDetails,
|
||||
),
|
||||
@@ -1092,7 +1092,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
if (imageTxEstimate > Duration.zero)
|
||||
_detailRow(
|
||||
sheetContext,
|
||||
label: 'Estimated tx',
|
||||
label: AppLocalizations.of(context)!.estimatedTx,
|
||||
value: imageTxEstimate.inSeconds < 60
|
||||
? '~${imageTxEstimate.inSeconds}s'
|
||||
: '~${imageTxEstimate.inMinutes}m ${imageTxEstimate.inSeconds % 60}s',
|
||||
@@ -1107,7 +1107,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
dense: true,
|
||||
visualDensity: VisualDensity.compact,
|
||||
title: Text(
|
||||
l10n.rawDump,
|
||||
AppLocalizations.of(context)!.rawDump,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -1432,7 +1432,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
final lon = coords.longitude.toStringAsFixed(6);
|
||||
|
||||
// Build share text
|
||||
final shareText = l10n.shareLocationText(
|
||||
final shareText = AppLocalizations.of(context)!.shareLocationText(
|
||||
markerInfo,
|
||||
lat,
|
||||
lon,
|
||||
@@ -1441,7 +1441,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
|
||||
// Share the location
|
||||
SharePlus.instance.share(
|
||||
ShareParams(text: shareText, subject: l10n.sarLocationShare),
|
||||
ShareParams(text: shareText, subject: AppLocalizations.of(context)!.sarLocationShare),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1852,14 +1852,14 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
}
|
||||
} else if (message.isChannelMessage) {
|
||||
if (message.channelIdx == 0) {
|
||||
channelDisplayName = l10n.publicChannel;
|
||||
channelDisplayName = AppLocalizations.of(context)!.publicChannel;
|
||||
} else {
|
||||
final channelContact = contactsProvider.channels.where((c) {
|
||||
return c.publicKey.length > 1 && c.publicKey[1] == message.channelIdx;
|
||||
}).firstOrNull;
|
||||
channelDisplayName =
|
||||
channelContact?.getLocalizedDisplayName(context) ??
|
||||
'${l10n.channel} ${message.channelIdx}';
|
||||
'${AppLocalizations.of(context)!.channel} ${message.channelIdx}';
|
||||
}
|
||||
|
||||
if (isOwnMessage) {
|
||||
@@ -1869,14 +1869,14 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
|
||||
final recipientSubtitle =
|
||||
isOwnMessage && message.isChannelMessage && recipientDisplayName != null
|
||||
? '${l10n.channel}: $recipientDisplayName'
|
||||
? '${AppLocalizations.of(context)!.channel}: $recipientDisplayName'
|
||||
: recipientDisplayName;
|
||||
final directCounterpartLabel = !message.isChannelMessage
|
||||
? (isOwnMessage ? recipientSubtitle : l10n.you)
|
||||
? (isOwnMessage ? recipientSubtitle : AppLocalizations.of(context)!.you)
|
||||
: null;
|
||||
final receivedChannelSubtitle =
|
||||
!isOwnMessage && message.isChannelMessage && channelDisplayName != null
|
||||
? '${l10n.channel}: $channelDisplayName'
|
||||
? '${AppLocalizations.of(context)!.channel}: $channelDisplayName'
|
||||
: null;
|
||||
|
||||
final shouldFloatBubble = widget.isCompact;
|
||||
@@ -1992,7 +1992,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.draw, size: 16, color: Colors.white),
|
||||
const SizedBox(width: 4),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.mapDrawing,
|
||||
style: Theme.of(context).textTheme.labelSmall
|
||||
@@ -2029,7 +2029,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
size: 16,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.sarAlert,
|
||||
style: Theme.of(context).textTheme.labelSmall
|
||||
@@ -2522,7 +2522,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
? Colors.red
|
||||
: Colors.orange,
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
SizedBox(width: 3),
|
||||
Text(
|
||||
message.deliveredRecipientsCount ==
|
||||
message.recipients!.length
|
||||
|
||||
@@ -55,12 +55,12 @@ class MessagesContent extends StatelessWidget {
|
||||
size: 64,
|
||||
color: Theme.of(context).disabledColor,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.noMessagesYet,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.pullDownToSync,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
|
||||
@@ -111,7 +111,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
|
||||
// Search field
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: EdgeInsets.all(16),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
@@ -154,7 +154,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
context: context,
|
||||
icon: Icons.all_inbox,
|
||||
title: l10n.showAll,
|
||||
subtitle: 'All messages',
|
||||
subtitle: l10n.allMessages,
|
||||
unreadCount: widget.unreadCount,
|
||||
isSelected: _isSelected('all', null),
|
||||
onTap: () {
|
||||
@@ -181,7 +181,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
),
|
||||
if (filteredChannels.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
l10n.noChannelsFound,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
@@ -234,7 +234,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
),
|
||||
if (filteredContacts.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
l10n.noContactsFound,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
@@ -283,7 +283,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
),
|
||||
if (filteredRooms.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
l10n.noRoomsFound,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
@@ -323,7 +323,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
size: 64,
|
||||
color: Theme.of(context).disabledColor,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.noRecipientsAvailable,
|
||||
style: Theme.of(context).textTheme.bodyLarge
|
||||
|
||||
@@ -381,7 +381,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Destination selection (compact dropdown with rooms, channels, and all contacts)
|
||||
Text(
|
||||
@@ -505,7 +505,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
size: 18,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.allTeamContacts,
|
||||
@@ -593,7 +593,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
color: Colors.green,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.directMessagesInfo(chatContactsCount),
|
||||
@@ -718,7 +718,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
SizedBox(width: 16),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.gettingLocation,
|
||||
style: TextStyle(color: colorScheme.onSurface),
|
||||
@@ -979,7 +979,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
size: 16,
|
||||
color: Colors.blue,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.exampleCoordinates,
|
||||
@@ -994,7 +994,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(height: 24),
|
||||
|
||||
// Optional notes
|
||||
Text(
|
||||
@@ -1193,7 +1193,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
icon: const Icon(Icons.send, size: 20),
|
||||
icon: Icon(Icons.send, size: 20),
|
||||
label: Text(
|
||||
AppLocalizations.of(context)!.sendSarMarker,
|
||||
style: const TextStyle(
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../../providers/contacts_provider.dart';
|
||||
import '../../providers/messages_provider.dart';
|
||||
import '../../utils/tictactoe_message_parser.dart';
|
||||
import '../../utils/toast_logger.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
|
||||
class TicTacToeMessageBubble extends StatelessWidget {
|
||||
final Message message;
|
||||
@@ -33,7 +34,7 @@ class TicTacToeMessageBubble extends StatelessWidget {
|
||||
final messages = context.watch<MessagesProvider>().messages;
|
||||
final selfKey = conn.deviceInfo.publicKey;
|
||||
if (selfKey == null || selfKey.length < 6) {
|
||||
return const Text('Tic-Tac-Toe unavailable');
|
||||
return Text(AppLocalizations.of(context)!.tictactoeUnavailable);
|
||||
}
|
||||
|
||||
final selfKey6 = _key6Hex(selfKey);
|
||||
@@ -43,7 +44,7 @@ class TicTacToeMessageBubble extends StatelessWidget {
|
||||
isSentByMe: isSentByMe,
|
||||
);
|
||||
if (opponent == null) {
|
||||
return const Text('Tic-Tac-Toe: opponent unknown');
|
||||
return Text(AppLocalizations.of(context)!.tictactoeOpponentUnknown);
|
||||
}
|
||||
final opponentKey6 = _key6Hex(opponent.publicKey);
|
||||
|
||||
@@ -69,7 +70,7 @@ class TicTacToeMessageBubble extends StatelessWidget {
|
||||
|
||||
start ??= event.type == TicTacToeEventType.start ? event : null;
|
||||
if (start == null) {
|
||||
return const Text('Tic-Tac-Toe: waiting for start');
|
||||
return Text(AppLocalizations.of(context)!.tictactoeWaitingForStart);
|
||||
}
|
||||
|
||||
final xPlayer = start.playerKey6;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
|
||||
/// Dialog that requests location permissions on app startup
|
||||
class PermissionRequestDialog extends StatefulWidget {
|
||||
@@ -109,8 +110,8 @@ class _PermissionRequestDialogState extends State<PermissionRequestDialog> {
|
||||
Icons.location_on,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Text('Location Permission'),
|
||||
SizedBox(width: 12),
|
||||
Text(AppLocalizations.of(context)!.locationPermission),
|
||||
],
|
||||
),
|
||||
content: Column(
|
||||
|
||||
@@ -210,7 +210,7 @@ class _SarTemplateEditDialogState extends State<SarTemplateEditDialog> {
|
||||
maxLines: 2,
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Color picker
|
||||
Text(
|
||||
@@ -305,7 +305,7 @@ class _SarTemplateEditDialogState extends State<SarTemplateEditDialog> {
|
||||
const SizedBox(width: 12),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _save,
|
||||
icon: const Icon(Icons.save),
|
||||
icon: Icon(Icons.save),
|
||||
label: Text(l10n.save),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1090,7 +1090,7 @@ class SensorTelemetryCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
if (state == SensorRefreshState.timeout)
|
||||
const _InlineAlertBadge(label: 'No response'),
|
||||
_InlineAlertBadge(label: l10n.noResponse),
|
||||
],
|
||||
),
|
||||
if (telemetry != null) ...[
|
||||
@@ -1107,8 +1107,8 @@ class SensorTelemetryCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
if (state == SensorRefreshState.refreshing)
|
||||
const _InlineStateMeta(
|
||||
label: 'Refreshing',
|
||||
_InlineStateMeta(
|
||||
label: l10n.refreshing,
|
||||
color: Color(0xFF266AC2),
|
||||
spinning: true,
|
||||
),
|
||||
@@ -1119,8 +1119,8 @@ class SensorTelemetryCard extends StatelessWidget {
|
||||
icon: Icons.check_circle,
|
||||
),
|
||||
if (state == SensorRefreshState.unavailable)
|
||||
const _InlineStateMeta(
|
||||
label: 'Unavailable',
|
||||
_InlineStateMeta(
|
||||
label: l10n.unavailable,
|
||||
color: Color(0xFFB13B55),
|
||||
icon: Icons.error_outline,
|
||||
),
|
||||
@@ -1153,17 +1153,17 @@ class SensorTelemetryCard extends StatelessWidget {
|
||||
}
|
||||
if (onCustomize != null) {
|
||||
items.add(
|
||||
const PopupMenuItem<String>(
|
||||
PopupMenuItem<String>(
|
||||
value: 'customize',
|
||||
child: Text('Customize fields'),
|
||||
child: Text(l10n.customizeFields),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (onRemove != null) {
|
||||
items.add(
|
||||
const PopupMenuItem<String>(
|
||||
PopupMenuItem<String>(
|
||||
value: 'remove',
|
||||
child: Text('Remove'),
|
||||
child: Text(l10n.remove),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,10 +54,10 @@ class UpdateDialog extends StatelessWidget {
|
||||
|
||||
// Optional: Build timestamp
|
||||
if (updateInfo.timestamp != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(height: 12),
|
||||
_buildInfoRow(
|
||||
context,
|
||||
label: 'Build Time',
|
||||
label: AppLocalizations.of(context)!.buildTime,
|
||||
value: _formatTimestamp(updateInfo.timestamp!),
|
||||
),
|
||||
],
|
||||
@@ -131,8 +131,8 @@ class UpdateDialog extends StatelessWidget {
|
||||
if (updateInfo.downloadUrl == null) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Download URL not available'),
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.downloadUrlNotAvailable),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
@@ -147,8 +147,8 @@ class UpdateDialog extends StatelessWidget {
|
||||
if (!canLaunch) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Cannot open download URL'),
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.cannotOpenDownloadUrl),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user