feat: Add localization support for contact and SAR marker display names

This commit is contained in:
Janez T
2025-10-16 21:09:33 +02:00
parent b6283e1c8f
commit c6c56f858d
6 changed files with 49 additions and 10 deletions

View File

@@ -10,7 +10,8 @@
"Bash(dart pub global deactivate:*)", "Bash(dart pub global deactivate:*)",
"Bash(flutter precache:*)", "Bash(flutter precache:*)",
"Bash(flutter analyze:*)", "Bash(flutter analyze:*)",
"Bash(flutter pub get:*)" "Bash(flutter pub get:*)",
"Bash(dart:*)"
], ],
"deny": [], "deny": [],
"ask": [] "ask": []

View File

@@ -4,6 +4,7 @@ import 'package:latlong2/latlong.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'contact_telemetry.dart'; import 'contact_telemetry.dart';
import 'advert_location.dart'; import 'advert_location.dart';
import '../l10n/app_localizations.dart';
/// MeshCore contact types /// MeshCore contact types
enum ContactType { enum ContactType {
@@ -213,6 +214,16 @@ class Contact {
return advName.substring(emoji.length).trim(); 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 /// Check if contact has a learned routing path
/// When true, messages will use direct routing. When false, messages will use flood mode. /// When true, messages will use direct routing. When false, messages will use flood mode.
bool get hasPath => outPathLen > 0 && outPathLen <= 64; bool get hasPath => outPathLen > 0 && outPathLen <= 64;

View File

@@ -1,5 +1,7 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import '../l10n/app_localizations.dart';
/// SAR (Search & Rescue) marker types /// SAR (Search & Rescue) marker types
enum SarMarkerType { enum SarMarkerType {
@@ -13,6 +15,23 @@ enum SarMarkerType {
final String emoji; final String emoji;
final String displayName; 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) { static SarMarkerType fromEmoji(String emoji) {
switch (emoji) { switch (emoji) {
case '🧑': case '🧑':

View File

@@ -28,8 +28,13 @@ class ContactsProvider with ChangeNotifier {
excludePublicKey: devicePublicKey, excludePublicKey: devicePublicKey,
); );
// Add stored contacts // Add stored contacts (excluding any with all-zeros public key)
const publicChannelKey = '0000000000000000000000000000000000000000000000000000000000000000';
for (final contact in storedContacts) { 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; _contacts[contact.publicKeyHex] = contact;
} }
@@ -49,7 +54,8 @@ class ContactsProvider with ChangeNotifier {
/// Ensure public channel always exists in the list /// Ensure public channel always exists in the list
void _ensurePublicChannelExists() { 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)) { if (!_contacts.containsKey(publicChannelKey)) {
// Create a pseudo-contact for the public channel (ephemeral broadcast) // Create a pseudo-contact for the public channel (ephemeral broadcast)
_contacts[publicChannelKey] = Contact( _contacts[publicChannelKey] = Contact(
@@ -70,9 +76,11 @@ class ContactsProvider with ChangeNotifier {
/// Persist contacts to storage (async, non-blocking) /// Persist contacts to storage (async, non-blocking)
Future<void> _persistContacts() async { Future<void> _persistContacts() async {
try { try {
// Don't persist the public channel pseudo-contact // Don't persist the public channel pseudo-contact (all zeros key)
final contactsToSave = _contacts.values const publicChannelKey = '0000000000000000000000000000000000000000000000000000000000000000';
.where((c) => c.publicKeyHex != 'public_channel_0') final contactsToSave = _contacts.entries
.where((entry) => entry.key != publicChannelKey)
.map((entry) => entry.value)
.toList(); .toList();
await _storageService.saveContacts(contactsToSave); await _storageService.saveContacts(contactsToSave);
} catch (e) { } catch (e) {

View File

@@ -172,7 +172,7 @@ class MapMarkers {
borderRadius: BorderRadius.circular(3), borderRadius: BorderRadius.circular(3),
), ),
child: Text( child: Text(
marker.type.displayName, marker.type.getLocalizedName(context),
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 9, fontSize: 9,
@@ -254,7 +254,7 @@ class MapMarkers {
children: [ children: [
Text(marker.type.emoji, style: const TextStyle(fontSize: 24)), Text(marker.type.emoji, style: const TextStyle(fontSize: 24)),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded(child: Text(marker.type.displayName)), Expanded(child: Text(marker.type.getLocalizedName(context))),
], ],
), ),
content: Column( content: Column(

View File

@@ -323,7 +323,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Text( child: Text(
contact.displayName, contact.getLocalizedDisplayName(context),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
@@ -764,7 +764,7 @@ class MarkerTypeChip extends StatelessWidget {
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Text( child: Text(
type.displayName, type.getLocalizedName(context),
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,