mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
feat: Add localization support for contact and SAR marker display names
This commit is contained in:
@@ -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": []
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 '🧑':
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user