Refactor: move Contact UI labels to l10n extension; rename raw getter to typeLabelRaw

This commit is contained in:
Serge Tarkovski
2026-04-25 00:29:20 +03:00
parent 6ae3f612ae
commit b7d0db8d1c
14 changed files with 74 additions and 39 deletions
+4 -4
View File
@@ -3997,7 +3997,7 @@ class MeshCoreConnector extends ChangeNotifier {
);
} else {
appLogger.info(
"Discovered contact ${contact.name} (type ${contact.typeLabel}) not added due to auto-add settings",
"Discovered contact ${contact.name} (type ${contact.typeLabelRaw}) not added due to auto-add settings",
tag: 'Connector',
);
return;
@@ -4019,7 +4019,7 @@ class MeshCoreConnector extends ChangeNotifier {
if (settings.notificationsEnabled && settings.notifyOnNewAdvert) {
_notificationService.showAdvertNotification(
contactName: contact.name,
contactType: contact.typeLabel,
contactType: contact.typeLabelRaw,
contactId: contact.publicKeyHex,
);
}
@@ -4094,7 +4094,7 @@ class MeshCoreConnector extends ChangeNotifier {
if (settings.notificationsEnabled && settings.notifyOnNewAdvert) {
_notificationService.showAdvertNotification(
contactName: contact.name,
contactType: contact.typeLabel,
contactType: contact.typeLabelRaw,
contactId: contact.publicKeyHex,
);
}
@@ -6025,7 +6025,7 @@ class MeshCoreConnector extends ChangeNotifier {
if (settings.notificationsEnabled && settings.notifyOnNewAdvert) {
_notificationService.showAdvertNotification(
contactName: contact.name,
contactType: contact.typeLabel,
contactType: contact.typeLabelRaw,
contactId: contact.publicKeyHex,
);
}
+36
View File
@@ -0,0 +1,36 @@
import '../connector/meshcore_protocol.dart';
import '../models/contact.dart';
import 'app_localizations.dart';
/// UI-level localization helpers for [Contact].
///
/// Kept out of the model layer so `Contact` does not depend on
/// `AppLocalizations`. Use these from widgets/screens; for logs and
/// non-UI export use `Contact.typeLabelRaw`.
extension ContactLocalization on Contact {
String typeLabel(AppLocalizations l10n) {
switch (type) {
case advTypeChat:
return l10n.contact_typeChat;
case advTypeRepeater:
return l10n.contact_typeRepeater;
case advTypeRoom:
return l10n.contact_typeRoom;
case advTypeSensor:
return l10n.contact_typeSensor;
default:
return l10n.contact_typeUnknown;
}
}
String pathLabel(AppLocalizations l10n) {
if (pathOverride != null) {
if (pathOverride! < 0) return l10n.chat_floodForced;
if (pathOverride == 0) return l10n.chat_directForced;
return l10n.chat_hopsForced(pathOverride!);
}
if (pathLength < 0) return l10n.channelPath_floodPath;
if (pathLength == 0) return l10n.chat_direct;
return l10n.chat_hopsCount(pathLength);
}
}
+3
View File
@@ -59,6 +59,9 @@ void main() async {
final notificationService = NotificationService();
await notificationService.initialize();
await backgroundService.initialize();
backgroundService.setLanguageOverrideProvider(
() => appSettingsService.settings.languageOverride,
);
_registerThirdPartyLicenses();
await chatTextScaleService.initialize();
+4 -28
View File
@@ -2,7 +2,6 @@ import 'dart:typed_data';
import 'package:meshcore_open/utils/app_logger.dart';
import '../connector/meshcore_protocol.dart';
import '../l10n/app_localizations.dart';
class Contact {
final Uint8List publicKey;
@@ -42,7 +41,10 @@ class Contact {
String get publicKeyHex => pubKeyToHex(publicKey);
String get typeLabel {
/// Non-localized type label, intended for logs and non-UI exports
/// (e.g. GPX). For UI use the `typeLabel(l10n)` extension in
/// `lib/l10n/contact_localization.dart`.
String get typeLabelRaw {
switch (type) {
case advTypeChat:
return 'Chat';
@@ -57,32 +59,6 @@ class Contact {
}
}
String typeLabelLocalized(AppLocalizations l10n) {
switch (type) {
case advTypeChat:
return l10n.contact_typeChat;
case advTypeRepeater:
return l10n.contact_typeRepeater;
case advTypeRoom:
return l10n.contact_typeRoom;
case advTypeSensor:
return l10n.contact_typeSensor;
default:
return l10n.contact_typeUnknown;
}
}
String pathLabel(AppLocalizations l10n) {
if (pathOverride != null) {
if (pathOverride! < 0) return l10n.chat_floodForced;
if (pathOverride == 0) return l10n.chat_directForced;
return l10n.chat_hopsForced(pathOverride!);
}
if (pathLength < 0) return l10n.channelPath_floodPath;
if (pathLength == 0) return l10n.chat_direct;
return l10n.chat_hopsCount(pathLength);
}
bool get hasLocation {
const double epsilon = 1e-6;
final lat = latitude ?? 0.0;
+2 -1
View File
@@ -20,6 +20,7 @@ import '../helpers/gif_helper.dart';
import '../helpers/path_helper.dart';
import '../models/channel_message.dart';
import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../models/message.dart';
import '../models/path_history.dart';
import '../models/translation_support.dart';
@@ -1168,7 +1169,7 @@ class _ChatScreenState extends State<ChatScreen> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(context.l10n.chat_type, contact.typeLabelLocalized(context.l10n)),
_buildInfoRow(context.l10n.chat_type, contact.typeLabel(context.l10n)),
_buildInfoRow(context.l10n.chat_path, contact.pathLabel(context.l10n)),
_buildInfoRow(
context.l10n.contact_lastSeen,
+2 -1
View File
@@ -13,6 +13,7 @@ import '../connector/meshcore_connector.dart';
import '../l10n/l10n.dart';
import '../connector/meshcore_protocol.dart';
import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../models/contact_group.dart';
import '../services/ui_view_state_service.dart';
import '../utils/contact_search.dart';
@@ -1123,7 +1124,7 @@ class _ContactsScreenState extends State<ContactsScreen>
value: isSelected,
title: Text(contact.name),
subtitle: Text(
contact.typeLabelLocalized(context.l10n),
contact.typeLabel(context.l10n),
),
onChanged: (value) {
setDialogState(() {
+2 -1
View File
@@ -16,6 +16,7 @@ import '../connector/meshcore_protocol.dart';
import '../models/app_settings.dart';
import '../models/channel.dart';
import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../services/app_settings_service.dart';
import '../services/path_history_service.dart';
import '../services/map_marker_service.dart';
@@ -1425,7 +1426,7 @@ class _MapScreenState extends State<MapScreen> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(context.l10n.map_type, contact.typeLabelLocalized(context.l10n)),
_buildInfoRow(context.l10n.map_type, contact.typeLabel(context.l10n)),
_buildInfoRow(context.l10n.map_path, contact.pathLabel(context.l10n)),
if (contact.hasLocation)
_buildInfoRow(
+1
View File
@@ -3,6 +3,7 @@ import 'package:meshcore_open/connector/meshcore_protocol.dart';
import 'package:provider/provider.dart';
import '../l10n/l10n.dart';
import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../services/app_settings_service.dart';
import 'repeater_status_screen.dart';
import 'repeater_cli_screen.dart';
+12
View File
@@ -6,6 +6,14 @@ import '../utils/platform_info.dart';
class BackgroundService {
bool _initialized = false;
String? Function()? _languageOverrideProvider;
/// Allows the app to expose its current language override (e.g. from
/// AppSettingsService) so the foreground notification matches the app UI
/// language instead of only the system locale.
void setLanguageOverrideProvider(String? Function()? provider) {
_languageOverrideProvider = provider;
}
Future<void> initialize() async {
if (!PlatformInfo.isAndroid || _initialized) return;
@@ -47,6 +55,10 @@ class BackgroundService {
Future<AppLocalizations> _loadLocalizations() async {
final supported = AppLocalizations.supportedLocales;
final override = _languageOverrideProvider?.call();
if (override != null && override.isNotEmpty) {
return AppLocalizations.delegate.load(Locale(override));
}
final system =
WidgetsBinding.instance.platformDispatcher.locale;
final match = basicLocaleListResolution(
+3 -3
View File
@@ -72,7 +72,7 @@ class GpxExport {
contact.name,
contact.latitude!,
contact.longitude!,
"Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}",
"Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}",
url,
);
}
@@ -91,7 +91,7 @@ class GpxExport {
contact.name,
contact.latitude!,
contact.longitude!,
"Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}",
"Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}",
url,
);
}
@@ -110,7 +110,7 @@ class GpxExport {
contact.name,
contact.latitude ?? 0.0,
contact.longitude ?? 0.0,
"Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}",
"Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}",
url,
);
}
+1
View File
@@ -9,6 +9,7 @@ import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart';
import '../l10n/l10n.dart';
import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../helpers/path_helper.dart';
import '../services/path_history_service.dart';
import '../helpers/snack_bar_builder.dart';
+2 -1
View File
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:meshcore_open/connector/meshcore_protocol.dart';
import '../l10n/l10n.dart';
import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../helpers/snack_bar_builder.dart';
class PathSelectionDialog extends StatefulWidget {
@@ -311,7 +312,7 @@ class _PathSelectionDialogState extends State<PathSelectionDialog> {
style: const TextStyle(fontSize: 14),
),
subtitle: Text(
'${contact.typeLabelLocalized(l10n)}${contact.publicKeyHex.substring(0, 2)}',
'${contact.typeLabel(l10n)}${contact.publicKeyHex.substring(0, 2)}',
style: const TextStyle(fontSize: 10),
),
trailing: isSelected
+1
View File
@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:provider/provider.dart';
import '../l10n/l10n.dart';
import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../services/storage_service.dart';
import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart';
+1
View File
@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:provider/provider.dart';
import '../l10n/l10n.dart';
import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../services/storage_service.dart';
import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart';