diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 89cf28f..94c22bd 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,8 @@ "Bash(dart pub global deactivate:*)", "Bash(flutter precache:*)", "Bash(flutter analyze:*)", - "Bash(flutter pub get:*)" + "Bash(flutter pub get:*)", + "Bash(dart:*)" ], "deny": [], "ask": [] diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 3f94a42..0fe3d7c 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -4,6 +4,7 @@ import 'package:latlong2/latlong.dart'; import 'package:flutter/material.dart'; import 'contact_telemetry.dart'; import 'advert_location.dart'; +import '../l10n/app_localizations.dart'; /// MeshCore contact types enum ContactType { @@ -213,6 +214,16 @@ class Contact { return advName.substring(emoji.length).trim(); } + /// Get localized display name (for Public Channel and other special contacts) + String getLocalizedDisplayName(BuildContext context) { + // Check if this is the Public Channel (all-zeros public key) + if (publicKeyHex == '0000000000000000000000000000000000000000000000000000000000000000') { + return AppLocalizations.of(context)!.publicChannel; + } + // For all other contacts, use the regular display name + return displayName; + } + /// Check if contact has a learned routing path /// When true, messages will use direct routing. When false, messages will use flood mode. bool get hasPath => outPathLen > 0 && outPathLen <= 64; diff --git a/lib/models/sar_marker.dart b/lib/models/sar_marker.dart index c539d4b..61e5743 100644 --- a/lib/models/sar_marker.dart +++ b/lib/models/sar_marker.dart @@ -1,5 +1,7 @@ import 'dart:typed_data'; +import 'package:flutter/material.dart'; import 'package:latlong2/latlong.dart'; +import '../l10n/app_localizations.dart'; /// SAR (Search & Rescue) marker types enum SarMarkerType { @@ -13,6 +15,23 @@ enum SarMarkerType { final String emoji; final String displayName; + /// Get localized display name + String getLocalizedName(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + switch (this) { + case SarMarkerType.foundPerson: + return l10n.sarMarkerFoundPerson; + case SarMarkerType.fire: + return l10n.sarMarkerFire; + case SarMarkerType.stagingArea: + return l10n.sarMarkerStagingArea; + case SarMarkerType.object: + return l10n.sarMarkerObject; + case SarMarkerType.unknown: + return 'Unknown'; + } + } + static SarMarkerType fromEmoji(String emoji) { switch (emoji) { case '🧑': diff --git a/lib/providers/contacts_provider.dart b/lib/providers/contacts_provider.dart index abaf7c1..6add3a9 100644 --- a/lib/providers/contacts_provider.dart +++ b/lib/providers/contacts_provider.dart @@ -28,8 +28,13 @@ class ContactsProvider with ChangeNotifier { excludePublicKey: devicePublicKey, ); - // Add stored contacts + // Add stored contacts (excluding any with all-zeros public key) + const publicChannelKey = '0000000000000000000000000000000000000000000000000000000000000000'; for (final contact in storedContacts) { + // Skip any contacts with all-zeros public key (shouldn't happen, but safety check) + if (contact.publicKeyHex == publicChannelKey) { + continue; + } _contacts[contact.publicKeyHex] = contact; } @@ -49,7 +54,8 @@ class ContactsProvider with ChangeNotifier { /// Ensure public channel always exists in the list void _ensurePublicChannelExists() { - const publicChannelKey = 'public_channel_0'; + // Public channel has all-zeros public key (32 bytes = 64 hex chars) + const publicChannelKey = '0000000000000000000000000000000000000000000000000000000000000000'; if (!_contacts.containsKey(publicChannelKey)) { // Create a pseudo-contact for the public channel (ephemeral broadcast) _contacts[publicChannelKey] = Contact( @@ -70,9 +76,11 @@ class ContactsProvider with ChangeNotifier { /// Persist contacts to storage (async, non-blocking) Future _persistContacts() async { try { - // Don't persist the public channel pseudo-contact - final contactsToSave = _contacts.values - .where((c) => c.publicKeyHex != 'public_channel_0') + // Don't persist the public channel pseudo-contact (all zeros key) + const publicChannelKey = '0000000000000000000000000000000000000000000000000000000000000000'; + final contactsToSave = _contacts.entries + .where((entry) => entry.key != publicChannelKey) + .map((entry) => entry.value) .toList(); await _storageService.saveContacts(contactsToSave); } catch (e) { diff --git a/lib/widgets/map_markers.dart b/lib/widgets/map_markers.dart index efe1b56..cfece21 100644 --- a/lib/widgets/map_markers.dart +++ b/lib/widgets/map_markers.dart @@ -172,7 +172,7 @@ class MapMarkers { borderRadius: BorderRadius.circular(3), ), child: Text( - marker.type.displayName, + marker.type.getLocalizedName(context), style: const TextStyle( color: Colors.white, fontSize: 9, @@ -254,7 +254,7 @@ class MapMarkers { children: [ Text(marker.type.emoji, style: const TextStyle(fontSize: 24)), const SizedBox(width: 8), - Expanded(child: Text(marker.type.displayName)), + Expanded(child: Text(marker.type.getLocalizedName(context))), ], ), content: Column( diff --git a/lib/widgets/messages/sar_update_sheet.dart b/lib/widgets/messages/sar_update_sheet.dart index 01071ae..1d8515b 100644 --- a/lib/widgets/messages/sar_update_sheet.dart +++ b/lib/widgets/messages/sar_update_sheet.dart @@ -323,7 +323,7 @@ class _SarUpdateSheetState extends State { const SizedBox(width: 12), Expanded( child: Text( - contact.displayName, + contact.getLocalizedDisplayName(context), overflow: TextOverflow.ellipsis, ), ), @@ -764,7 +764,7 @@ class MarkerTypeChip extends StatelessWidget { const SizedBox(width: 16), Expanded( child: Text( - type.displayName, + type.getLocalizedName(context), style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500,