Refactor localization in UI components and error messages

- Updated various UI components to utilize AppLocalizations for better internationalization support.
- Replaced hardcoded strings with localized strings in home_screen.dart, ble_connection_manager.dart, contact_tile.dart, room_login_sheet.dart, compass widgets, and sar_update_sheet.dart.
- Improved user feedback messages by integrating localization for actions like pinging contacts, displaying connection statuses, and error notifications.
- Enhanced the clarity of UI elements by ensuring all text is translatable, improving the overall user experience for non-English speakers.
This commit is contained in:
Janez T
2025-10-16 15:39:48 +02:00
parent 1056380f3a
commit 01a1bbec44
17 changed files with 2469 additions and 217 deletions

View File

@@ -154,7 +154,7 @@ class ContactTile extends StatelessWidget {
),
const SizedBox(width: 2),
Text(
contact.hasPath ? 'Direct' : 'Flood',
contact.hasPath ? AppLocalizations.of(context)!.direct : AppLocalizations.of(context)!.flood,
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w600,
@@ -189,7 +189,7 @@ class ContactTile extends StatelessWidget {
const Icon(Icons.admin_panel_settings, size: 10, color: Colors.red),
const SizedBox(width: 2),
Text(
'Admin',
AppLocalizations.of(context)!.admin,
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Colors.red,
fontWeight: FontWeight.bold,
@@ -213,7 +213,7 @@ class ContactTile extends StatelessWidget {
const Icon(Icons.check_circle, size: 10, color: Colors.green),
const SizedBox(width: 2),
Text(
'Logged In',
AppLocalizations.of(context)!.loggedIn,
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Colors.green,
fontWeight: FontWeight.bold,
@@ -263,7 +263,7 @@ class ContactTile extends StatelessWidget {
const Icon(Icons.sensors_off, size: 12, color: Colors.grey),
const SizedBox(width: 4),
Text(
'No GPS data',
AppLocalizations.of(context)!.noGpsData,
style: Theme.of(context).textTheme.labelSmall,
),
],
@@ -277,7 +277,7 @@ class ContactTile extends StatelessWidget {
const Icon(Icons.straighten, size: 12, color: Colors.blue),
const SizedBox(width: 4),
Text(
'Distance: $distanceText',
'${AppLocalizations.of(context)!.distance}: $distanceText',
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Colors.blue,
fontWeight: FontWeight.w500,
@@ -300,8 +300,8 @@ class ContactTile extends StatelessWidget {
ToastLogger.info(
context,
hasPath
? 'Pinging ${contact.displayName} (direct via path)...'
: 'Pinging ${contact.displayName} (flooding - no path)...',
? AppLocalizations.of(context)!.pingingDirect(contact.displayName)
: AppLocalizations.of(context)!.pingingFlood(contact.displayName),
);
// Use smart ping with automatic fallback
@@ -313,7 +313,7 @@ class ContactTile extends StatelessWidget {
if (context.mounted) {
ToastLogger.warning(
context,
'Direct ping timeout - retrying ${contact.displayName} with flooding...',
AppLocalizations.of(context)!.directPingTimeout(contact.displayName),
);
}
},
@@ -324,12 +324,15 @@ class ContactTile extends StatelessWidget {
if (result.success) {
ToastLogger.success(
context,
'Ping successful to ${contact.displayName}${result.retriedWithFlooding ? ' (via flooding fallback)' : ''}',
AppLocalizations.of(context)!.pingSuccessful(
contact.displayName,
result.retriedWithFlooding ? AppLocalizations.of(context)!.viaFloodingFallback : '',
),
);
} else {
ToastLogger.error(
context,
'Ping failed to ${contact.displayName} - no response received',
AppLocalizations.of(context)!.pingFailed(contact.displayName),
);
}
}
@@ -362,8 +365,7 @@ class ContactTile extends StatelessWidget {
builder: (context) => AlertDialog(
title: Text(AppLocalizations.of(context)!.deleteContact),
content: Text(
'Are you sure you want to delete "${contact.displayName}"?\n\n'
'This will remove the contact from both the app and the companion radio device.',
AppLocalizations.of(context)!.deleteContactConfirmation(contact.displayName),
),
actions: [
TextButton(
@@ -390,7 +392,7 @@ class ContactTile extends StatelessWidget {
try {
// Show loading toast
ToastLogger.info(context, 'Removing ${contact.displayName}...');
ToastLogger.info(context, AppLocalizations.of(context)!.removingContact(contact.displayName));
// Remove contact from provider (which will also remove from device)
await contactsProvider.removeContact(
@@ -403,11 +405,11 @@ class ContactTile extends StatelessWidget {
);
if (context.mounted) {
ToastLogger.success(context, 'Contact "${contact.displayName}" removed');
ToastLogger.success(context, AppLocalizations.of(context)!.contactRemoved(contact.displayName));
}
} catch (e) {
if (context.mounted) {
ToastLogger.error(context, 'Failed to remove contact: $e');
ToastLogger.error(context, AppLocalizations.of(context)!.failedToRemoveContact(e.toString()));
}
}
}
@@ -483,18 +485,18 @@ class ContactTile extends StatelessWidget {
controller: scrollController,
padding: const EdgeInsets.all(16),
children: [
_DetailRow('Type', contact.type.displayName),
_DetailRow(AppLocalizations.of(context)!.type, contact.type.displayName),
// Public Key with copy button
Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
SizedBox(
width: 100,
child: Text(
'Public Key:',
style: TextStyle(fontWeight: FontWeight.w500),
'${AppLocalizations.of(context)!.publicKey}:',
style: const TextStyle(fontWeight: FontWeight.w500),
),
),
Expanded(
@@ -524,40 +526,40 @@ class ContactTile extends StatelessWidget {
],
),
),
_DetailRow('Last Seen', contact.timeSinceLastSeen),
_DetailRow(AppLocalizations.of(context)!.lastSeen, contact.timeSinceLastSeen),
const SizedBox(height: 16),
// Room Login Status
if (roomLoginState != null) ...[
const Text(
'Room Status:',
style: TextStyle(
Text(
'${AppLocalizations.of(context)!.roomStatus}:',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const SizedBox(height: 8),
_DetailRow(
'Login Status',
roomLoginState.isLoggedIn ? 'Logged In' : 'Not Logged In',
AppLocalizations.of(context)!.loginStatus,
roomLoginState.isLoggedIn ? AppLocalizations.of(context)!.loggedIn : AppLocalizations.of(context)!.notLoggedIn,
),
if (roomLoginState.isLoggedIn) ...[
_DetailRow(
'Admin Access',
roomLoginState.isAdmin ? 'Yes' : 'No',
AppLocalizations.of(context)!.adminAccess,
roomLoginState.isAdmin ? AppLocalizations.of(context)!.yes : AppLocalizations.of(context)!.no,
),
_DetailRow(
'Permissions',
AppLocalizations.of(context)!.permissions,
roomLoginState.permissions.toString(),
),
if (roomLoginState.loginDurationFormatted != null)
_DetailRow(
'Logged In',
AppLocalizations.of(context)!.loggedIn,
roomLoginState.loginDurationFormatted!,
),
],
_DetailRow(
'Password Saved',
roomLoginState.hasPassword ? 'Yes' : 'No',
AppLocalizations.of(context)!.passwordSaved,
roomLoginState.hasPassword ? AppLocalizations.of(context)!.yes : AppLocalizations.of(context)!.no,
),
const SizedBox(height: 16),
],
@@ -565,9 +567,9 @@ class ContactTile extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Location:',
style: TextStyle(
Text(
AppLocalizations.of(context)!.locationColon,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
@@ -632,9 +634,9 @@ class ContactTile extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Telemetry:',
style: TextStyle(
Text(
'${AppLocalizations.of(context)!.telemetry}:',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
@@ -643,7 +645,7 @@ class ContactTile extends StatelessWidget {
onPressed: () {
final connectionProvider = context.read<ConnectionProvider>();
connectionProvider.requestTelemetry(contact.publicKey, zeroHop: true);
ToastLogger.info(context, 'Requesting telemetry from ${contact.displayName}...');
ToastLogger.info(context, AppLocalizations.of(context)!.requestingTelemetry(contact.displayName));
},
icon: const Icon(Icons.refresh, size: 18),
label: Text(AppLocalizations.of(context)!.refresh),
@@ -656,25 +658,25 @@ class ContactTile extends StatelessWidget {
const SizedBox(height: 8),
if (contact.telemetry!.batteryMilliVolts != null)
_DetailRow(
'Voltage',
AppLocalizations.of(context)!.voltage,
'${(contact.telemetry!.batteryMilliVolts! / 1000).toStringAsFixed(3)}V'
'${contact.telemetry!.batteryPercentage != null ? ' (${contact.telemetry!.batteryPercentage!.toStringAsFixed(1)}%)' : ''}',
)
else if (contact.telemetry!.batteryPercentage != null)
_DetailRow('Battery', '${contact.telemetry!.batteryPercentage!.toStringAsFixed(1)}%'),
_DetailRow(AppLocalizations.of(context)!.battery, '${contact.telemetry!.batteryPercentage!.toStringAsFixed(1)}%'),
if (contact.telemetry!.temperature != null)
_DetailRow('Temperature', '${contact.telemetry!.temperature!.toStringAsFixed(1)}°C'),
_DetailRow(AppLocalizations.of(context)!.temperature, '${contact.telemetry!.temperature!.toStringAsFixed(1)}°C'),
if (contact.telemetry!.humidity != null)
_DetailRow('Humidity', '${contact.telemetry!.humidity!.toStringAsFixed(1)}%'),
_DetailRow(AppLocalizations.of(context)!.humidity, '${contact.telemetry!.humidity!.toStringAsFixed(1)}%'),
if (contact.telemetry!.pressure != null)
_DetailRow('Pressure', '${contact.telemetry!.pressure!.toStringAsFixed(1)} hPa'),
_DetailRow(AppLocalizations.of(context)!.pressure, '${contact.telemetry!.pressure!.toStringAsFixed(1)} hPa'),
if (contact.telemetry!.gpsLocation != null)
_DetailRow(
'GPS (Telemetry)',
AppLocalizations.of(context)!.gpsTelemetry,
'${contact.telemetry!.gpsLocation!.latitude.toStringAsFixed(6)}, ${contact.telemetry!.gpsLocation!.longitude.toStringAsFixed(6)}',
),
_DetailRow(
'Updated',
AppLocalizations.of(context)!.updated,
'${_formatTimestamp(contact.telemetry!.timestamp)} (${_formatTimeAgo(contact.telemetry!.timestamp)})',
),
],
@@ -703,7 +705,7 @@ class ContactTile extends StatelessWidget {
child: OutlinedButton.icon(
onPressed: () {
connectionProvider.resetPath(contact.publicKey);
ToastLogger.info(context, 'Path reset for ${contact.displayName}. Next message will find a new route.');
ToastLogger.info(context, AppLocalizations.of(context)!.pathResetInfo(contact.displayName));
},
icon: const Icon(Icons.route),
label: Text(AppLocalizations.of(context)!.resetPath),
@@ -726,7 +728,7 @@ class ContactTile extends StatelessWidget {
_showRoomLoginDialog(context, contact);
},
icon: const Icon(Icons.login),
label: Text(roomLoginState?.isLoggedIn == true ? 'Re-Login to Room' : 'Login to Room'),
label: Text(roomLoginState?.isLoggedIn == true ? AppLocalizations.of(context)!.reLoginToRoom : AppLocalizations.of(context)!.loginToRoom),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor: _getTypeColor(contact.type, context),

View File

@@ -151,11 +151,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Failed to add room to device: $e\n\n'
'The room may not have advertised yet.\n'
'Try waiting for the room to broadcast.',
),
content: Text(AppLocalizations.of(context)!.failedToAddRoom(e.toString())),
backgroundColor: Theme.of(context).colorScheme.error,
duration: const Duration(seconds: 7),
),
@@ -316,7 +312,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Login to Room',
AppLocalizations.of(context)!.loginToRoom,
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 18,
@@ -358,7 +354,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
const SizedBox(width: 12),
Expanded(
child: Text(
'Enter the password to access this room. The password will be saved for future use.',
AppLocalizations.of(context)!.enterPasswordInfo,
style: TextStyle(
color: colorScheme.onPrimaryContainer,
fontSize: 12,
@@ -392,9 +388,9 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
maxLengthEnforcement: MaxLengthEnforcement.enforced,
style: TextStyle(color: colorScheme.onSurface),
decoration: InputDecoration(
labelText: 'Password',
labelText: AppLocalizations.of(context)!.password,
labelStyle: TextStyle(color: colorScheme.onSurfaceVariant),
hintText: 'Enter room password',
hintText: AppLocalizations.of(context)!.enterRoomPassword,
hintStyle: TextStyle(color: colorScheme.onSurfaceVariant),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
@@ -436,7 +432,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.login),
label: Text(_isLoggingIn ? 'Logging in...' : 'Login'),
label: Text(_isLoggingIn ? AppLocalizations.of(context)!.loggingInDots : AppLocalizations.of(context)!.login),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
),

View File

@@ -1,6 +1,7 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../../../models/contact.dart';
/// Contact list section for the compass dialog.
@@ -28,7 +29,7 @@ class CompassContactList extends StatelessWidget {
}
if (position == null) {
return const Text('Location unavailable');
return Text(AppLocalizations.of(context)!.locationUnavailable);
}
// Calculate bearings and distances
@@ -60,13 +61,14 @@ class CompassContactList extends StatelessWidget {
contactsWithBearing.sort((a, b) =>
(a['distance'] as double).compareTo(b['distance'] as double));
final l10n = AppLocalizations.of(context)!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 8),
child: Text(
'Nearby Contacts',
l10n.nearbyContacts,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
@@ -120,7 +122,7 @@ class CompassContactList extends StatelessWidget {
),
if (heading != null)
Text(
_formatRelativeBearing(bearing, heading!),
_formatRelativeBearing(bearing, heading!, context),
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Colors.grey,
),
@@ -189,7 +191,8 @@ class CompassContactList extends StatelessWidget {
}
}
String _formatRelativeBearing(double bearing, double heading) {
String _formatRelativeBearing(double bearing, double heading, BuildContext context) {
final l10n = AppLocalizations.of(context)!;
// Calculate relative bearing (how much to turn from current heading)
double relative = bearing - heading;
@@ -204,11 +207,11 @@ class CompassContactList extends StatelessWidget {
final absRelative = relative.abs().round();
if (absRelative < 10) {
return 'ahead';
return l10n.ahead;
} else if (relative > 0) {
return '$absRelative° right';
return l10n.degreesRight(absRelative);
} else {
return '$absRelative° left';
return l10n.degreesLeft(absRelative);
}
}
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.dart';
/// Filter controls for the compass dialog.
/// Allows filtering of contacts and SAR marker types.
@@ -32,15 +33,16 @@ class CompassFilters extends StatefulWidget {
class _CompassFiltersState extends State<CompassFilters> {
void _showFilterDialog() {
final l10n = AppLocalizations.of(context)!;
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setDialogState) => AlertDialog(
title: const Row(
title: Row(
children: [
Icon(Icons.filter_list, size: 20),
SizedBox(width: 8),
Text('Filter Markers'),
const Icon(Icons.filter_list, size: 20),
const SizedBox(width: 8),
Text(l10n.filterMarkers),
],
),
contentPadding: const EdgeInsets.fromLTRB(24, 16, 24, 0),
@@ -52,7 +54,7 @@ class _CompassFiltersState extends State<CompassFilters> {
_CompactFilterItem(
icon: Icons.person,
color: Theme.of(context).colorScheme.primary,
label: 'Contacts',
label: l10n.contactsFilter,
value: widget.showContacts,
onChanged: (value) {
widget.onShowContactsChanged(value);
@@ -66,7 +68,7 @@ class _CompassFiltersState extends State<CompassFilters> {
Padding(
padding: const EdgeInsets.only(left: 8, bottom: 8, top: 4),
child: Text(
'SAR Markers',
l10n.sarMarkers,
style: Theme.of(context).textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.bold,
),
@@ -75,7 +77,7 @@ class _CompassFiltersState extends State<CompassFilters> {
_CompactFilterItem(
icon: Icons.person_pin,
color: Colors.green,
label: 'Found Person',
label: l10n.foundPerson,
value: widget.showFoundPerson,
onChanged: (value) {
widget.onShowFoundPersonChanged(value);
@@ -86,7 +88,7 @@ class _CompassFiltersState extends State<CompassFilters> {
_CompactFilterItem(
icon: Icons.local_fire_department,
color: Colors.red,
label: 'Fire',
label: l10n.fire,
value: widget.showFire,
onChanged: (value) {
widget.onShowFireChanged(value);
@@ -97,7 +99,7 @@ class _CompassFiltersState extends State<CompassFilters> {
_CompactFilterItem(
icon: Icons.home_work,
color: Colors.orange,
label: 'Staging Area',
label: l10n.stagingArea,
value: widget.showStagingArea,
onChanged: (value) {
widget.onShowStagingAreaChanged(value);
@@ -112,11 +114,11 @@ class _CompassFiltersState extends State<CompassFilters> {
widget.onShowAll();
setDialogState(() {});
},
child: const Text('Show All'),
child: Text(l10n.showAll),
),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
child: Text(l10n.close),
),
],
),
@@ -126,9 +128,10 @@ class _CompassFiltersState extends State<CompassFilters> {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return IconButton(
icon: const Icon(Icons.filter_list),
tooltip: 'Filter markers',
tooltip: l10n.filterMarkersTooltip,
onPressed: () => _showFilterDialog(),
);
}

View File

@@ -3,6 +3,7 @@ import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:latlong2/latlong.dart';
import '../../../l10n/app_localizations.dart';
import '../../../models/contact.dart';
import '../../../models/sar_marker.dart';
@@ -76,18 +77,19 @@ class CompassHeader extends StatelessWidget {
}
Widget _buildInfoRow(BuildContext context, double? heading, Position? position) {
final l10n = AppLocalizations.of(context)!;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildInfoCard(
context,
'Heading',
l10n.heading,
heading != null ? '${heading.round()}°' : '--',
Icons.explore,
),
_buildInfoCard(
context,
'Elevation',
l10n.elevation,
position?.altitude != null
? '${position!.altitude.round()}m'
: '--',
@@ -95,7 +97,7 @@ class CompassHeader extends StatelessWidget {
),
_buildInfoCard(
context,
'Accuracy',
l10n.accuracy,
position?.accuracy != null
? '±${position!.accuracy.round()}m'
: '--',
@@ -566,12 +568,16 @@ class _LocationFormatToggleState extends State<_LocationFormatToggle> {
return const SizedBox.shrink();
}
final l10n = AppLocalizations.of(context)!;
final String displayText;
if (_showDMS) {
displayText = '${_formatDMS(position.latitude, true)} ${_formatDMS(position.longitude, false)}';
} else {
displayText = 'Lat: ${position.latitude.toStringAsFixed(5)} Lon: ${position.longitude.toStringAsFixed(5)}';
displayText = l10n.latLonFormat(
position.latitude.toStringAsFixed(5),
position.longitude.toStringAsFixed(5),
);
}
return GestureDetector(

View File

@@ -1,6 +1,7 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../../../models/sar_marker.dart';
/// SAR marker list section for the compass dialog.
@@ -28,7 +29,7 @@ class CompassSarList extends StatelessWidget {
}
if (position == null) {
return const Text('Location unavailable');
return Text(AppLocalizations.of(context)!.locationUnavailable);
}
// Calculate bearings and distances for SAR markers
@@ -58,13 +59,14 @@ class CompassSarList extends StatelessWidget {
markersWithBearing.sort((a, b) =>
(a['distance'] as double).compareTo(b['distance'] as double));
final l10n = AppLocalizations.of(context)!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 16, top: 16, bottom: 8),
child: Text(
'SAR Markers',
l10n.sarMarkers,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
@@ -139,7 +141,7 @@ class CompassSarList extends StatelessWidget {
),
if (heading != null)
Text(
_formatRelativeBearing(bearing, heading!),
_formatRelativeBearing(bearing, heading!, context),
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Colors.grey,
),
@@ -208,7 +210,8 @@ class CompassSarList extends StatelessWidget {
}
}
String _formatRelativeBearing(double bearing, double heading) {
String _formatRelativeBearing(double bearing, double heading, BuildContext context) {
final l10n = AppLocalizations.of(context)!;
// Calculate relative bearing (how much to turn from current heading)
double relative = bearing - heading;
@@ -223,11 +226,11 @@ class CompassSarList extends StatelessWidget {
final absRelative = relative.abs().round();
if (absRelative < 10) {
return 'ahead';
return l10n.ahead;
} else if (relative > 0) {
return '$absRelative° right';
return l10n.degreesRight(absRelative);
} else {
return '$absRelative° left';
return l10n.degreesLeft(absRelative);
}
}
}

View File

@@ -176,7 +176,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
),
),
Text(
'Quick location marker',
AppLocalizations.of(context)!.quickLocationMarker,
style: TextStyle(
color: colorScheme.onSurfaceVariant,
fontSize: 14,
@@ -203,7 +203,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
children: [
// Marker type selection
Text(
'Marker Type',
AppLocalizations.of(context)!.markerType,
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 16,
@@ -238,7 +238,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
// Destination selection (compact dropdown with rooms and channel)
Text(
'Send To',
AppLocalizations.of(context)!.sendTo,
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 16,
@@ -262,14 +262,14 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
width: 1,
),
),
child: const Row(
child: Row(
children: [
Icon(Icons.error_outline, color: Colors.red, size: 20),
SizedBox(width: 8),
const Icon(Icons.error_outline, color: Colors.red, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
'No destinations available.',
style: TextStyle(
AppLocalizations.of(context)!.noDestinationsAvailable,
style: const TextStyle(
color: Colors.white70,
fontSize: 11,
),
@@ -298,7 +298,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
Icon(Icons.arrow_drop_down_circle, size: 18, color: colorScheme.onSurfaceVariant),
const SizedBox(width: 12),
Text(
'Select destination...',
AppLocalizations.of(context)!.selectDestination,
style: TextStyle(color: colorScheme.onSurfaceVariant),
),
],
@@ -373,8 +373,8 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
Expanded(
child: Text(
_selectedContact!.isChannel
? 'Ephemeral: Broadcast over-the-air only. Not stored - nodes must be online.'
: 'Persistent: Stored immutably in room. Synced automatically and preserved offline.',
? AppLocalizations.of(context)!.ephemeralBroadcastInfo
: AppLocalizations.of(context)!.persistentRoomInfo,
style: const TextStyle(
color: Colors.white70,
fontSize: 11,
@@ -390,7 +390,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
Row(
children: [
Text(
'Location',
AppLocalizations.of(context)!.location,
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 16,
@@ -409,9 +409,9 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
width: 1,
),
),
child: const Text(
'From Map',
style: TextStyle(
child: Text(
AppLocalizations.of(context)!.fromMap,
style: const TextStyle(
color: Colors.blue,
fontSize: 11,
fontWeight: FontWeight.bold,
@@ -441,7 +441,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
),
const SizedBox(width: 16),
Text(
'Getting location...',
AppLocalizations.of(context)!.gettingLocation,
style: TextStyle(color: colorScheme.onSurface),
),
],
@@ -466,9 +466,9 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Location Error',
style: TextStyle(
Text(
AppLocalizations.of(context)!.locationError,
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 13,
@@ -488,7 +488,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
IconButton(
icon: const Icon(Icons.refresh, color: Colors.red),
onPressed: _getCurrentLocation,
tooltip: 'Retry',
tooltip: AppLocalizations.of(context)!.retry,
),
],
),
@@ -529,7 +529,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
onPressed: _getCurrentLocation,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
tooltip: 'Refresh location',
tooltip: AppLocalizations.of(context)!.refreshLocation,
),
],
),
@@ -544,7 +544,9 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
),
const SizedBox(width: 6),
Text(
'Accuracy: ±${_currentPosition!.accuracy!.round()}m',
AppLocalizations.of(context)!.accuracyMeters(
_currentPosition!.accuracy!.round(),
),
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
@@ -560,7 +562,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
// Optional notes
Text(
'Notes (optional)',
AppLocalizations.of(context)!.notesOptional,
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 16,
@@ -574,7 +576,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
maxLength: 100,
style: TextStyle(fontSize: 14, color: colorScheme.onSurface),
decoration: InputDecoration(
hintText: 'Add additional information...',
hintText: AppLocalizations.of(context)!.addAdditionalInformation,
hintStyle: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
filled: true,
fillColor: colorScheme.surfaceContainerHighest,
@@ -648,9 +650,9 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
builder: (context) => AlertDialog(
title: Text(AppLocalizations.of(context)!.lowLocationAccuracy),
content: Text(
'Location accuracy is ±${_currentPosition!.accuracy!.round()}m. '
'This may not be accurate enough for SAR operations.\n\n'
'Continue anyway?',
AppLocalizations.of(context)!.lowAccuracyWarning(
_currentPosition!.accuracy!.round(),
),
),
actions: [
TextButton(