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(flutter precache:*)",
"Bash(flutter analyze:*)",
"Bash(flutter pub get:*)"
"Bash(flutter pub get:*)",
"Bash(dart:*)"
],
"deny": [],
"ask": []

View File

@@ -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;

View File

@@ -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 '🧑':

View File

@@ -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<void> _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) {

View File

@@ -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(

View File

@@ -323,7 +323,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
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,