Compare commits

...

37 Commits

Author SHA1 Message Date
Enot (ded) Skelly 00636c9084 rename ble debug log
companion protocol so changed to companion and ref:
BLE/TCP/USB
2026-05-05 15:25:32 -07:00
zjs81 67238468ce Merge pull request #423 from zjs81/offgrid-CR
increase CR for off grid
2026-05-05 15:14:00 -07:00
Enot (ded) Skelly ae32e76563 fix someones formatting 2026-05-05 11:07:32 -07:00
Enot (ded) Skelly 5572c9ee75 increase CR for off grid
default should be higher assuming trees etc
2026-05-05 10:59:36 -07:00
zjs81 f1d93bd5e8 Merge pull request #399 from zjs81/contacts-sync
fix issues with contact sync
2026-04-30 15:41:25 -07:00
zjs81 eb597b6c68 Merge pull request #416 from zjs81/dev-DesktopMapControls
Add desktop map controls
2026-04-29 12:32:36 -07:00
zjs81 efe21c4e87 Merge pull request #417 from ericszimmermann/ez_latin_heuristics2
latin languages heuristics
2026-04-29 12:31:23 -07:00
ZIER 38fece3313 replace pattern with String. 2026-04-29 11:51:50 +02:00
ZIER 3af3cce606 latin languages heuristics 2026-04-29 11:04:36 +02:00
Ded 026ec6f7de bump app protocol version as we support v4+ features (#398) 2026-04-28 22:35:48 -07:00
Winston Lowe eb50249b93 Add desktop map controls and improve zoom functionality across multiple screens 2026-04-28 19:26:51 -07:00
zjs81 99c0ab7e22 Merge pull request #404 from pioneer/ukrainian-translations
Ukrainian translation polished + localized hardcoded strings
2026-04-27 13:24:43 -07:00
zjs81 2950a9a687 Merge branch 'dev' into pr-404-merge 2026-04-27 13:23:53 -07:00
zjs81 1b3de54873 Merge pull request #412 from just-stuff-tm/enhancement/los-obstruction-pinning-411
add selectable LOS obstruction pinning for repeater placement Enhancement #411
2026-04-27 13:13:40 -07:00
zjs81 20a9ef3c2b Merge branch 'dev' into enhancement/los-obstruction-pinning-411 2026-04-27 13:13:19 -07:00
zjs81 a741e12ad1 Merge pull request #413 from ericszimmermann/ez_marker_update_squashed
Improve SharedMarker handling
2026-04-27 13:12:09 -07:00
zjs81 e54f30d6fb Merge pull request #414 from Diadlo/fix/jump_to_unread
Improve work with unread messages
2026-04-27 13:11:00 -07:00
zjs81 e1d23ad2c7 style: dart format
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:09:10 -07:00
zjs81 f07993b367 fix(chat): remove unnecessary Navigator.pop calls after setting unread counts 2026-04-27 13:07:21 -07:00
zjs81 0e5f1a45c4 fix(chat): address mark-as-unread double-pop and missed map entry point
- Remove stray Navigator.pop(context) in _markAsUnread for both contact
  and channel chats so the action no longer exits the conversation
- Thread initialUnreadCount through map discovered-contact "Open Chat"
  button so the unread divider/jump still fires from that entry point

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:07:16 -07:00
Dmitry Polshakov f10aeaeba8 chore(l10n): regenerate localizations for mark-as-unread strings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 15:58:03 +03:00
Dmitry Polshakov 00e4f52d75 feat(chat): add "Mark as Unread" action and unread messages divider
- Add "Mark as Unread" option to message context menu in both
  contact and channel chats
- Show "New messages" divider line between read and unread messages
- Add setContactUnreadCount/setChannelUnreadCount methods to connector

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 15:58:03 +03:00
Dmitry Polshakov 3ea2e4763e fix(chat): fix jump-to-oldest-unread scroll not reaching target message
- Pass initialUnreadCount to chat screens before markRead clears it
- Use two-phase scroll: jumpTo estimated offset to build lazy items,
  then ensureVisible for precise positioning
- Await ensureVisible before clearing scroll guard to prevent
  scrollToBottomIfAtBottom from overriding the animation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 14:07:02 +03:00
ericz 94d9afe8b1 consolidate parsing in single parseMarkerText in map_screen.dart 2026-04-26 01:23:33 +02:00
ericz 7db3a12723 squashed commit for: deduplicate markers, allow for updates on position with same label with drawing line, get marker back after deletion in map through tabbing on icon in poi-message. 2026-04-26 00:13:26 +02:00
just-stuff-tm fcf10b4a73 added strings translategemma didnt translate to proper locallization 2026-04-25 09:11:49 -04:00
just-stuff-tm 7f353490cf contextstream/ is added to .gitignore 2026-04-25 09:07:28 -04:00
just_stuff_tm 46683e0ec2 Delete .contextstream/config.json 2026-04-25 09:04:00 -04:00
just-stuff-tm 4e368d562d add selectable LOS obstruction pinning for repeater placement 2026-04-25 08:56:28 -04:00
Serge Tarkovski 92d3009eb4 Fix swapped url/desc args in GPX export and add ContactLocalization unit tests 2026-04-25 01:32:43 +03:00
Serge Tarkovski f8d00caae0 Validate locale override and use preferred locale list for fallback 2026-04-25 01:03:11 +03:00
Serge Tarkovski e03d80b71f Merge remote-tracking branch 'origin/dev' into ukrainian-translations 2026-04-25 00:48:41 +03:00
Serge Tarkovski b7d0db8d1c Refactor: move Contact UI labels to l10n extension; rename raw getter to typeLabelRaw 2026-04-25 00:29:20 +03:00
Serge Tarkovski 6ae3f612ae Localize Score, fix login dialog overflow, use locale-aware date format in channel chat 2026-04-24 13:58:31 +03:00
zjs81 40d3941aab Merge pull request #405 from zjs81/#401-make-multi-ack-a-toggle
#401 make multi ack a toggle
2026-04-23 23:54:52 -07:00
Serge Tarkovski 5e446207c6 Ukrainian translation polished; localized remaining hardcoded UI strings 2026-04-23 17:47:37 +03:00
Enot (ded) Skelly 820bac0db0 fix issues with contact sync
this adds the actual last modified timestamp when present, before we used
last advert time as last modified in error

also sets _pendingInitialContactsSync to true on first connect over BLE
2026-04-21 16:44:04 -07:00
75 changed files with 6408 additions and 704 deletions
+1
View File
@@ -87,6 +87,7 @@ keystore.properties
# IDE # IDE
.vscode/launch.json .vscode/launch.json
.vscode/settings.json .vscode/settings.json
.contextstream/
# Cloudflare Wrangler # Cloudflare Wrangler
.wrangler .wrangler
+29 -5
View File
@@ -658,6 +658,27 @@ class MeshCoreConnector extends ChangeNotifier {
} }
} }
void setContactUnreadCount(String contactKeyHex, int count) {
_contactUnreadCount[contactKeyHex] = count;
_unreadStore.saveContactUnreadCount(
Map<String, int>.from(_contactUnreadCount),
);
notifyListeners();
}
void setChannelUnreadCount(int channelIndex, int count) {
final channel = _findChannelByIndex(channelIndex);
if (channel != null) {
channel.unreadCount = count;
unawaited(
_channelStore.saveChannels(
_channels.isNotEmpty ? _channels : _cachedChannels,
),
);
notifyListeners();
}
}
void markChannelRead(int channelIndex) { void markChannelRead(int channelIndex) {
final channel = _findChannelByIndex(channelIndex); final channel = _findChannelByIndex(channelIndex);
if (channel != null && channel.unreadCount > 0) { if (channel != null && channel.unreadCount > 0) {
@@ -2140,6 +2161,7 @@ class MeshCoreConnector extends ChangeNotifier {
return; return;
} }
_bleInitialSyncStarted = true; _bleInitialSyncStarted = true;
_pendingInitialContactsSync = true;
await _requestDeviceInfo(); await _requestDeviceInfo();
_startBatteryPolling(); _startBatteryPolling();
@@ -3997,7 +4019,7 @@ class MeshCoreConnector extends ChangeNotifier {
); );
} else { } else {
appLogger.info( 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', tag: 'Connector',
); );
return; return;
@@ -4019,7 +4041,7 @@ class MeshCoreConnector extends ChangeNotifier {
if (settings.notificationsEnabled && settings.notifyOnNewAdvert) { if (settings.notificationsEnabled && settings.notifyOnNewAdvert) {
_notificationService.showAdvertNotification( _notificationService.showAdvertNotification(
contactName: contact.name, contactName: contact.name,
contactType: contact.typeLabel, contactType: contact.typeLabelRaw,
contactId: contact.publicKeyHex, contactId: contact.publicKeyHex,
); );
} }
@@ -4094,7 +4116,7 @@ class MeshCoreConnector extends ChangeNotifier {
if (settings.notificationsEnabled && settings.notifyOnNewAdvert) { if (settings.notificationsEnabled && settings.notifyOnNewAdvert) {
_notificationService.showAdvertNotification( _notificationService.showAdvertNotification(
contactName: contact.name, contactName: contact.name,
contactType: contact.typeLabel, contactType: contact.typeLabelRaw,
contactId: contact.publicKeyHex, contactId: contact.publicKeyHex,
); );
} }
@@ -4117,7 +4139,9 @@ class MeshCoreConnector extends ChangeNotifier {
if (_contacts.isEmpty) return 0; if (_contacts.isEmpty) return 0;
var latest = 0; var latest = 0;
for (final contact in _contacts) { for (final contact in _contacts) {
final seconds = contact.lastSeen.millisecondsSinceEpoch ~/ 1000; // prefer lastmod per spec, fallback to lastseen
final source = contact.lastModified ?? contact.lastSeen;
final seconds = source.millisecondsSinceEpoch ~/ 1000;
if (seconds > latest) { if (seconds > latest) {
latest = seconds; latest = seconds;
} }
@@ -6025,7 +6049,7 @@ class MeshCoreConnector extends ChangeNotifier {
if (settings.notificationsEnabled && settings.notifyOnNewAdvert) { if (settings.notificationsEnabled && settings.notifyOnNewAdvert) {
_notificationService.showAdvertNotification( _notificationService.showAdvertNotification(
contactName: contact.name, contactName: contact.name,
contactType: contact.typeLabel, contactType: contact.typeLabelRaw,
contactId: contact.publicKeyHex, contactId: contact.publicKeyHex,
); );
} }
+10 -5
View File
@@ -320,7 +320,7 @@ const int maxPathSize = 64;
const int pathHashSize = 1; const int pathHashSize = 1;
const int maxNameSize = 32; const int maxNameSize = 32;
const int maxFrameSize = 172; const int maxFrameSize = 172;
const int appProtocolVersion = 3; const int appProtocolVersion = 4;
// Matches firmware MAX_TEXT_LEN (10 * CIPHER_BLOCK_SIZE). // Matches firmware MAX_TEXT_LEN (10 * CIPHER_BLOCK_SIZE).
const int maxTextPayloadBytes = 160; const int maxTextPayloadBytes = 160;
const int _sendTextMsgOverheadBytes = const int _sendTextMsgOverheadBytes =
@@ -735,10 +735,15 @@ Uint8List buildUpdateContactPathFrame(
writer.writeInt32LE((longitude * 1e6).round()); writer.writeInt32LE((longitude * 1e6).round());
} }
if (lastModified != null) { final hasLocation = lat != null && lon != null;
// Last modified if (hasLocation || lastModified != null) {
final lastModifiedTimestamp = lastModified.millisecondsSinceEpoch ~/ 1000; writer.writeInt32LE(hasLocation ? (lat * 1e6).round() : 0);
writer.writeUInt32LE(lastModifiedTimestamp); writer.writeInt32LE(hasLocation ? (lon * 1e6).round() : 0);
if (lastModified != null) {
// Last modified
final lastModifiedTimestamp = lastModified.millisecondsSinceEpoch ~/ 1000;
writer.writeUInt32LE(lastModifiedTimestamp);
}
} }
return writer.toBytes(); return writer.toBytes();
+19
View File
@@ -49,6 +49,25 @@ class ChatScrollController extends ScrollController {
} }
} }
/// Jumps toward an off-screen message so that lazy ListView.builder builds
/// items near it. Only visible + cacheExtent items have real heights, so we
/// use proportion of maxScrollExtent (itself an estimate from built items'
/// avg height). Call [onJumped] on the next frame to ensureVisible/scroll
/// to the exact target.
void jumpToEstimatedOffset({
required int unreadCount,
required int totalMessages,
required VoidCallback onJumped,
}) {
if (!hasClients || totalMessages == 0) return;
final maxExtent = position.maxScrollExtent;
final jumpOffset = maxExtent * (unreadCount / totalMessages);
if (jumpOffset > 100) {
jumpTo(jumpOffset);
}
WidgetsBinding.instance.addPostFrameCallback((_) => onJumped());
}
void scrollToBottomIfAtBottom() { void scrollToBottomIfAtBottom() {
// Only scroll if jump button is NOT showing (i.e., already at bottom) // Only scroll if jump button is NOT showing (i.e., already at bottom)
if (!showJumpToBottom.value && hasClients && position.maxScrollExtent > 0) { if (!showJumpToBottom.value && hasClients && position.maxScrollExtent > 0) {
+84 -1
View File
@@ -104,6 +104,8 @@
"settings_privacyModeEnabled": "Режим на поверителност е активиран", "settings_privacyModeEnabled": "Режим на поверителност е активиран",
"settings_privacyModeDisabled": "Режим на поверителност е деактивиран", "settings_privacyModeDisabled": "Режим на поверителност е деактивиран",
"settings_actions": "Действия", "settings_actions": "Действия",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Изпрати Реклама", "settings_sendAdvertisement": "Изпрати Реклама",
"settings_sendAdvertisementSubtitle": "Сега присъствие в ефир", "settings_sendAdvertisementSubtitle": "Сега присъствие в ефир",
"settings_advertisementSent": "Реклама изпратена", "settings_advertisementSent": "Реклама изпратена",
@@ -2066,5 +2068,86 @@
"room_guest": "Информация за сървъра на стаята", "room_guest": "Информация за сървъра на стаята",
"repeater_guest": "Информация за ретранслаторите", "repeater_guest": "Информация за ретранслаторите",
"repeater_guestTools": "Инструменти за гости", "repeater_guestTools": "Инструменти за гости",
"settings_multiAck": "Множество потвърждения" "common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"settings_multiAck": "Множество потвърждения",
"map_sharedAt": "Споделено",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losSelectedObstructionTitle": "Избрано препятствие",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losBlockedSpotsHint": "Кликнете върху блокираната точка, за да я отбележите на картата.",
"losBlockedSpotsTitle": "Ограничени места",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})."
} }
+84 -1
View File
@@ -104,6 +104,8 @@
"settings_privacyModeEnabled": "Datenschutzmodus aktiviert", "settings_privacyModeEnabled": "Datenschutzmodus aktiviert",
"settings_privacyModeDisabled": "Datenschutzmodus deaktiviert", "settings_privacyModeDisabled": "Datenschutzmodus deaktiviert",
"settings_actions": "Aktionen", "settings_actions": "Aktionen",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Sende Ankündigung", "settings_sendAdvertisement": "Sende Ankündigung",
"settings_sendAdvertisementSubtitle": "Sende eine Ankündigung", "settings_sendAdvertisementSubtitle": "Sende eine Ankündigung",
"settings_advertisementSent": "Ankündigung gesendet", "settings_advertisementSent": "Ankündigung gesendet",
@@ -2094,5 +2096,86 @@
"repeater_guestTools": "Gastwerkzeuge", "repeater_guestTools": "Gastwerkzeuge",
"chat_sendMessage": "Nachricht senden", "chat_sendMessage": "Nachricht senden",
"room_guest": "Informationen zum Room Server", "room_guest": "Informationen zum Room Server",
"settings_multiAck": "Mehrere Bestätigungen" "common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"settings_multiAck": "Mehrere Bestätigungen",
"map_sharedAt": "Geteilt",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losBlockedSpotsTitle": "Reservierte Plätze",
"losSelectedObstructionTitle": "Ausgewählte Behinderung",
"losBlockedSpotChip": "{distance} • {distanceUnit} • {obstruction} {heightUnit}",
"losBlockedSpotsHint": "Klicken Sie auf einen blockierten Bereich, um ihn auf der Karte hervorzuheben.",
"losSelectedObstructionDetails": "Blockiert durch {obstruction} in einer Höhe von {heightUnit}, {distanceFromA} von A und {distanceFromB} von B ({distanceUnit})."
} }
+95 -3
View File
@@ -12,6 +12,7 @@
"common_delete": "Delete", "common_delete": "Delete",
"common_deleteAll": "Delete All", "common_deleteAll": "Delete All",
"common_close": "Close", "common_close": "Close",
"common_done": "Done",
"common_edit": "Edit", "common_edit": "Edit",
"common_add": "Add", "common_add": "Add",
"common_settings": "Settings", "common_settings": "Settings",
@@ -181,6 +182,8 @@
"settings_multiAck": "Multi-ACKs", "settings_multiAck": "Multi-ACKs",
"settings_telemetryModeUpdated": "Telemetry mode updated", "settings_telemetryModeUpdated": "Telemetry mode updated",
"settings_actions": "Actions", "settings_actions": "Actions",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Send Advertisement", "settings_sendAdvertisement": "Send Advertisement",
"settings_sendAdvertisementSubtitle": "Broadcast presence now", "settings_sendAdvertisementSubtitle": "Broadcast presence now",
"settings_advertisementSent": "Advertisement sent", "settings_advertisementSent": "Advertisement sent",
@@ -193,8 +196,8 @@
"settings_rebootDeviceSubtitle": "Restart the MeshCore device", "settings_rebootDeviceSubtitle": "Restart the MeshCore device",
"settings_rebootDeviceConfirm": "Are you sure you want to reboot the device? You will be disconnected.", "settings_rebootDeviceConfirm": "Are you sure you want to reboot the device? You will be disconnected.",
"settings_debug": "Debug", "settings_debug": "Debug",
"settings_bleDebugLog": "BLE Debug Log", "settings_companionDebugLog": "Companion Debug Log",
"settings_bleDebugLogSubtitle": "BLE commands, responses, and raw data", "settings_companionDebugLogSubtitle": "BLE/TCP/USB commands, responses, and raw data",
"settings_appDebugLog": "App Debug Log", "settings_appDebugLog": "App Debug Log",
"settings_appDebugLogSubtitle": "Application debug messages", "settings_appDebugLogSubtitle": "Application debug messages",
"settings_about": "About", "settings_about": "About",
@@ -516,6 +519,14 @@
}, },
"channels_hashtagChannel": "Hashtag channel", "channels_hashtagChannel": "Hashtag channel",
"channels_public": "Public", "channels_public": "Public",
"channels_via": "via {path}",
"@channels_via": {
"placeholders": {
"path": {
"type": "String"
}
}
},
"channels_private": "Private", "channels_private": "Private",
"channels_publicChannel": "Public channel", "channels_publicChannel": "Public channel",
"channels_privateChannel": "Private channel", "channels_privateChannel": "Private channel",
@@ -766,6 +777,7 @@
} }
}, },
"chat_successes": "successes", "chat_successes": "successes",
"chat_score": "Score",
"chat_removePath": "Remove path", "chat_removePath": "Remove path",
"chat_noPathHistoryYet": "No path history yet.\nSend a message to discover paths.", "chat_noPathHistoryYet": "No path history yet.\nSend a message to discover paths.",
"chat_pathActions": "Path Actions:", "chat_pathActions": "Path Actions:",
@@ -817,6 +829,8 @@
} }
} }
}, },
"chat_markAsUnread": "Mark as Unread",
"chat_newMessages": "New messages",
"chat_openLink": "Open Link?", "chat_openLink": "Open Link?",
"chat_openLinkConfirmation": "Do you want to open this link in your browser?", "chat_openLinkConfirmation": "Do you want to open this link in your browser?",
"chat_open": "Open", "chat_open": "Open",
@@ -862,6 +876,12 @@
"map_from": "From", "map_from": "From",
"map_source": "Source", "map_source": "Source",
"map_flags": "Flags", "map_flags": "Flags",
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"map_shareMarkerHere": "Share marker here", "map_shareMarkerHere": "Share marker here",
"map_setAsMyLocation": "Set as my location", "map_setAsMyLocation": "Set as my location",
"map_pinLabel": "Pin label", "map_pinLabel": "Pin label",
@@ -896,6 +916,7 @@
"map_guessedLocation": "Guessed location", "map_guessedLocation": "Guessed location",
"map_lastSeenTime": "Last Seen Time", "map_lastSeenTime": "Last Seen Time",
"map_sharedPin": "Shared pin", "map_sharedPin": "Shared pin",
"map_sharedAt": "Shared",
"map_joinRoom": "Join Room", "map_joinRoom": "Join Room",
"map_manageRepeater": "Manage Repeater", "map_manageRepeater": "Manage Repeater",
"map_tapToAdd": "Tap on nodes to add them to the path.", "map_tapToAdd": "Tap on nodes to add them to the path.",
@@ -1875,6 +1896,46 @@
"losLegendRadioHorizon": "Radio horizon", "losLegendRadioHorizon": "Radio horizon",
"losLegendLosBeam": "LOS beam", "losLegendLosBeam": "LOS beam",
"losLegendTerrain": "Terrain", "losLegendTerrain": "Terrain",
"losBlockedSpotsTitle": "Blocked spots",
"losBlockedSpotsHint": "Tap a blocked spot to highlight it on the map.",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"losSelectedObstructionTitle": "Selected obstruction",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).",
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losFrequencyLabel": "Frequency", "losFrequencyLabel": "Frequency",
"losFrequencyInfoTooltip": "View calculation details", "losFrequencyInfoTooltip": "View calculation details",
"losFrequencyDialogTitle": "Radio horizon calculation", "losFrequencyDialogTitle": "Radio horizon calculation",
@@ -2114,5 +2175,36 @@
} }
}, },
"translation_translationOptions": "Translation options", "translation_translationOptions": "Translation options",
"translation_systemLanguage": "System language" "translation_systemLanguage": "System language",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown"
} }
+84 -1
View File
@@ -104,6 +104,8 @@
"settings_privacyModeEnabled": "Modo de privacidad activado", "settings_privacyModeEnabled": "Modo de privacidad activado",
"settings_privacyModeDisabled": "Modo de privacidad desactivado", "settings_privacyModeDisabled": "Modo de privacidad desactivado",
"settings_actions": "Acciones", "settings_actions": "Acciones",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Enviar Anuncio", "settings_sendAdvertisement": "Enviar Anuncio",
"settings_sendAdvertisementSubtitle": "Presencia de transmisión ahora", "settings_sendAdvertisementSubtitle": "Presencia de transmisión ahora",
"settings_advertisementSent": "Anuncio enviado", "settings_advertisementSent": "Anuncio enviado",
@@ -2094,5 +2096,86 @@
"chat_sendMessage": "Enviar mensaje", "chat_sendMessage": "Enviar mensaje",
"repeater_guestTools": "Herramientas para invitados", "repeater_guestTools": "Herramientas para invitados",
"room_guest": "Información del servidor", "room_guest": "Información del servidor",
"settings_multiAck": "Múltiples respuestas de confirmación" "common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"settings_multiAck": "Múltiples respuestas de confirmación",
"map_sharedAt": "Compartido",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losBlockedSpotsTitle": "Espacios ocupados",
"losBlockedSpotsHint": "Seleccione un punto bloqueado para resaltarlo en el mapa.",
"losSelectedObstructionTitle": "Obstrucción seleccionada",
"losSelectedObstructionDetails": "Bloqueado por {obstruction} a una altura de {heightUnit}, a {distanceFromA} metros de A y a {distanceFromB} metros de B ({distanceUnit}).",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}"
} }
+84 -1
View File
@@ -104,6 +104,8 @@
"settings_privacyModeEnabled": "Mode de confidentialité activé", "settings_privacyModeEnabled": "Mode de confidentialité activé",
"settings_privacyModeDisabled": "Mode de confidentialité désactivé", "settings_privacyModeDisabled": "Mode de confidentialité désactivé",
"settings_actions": "Actions", "settings_actions": "Actions",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "S'annoncer", "settings_sendAdvertisement": "S'annoncer",
"settings_sendAdvertisementSubtitle": "Présence diffusée maintenant", "settings_sendAdvertisementSubtitle": "Présence diffusée maintenant",
"settings_advertisementSent": "Annonce envoyée", "settings_advertisementSent": "Annonce envoyée",
@@ -2066,5 +2068,86 @@
"chat_sendMessage": "Envoyer un message", "chat_sendMessage": "Envoyer un message",
"room_guest": "Informations sur le serveur", "room_guest": "Informations sur le serveur",
"repeater_guest": "Informations sur les répéteurs", "repeater_guest": "Informations sur les répéteurs",
"settings_multiAck": "Plusieurs accusés de réception" "common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"settings_multiAck": "Plusieurs accusés de réception",
"map_sharedAt": "Partagé",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losSelectedObstructionTitle": "Obstruction sélectionnée",
"losBlockedSpotsTitle": "Places occupés",
"losBlockedSpotsHint": "Sélectionnez un emplacement bloqué pour le mettre en évidence sur la carte.",
"losSelectedObstructionDetails": "Bloqué par {obstruction}, à une hauteur de {heightUnit}, à une distance de {distanceFromA} par rapport à A et à une distance de {distanceFromB} par rapport à B ({distanceUnit}).",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}"
} }
+84 -1
View File
@@ -167,6 +167,8 @@
"settings_privacyModeEnabled": "Adatvédelem mód beállítva", "settings_privacyModeEnabled": "Adatvédelem mód beállítva",
"settings_privacyModeDisabled": "Adatvédelem mód kikapcsolva", "settings_privacyModeDisabled": "Adatvédelem mód kikapcsolva",
"settings_actions": "Tevékenységek", "settings_actions": "Tevékenységek",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Hirdetés küldése", "settings_sendAdvertisement": "Hirdetés küldése",
"settings_sendAdvertisementSubtitle": "A nyilvános megjelenés", "settings_sendAdvertisementSubtitle": "A nyilvános megjelenés",
"settings_advertisementSent": "Hirdetés elküldve", "settings_advertisementSent": "Hirdetés elküldve",
@@ -2104,5 +2106,86 @@
"room_guest": "Szoba szerver információk", "room_guest": "Szoba szerver információk",
"chat_sendMessage": "Üzenet küldése", "chat_sendMessage": "Üzenet küldése",
"repeater_guest": "Adatok a repeaterről", "repeater_guest": "Adatok a repeaterről",
"settings_multiAck": "Többszörös visszaigazolások" "common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"settings_multiAck": "Többszörös visszaigazolások",
"map_sharedAt": "Megosztva",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losSelectedObstructionTitle": "Kiválasztott akadály",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losBlockedSpotsHint": "A blokkolt területet megjelölve, hogy a térképen kiemeljük.",
"losBlockedSpotsTitle": "Foglalhatatlan területek",
"losSelectedObstructionDetails": "Elakadt a {obstruction} miatt, {heightUnit} magasságban, {distanceFromA} méterrel A-tól és {distanceFromB} méterrel B-től ({distanceUnit})."
} }
+84 -1
View File
@@ -104,6 +104,8 @@
"settings_privacyModeEnabled": "Modalità privacy abilitata", "settings_privacyModeEnabled": "Modalità privacy abilitata",
"settings_privacyModeDisabled": "Modalità privacy disabilitata", "settings_privacyModeDisabled": "Modalità privacy disabilitata",
"settings_actions": "Azioni", "settings_actions": "Azioni",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Invia Annuncio", "settings_sendAdvertisement": "Invia Annuncio",
"settings_sendAdvertisementSubtitle": "Presenza trasmessa ora", "settings_sendAdvertisementSubtitle": "Presenza trasmessa ora",
"settings_advertisementSent": "Annuncio inviato", "settings_advertisementSent": "Annuncio inviato",
@@ -2066,5 +2068,86 @@
"repeater_guestTools": "Strumenti per gli ospiti", "repeater_guestTools": "Strumenti per gli ospiti",
"chat_sendMessage": "Invia messaggio", "chat_sendMessage": "Invia messaggio",
"room_guest": "Informazioni sul server", "room_guest": "Informazioni sul server",
"settings_multiAck": "ACK multipli" "common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"settings_multiAck": "ACK multipli",
"map_sharedAt": "Condiviso",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losSelectedObstructionTitle": "Ostacolo selezionato",
"losBlockedSpotsHint": "Tocca un punto bloccato sulla mappa per evidenziarlo.",
"losBlockedSpotsTitle": "Posti occupati",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})."
} }
+84 -1
View File
@@ -167,6 +167,8 @@
"settings_privacyModeEnabled": "プライバシーモードが有効になっています", "settings_privacyModeEnabled": "プライバシーモードが有効になっています",
"settings_privacyModeDisabled": "プライバシーモードは無効化されています", "settings_privacyModeDisabled": "プライバシーモードは無効化されています",
"settings_actions": "行動", "settings_actions": "行動",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "広告を送信する", "settings_sendAdvertisement": "広告を送信する",
"settings_sendAdvertisementSubtitle": "現在、放送での活動", "settings_sendAdvertisementSubtitle": "現在、放送での活動",
"settings_advertisementSent": "広告が送信されました", "settings_advertisementSent": "広告が送信されました",
@@ -2104,5 +2106,86 @@
"chat_sendMessage": "メッセージを送信する", "chat_sendMessage": "メッセージを送信する",
"repeater_guest": "繰り返し送信に関する情報", "repeater_guest": "繰り返し送信に関する情報",
"repeater_guestTools": "ゲスト向けツール", "repeater_guestTools": "ゲスト向けツール",
"settings_multiAck": "複数のACK(応答)" "common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"settings_multiAck": "複数のACK(応答)",
"map_sharedAt": "共有済み",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losBlockedSpotsHint": "地図上で、特定された場所を強調するために、該当する場所をタップしてください。",
"losSelectedObstructionTitle": "選択された障害",
"losBlockedSpotsTitle": "利用できない場所",
"losSelectedObstructionDetails": "{obstruction} によって {heightUnit} の高さで、A地点から {distanceFromA}、B地点から {distanceFromB} ({distanceUnit}) の距離で塞がれています。",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}"
} }
+84 -1
View File
@@ -167,6 +167,8 @@
"settings_privacyModeEnabled": "개인 정보 보호 모드 활성화", "settings_privacyModeEnabled": "개인 정보 보호 모드 활성화",
"settings_privacyModeDisabled": "개인 정보 보호 모드 비활성화", "settings_privacyModeDisabled": "개인 정보 보호 모드 비활성화",
"settings_actions": "행동", "settings_actions": "행동",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "광고 전송", "settings_sendAdvertisement": "광고 전송",
"settings_sendAdvertisementSubtitle": "방송 활동", "settings_sendAdvertisementSubtitle": "방송 활동",
"settings_advertisementSent": "광고 전송", "settings_advertisementSent": "광고 전송",
@@ -2104,5 +2106,86 @@
"chat_sendMessage": "메시지를 보내기", "chat_sendMessage": "메시지를 보내기",
"repeater_guest": "반복 장비 정보", "repeater_guest": "반복 장비 정보",
"room_guest": "서버 정보", "room_guest": "서버 정보",
"settings_multiAck": "다중 ACK" "common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"settings_multiAck": "다중 ACK",
"map_sharedAt": "공유됨",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losBlockedSpotsHint": "지도에서 특정 위치를 강조하려면 해당 위치를 클릭하세요.",
"losBlockedSpotsTitle": "차단된 공간",
"losSelectedObstructionTitle": "선택된 장애물",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})."
} }
+191 -6
View File
@@ -202,6 +202,12 @@ abstract class AppLocalizations {
/// **'Close'** /// **'Close'**
String get common_close; String get common_close;
/// No description provided for @common_done.
///
/// In en, this message translates to:
/// **'Done'**
String get common_done;
/// No description provided for @common_edit. /// No description provided for @common_edit.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -916,6 +922,18 @@ abstract class AppLocalizations {
/// **'Actions'** /// **'Actions'**
String get settings_actions; String get settings_actions;
/// No description provided for @settings_deleteAllPaths.
///
/// In en, this message translates to:
/// **'Delete All Paths'**
String get settings_deleteAllPaths;
/// No description provided for @settings_deleteAllPathsSubtitle.
///
/// In en, this message translates to:
/// **'Clear all path data from contacts.'**
String get settings_deleteAllPathsSubtitle;
/// No description provided for @settings_sendAdvertisement. /// No description provided for @settings_sendAdvertisement.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -988,17 +1006,17 @@ abstract class AppLocalizations {
/// **'Debug'** /// **'Debug'**
String get settings_debug; String get settings_debug;
/// No description provided for @settings_bleDebugLog. /// No description provided for @settings_companionDebugLog.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'BLE Debug Log'** /// **'Companion Debug Log'**
String get settings_bleDebugLog; String get settings_companionDebugLog;
/// No description provided for @settings_bleDebugLogSubtitle. /// No description provided for @settings_companionDebugLogSubtitle.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'BLE commands, responses, and raw data'** /// **'BLE/TCP/USB commands, responses, and raw data'**
String get settings_bleDebugLogSubtitle; String get settings_companionDebugLogSubtitle;
/// No description provided for @settings_appDebugLog. /// No description provided for @settings_appDebugLog.
/// ///
@@ -2044,6 +2062,12 @@ abstract class AppLocalizations {
/// **'Public'** /// **'Public'**
String get channels_public; String get channels_public;
/// No description provided for @channels_via.
///
/// In en, this message translates to:
/// **'via {path}'**
String channels_via(String path);
/// No description provided for @channels_private. /// No description provided for @channels_private.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -2662,6 +2686,12 @@ abstract class AppLocalizations {
/// **'successes'** /// **'successes'**
String get chat_successes; String get chat_successes;
/// No description provided for @chat_score.
///
/// In en, this message translates to:
/// **'Score'**
String get chat_score;
/// No description provided for @chat_removePath. /// No description provided for @chat_removePath.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -2824,6 +2854,18 @@ abstract class AppLocalizations {
/// **'Unread: {count}'** /// **'Unread: {count}'**
String chat_unread(int count); String chat_unread(int count);
/// No description provided for @chat_markAsUnread.
///
/// In en, this message translates to:
/// **'Mark as Unread'**
String get chat_markAsUnread;
/// No description provided for @chat_newMessages.
///
/// In en, this message translates to:
/// **'New messages'**
String get chat_newMessages;
/// No description provided for @chat_openLink. /// No description provided for @chat_openLink.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -2968,6 +3010,42 @@ abstract class AppLocalizations {
/// **'Flags'** /// **'Flags'**
String get map_flags; String get map_flags;
/// No description provided for @map_type.
///
/// In en, this message translates to:
/// **'Type'**
String get map_type;
/// No description provided for @map_path.
///
/// In en, this message translates to:
/// **'Path'**
String get map_path;
/// No description provided for @map_location.
///
/// In en, this message translates to:
/// **'Location'**
String get map_location;
/// No description provided for @map_estLocation.
///
/// In en, this message translates to:
/// **'Est. Location'**
String get map_estLocation;
/// No description provided for @map_publicKey.
///
/// In en, this message translates to:
/// **'Public Key'**
String get map_publicKey;
/// No description provided for @map_publicKeyPrefixHint.
///
/// In en, this message translates to:
/// **'e.g. ab12'**
String get map_publicKeyPrefixHint;
/// No description provided for @map_shareMarkerHere. /// No description provided for @map_shareMarkerHere.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -3130,6 +3208,12 @@ abstract class AppLocalizations {
/// **'Shared pin'** /// **'Shared pin'**
String get map_sharedPin; String get map_sharedPin;
/// No description provided for @map_sharedAt.
///
/// In en, this message translates to:
/// **'Shared'**
String get map_sharedAt;
/// No description provided for @map_joinRoom. /// No description provided for @map_joinRoom.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -5640,6 +5724,47 @@ abstract class AppLocalizations {
/// **'Terrain'** /// **'Terrain'**
String get losLegendTerrain; String get losLegendTerrain;
/// No description provided for @losBlockedSpotsTitle.
///
/// In en, this message translates to:
/// **'Blocked spots'**
String get losBlockedSpotsTitle;
/// No description provided for @losBlockedSpotsHint.
///
/// In en, this message translates to:
/// **'Tap a blocked spot to highlight it on the map.'**
String get losBlockedSpotsHint;
/// No description provided for @losBlockedSpotChip.
///
/// In en, this message translates to:
/// **'{distance} {distanceUnit} • {obstruction} {heightUnit}'**
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
);
/// No description provided for @losSelectedObstructionTitle.
///
/// In en, this message translates to:
/// **'Selected obstruction'**
String get losSelectedObstructionTitle;
/// No description provided for @losSelectedObstructionDetails.
///
/// In en, this message translates to:
/// **'Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).'**
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
);
/// No description provided for @losFrequencyLabel. /// No description provided for @losFrequencyLabel.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -6388,6 +6513,66 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'System language'** /// **'System language'**
String get translation_systemLanguage; String get translation_systemLanguage;
/// No description provided for @background_serviceTitle.
///
/// In en, this message translates to:
/// **'MeshCore running'**
String get background_serviceTitle;
/// No description provided for @background_serviceText.
///
/// In en, this message translates to:
/// **'Keeping BLE connected'**
String get background_serviceText;
/// No description provided for @appSettings_translationModelDeleted.
///
/// In en, this message translates to:
/// **'Deleted {name}'**
String appSettings_translationModelDeleted(String name);
/// No description provided for @appSettings_translationModelDeleteFailed.
///
/// In en, this message translates to:
/// **'Failed to delete: {error}'**
String appSettings_translationModelDeleteFailed(String error);
/// No description provided for @channels_channelUpdateFailed.
///
/// In en, this message translates to:
/// **'Failed to update channel: {error}'**
String channels_channelUpdateFailed(String error);
/// No description provided for @contact_typeChat.
///
/// In en, this message translates to:
/// **'Chat'**
String get contact_typeChat;
/// No description provided for @contact_typeRepeater.
///
/// In en, this message translates to:
/// **'Repeater'**
String get contact_typeRepeater;
/// No description provided for @contact_typeRoom.
///
/// In en, this message translates to:
/// **'Room'**
String get contact_typeRoom;
/// No description provided for @contact_typeSensor.
///
/// In en, this message translates to:
/// **'Sensor'**
String get contact_typeSensor;
/// No description provided for @contact_typeUnknown.
///
/// In en, this message translates to:
/// **'Unknown'**
String get contact_typeUnknown;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate
+115 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get common_close => 'Затвори'; String get common_close => 'Затвори';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Редактирай'; String get common_edit => 'Редактирай';
@@ -445,6 +448,13 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get settings_actions => 'Действия'; String get settings_actions => 'Действия';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Изпрати Реклама'; String get settings_sendAdvertisement => 'Изпрати Реклама';
@@ -486,11 +496,11 @@ class AppLocalizationsBg extends AppLocalizations {
String get settings_debug => 'Отстрани'; String get settings_debug => 'Отстрани';
@override @override
String get settings_bleDebugLog => 'Лог за отстраняване на грешки на BLE'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'Команди, отговори и сурови данни BLE'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => String get settings_appDebugLog =>
@@ -1095,6 +1105,11 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get channels_public => 'Публично'; String get channels_public => 'Публично';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Личен'; String get channels_private => 'Личен';
@@ -1456,6 +1471,9 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get chat_successes => 'Успехи'; String get chat_successes => 'Успехи';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Премахни пътя'; String get chat_removePath => 'Премахни пътя';
@@ -1558,6 +1576,12 @@ class AppLocalizationsBg extends AppLocalizations {
return 'Непрочетени: $count'; return 'Непрочетени: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Отваряне на връзката?'; String get chat_openLink => 'Отваряне на връзката?';
@@ -1639,6 +1663,24 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get map_flags => 'Флаг'; String get map_flags => 'Флаг';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Споделете маркер тук'; String get map_shareMarkerHere => 'Споделете маркер тук';
@@ -1724,6 +1766,9 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get map_sharedPin => 'Споделено копие'; String get map_sharedPin => 'Споделено копие';
@override
String get map_sharedAt => 'Споделено';
@override @override
String get map_joinRoom => 'Присъедини се към стаята'; String get map_joinRoom => 'Присъедини се към стаята';
@@ -3236,6 +3281,37 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Терен'; String get losLegendTerrain => 'Терен';
@override
String get losBlockedSpotsTitle => 'Ограничени места';
@override
String get losBlockedSpotsHint =>
'Кликнете върху блокираната точка, за да я отбележите на картата.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Избрано препятствие';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Честота'; String get losFrequencyLabel => 'Честота';
@@ -3700,4 +3776,40 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Език на системата'; String get translation_systemLanguage => 'Език на системата';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+115 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get common_close => 'Schließen'; String get common_close => 'Schließen';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Bearbeiten'; String get common_edit => 'Bearbeiten';
@@ -443,6 +446,13 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get settings_actions => 'Aktionen'; String get settings_actions => 'Aktionen';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Sende Ankündigung'; String get settings_sendAdvertisement => 'Sende Ankündigung';
@@ -483,11 +493,11 @@ class AppLocalizationsDe extends AppLocalizations {
String get settings_debug => 'Fehlerbehebung'; String get settings_debug => 'Fehlerbehebung';
@override @override
String get settings_bleDebugLog => 'BLE-Debug-Protokoll'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'BLE-Befehle, Antworten und Rohdaten'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'App-Debug-Protokoll'; String get settings_appDebugLog => 'App-Debug-Protokoll';
@@ -1090,6 +1100,11 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get channels_public => 'Öffentlich'; String get channels_public => 'Öffentlich';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Privat'; String get channels_private => 'Privat';
@@ -1455,6 +1470,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get chat_successes => 'Erfolgreich'; String get chat_successes => 'Erfolgreich';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Pfad entfernen'; String get chat_removePath => 'Pfad entfernen';
@@ -1555,6 +1573,12 @@ class AppLocalizationsDe extends AppLocalizations {
return 'Ungelesen: $count'; return 'Ungelesen: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Link öffnen?'; String get chat_openLink => 'Link öffnen?';
@@ -1636,6 +1660,24 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get map_flags => 'Flags'; String get map_flags => 'Flags';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Teilen Sie den Marker hier.'; String get map_shareMarkerHere => 'Teilen Sie den Marker hier.';
@@ -1721,6 +1763,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get map_sharedPin => 'Gemeinsames Passwort'; String get map_sharedPin => 'Gemeinsames Passwort';
@override
String get map_sharedAt => 'Geteilt';
@override @override
String get map_joinRoom => 'Beitreten Sie dem Raum'; String get map_joinRoom => 'Beitreten Sie dem Raum';
@@ -3241,6 +3286,37 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Gelände'; String get losLegendTerrain => 'Gelände';
@override
String get losBlockedSpotsTitle => 'Reservierte Plätze';
@override
String get losBlockedSpotsHint =>
'Klicken Sie auf einen blockierten Bereich, um ihn auf der Karte hervorzuheben.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance$distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Ausgewählte Behinderung';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blockiert durch $obstruction in einer Höhe von $heightUnit, $distanceFromA von A und $distanceFromB von B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Frequenz'; String get losFrequencyLabel => 'Frequenz';
@@ -3711,4 +3787,40 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Sprache des Systems'; String get translation_systemLanguage => 'Sprache des Systems';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+115 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get common_close => 'Close'; String get common_close => 'Close';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Edit'; String get common_edit => 'Edit';
@@ -435,6 +438,13 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get settings_actions => 'Actions'; String get settings_actions => 'Actions';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Send Advertisement'; String get settings_sendAdvertisement => 'Send Advertisement';
@@ -474,11 +484,11 @@ class AppLocalizationsEn extends AppLocalizations {
String get settings_debug => 'Debug'; String get settings_debug => 'Debug';
@override @override
String get settings_bleDebugLog => 'BLE Debug Log'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'BLE commands, responses, and raw data'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'App Debug Log'; String get settings_appDebugLog => 'App Debug Log';
@@ -1072,6 +1082,11 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get channels_public => 'Public'; String get channels_public => 'Public';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Private'; String get channels_private => 'Private';
@@ -1428,6 +1443,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get chat_successes => 'successes'; String get chat_successes => 'successes';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Remove path'; String get chat_removePath => 'Remove path';
@@ -1525,6 +1543,12 @@ class AppLocalizationsEn extends AppLocalizations {
return 'Unread: $count'; return 'Unread: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Open Link?'; String get chat_openLink => 'Open Link?';
@@ -1606,6 +1630,24 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get map_flags => 'Flags'; String get map_flags => 'Flags';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Share marker here'; String get map_shareMarkerHere => 'Share marker here';
@@ -1690,6 +1732,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get map_sharedPin => 'Shared pin'; String get map_sharedPin => 'Shared pin';
@override
String get map_sharedAt => 'Shared';
@override @override
String get map_joinRoom => 'Join Room'; String get map_joinRoom => 'Join Room';
@@ -3180,6 +3225,37 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Terrain'; String get losLegendTerrain => 'Terrain';
@override
String get losBlockedSpotsTitle => 'Blocked spots';
@override
String get losBlockedSpotsHint =>
'Tap a blocked spot to highlight it on the map.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Selected obstruction';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Frequency'; String get losFrequencyLabel => 'Frequency';
@@ -3631,4 +3707,40 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'System language'; String get translation_systemLanguage => 'System language';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+115 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get common_close => 'Cerrar'; String get common_close => 'Cerrar';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Editar'; String get common_edit => 'Editar';
@@ -442,6 +445,13 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get settings_actions => 'Acciones'; String get settings_actions => 'Acciones';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Enviar Anuncio'; String get settings_sendAdvertisement => 'Enviar Anuncio';
@@ -484,11 +494,11 @@ class AppLocalizationsEs extends AppLocalizations {
String get settings_debug => 'Depurar'; String get settings_debug => 'Depurar';
@override @override
String get settings_bleDebugLog => 'Registro de Depuración BLE'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'Comandos, respuestas y datos brutos de BLE'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'Registro de Depuración de la App'; String get settings_appDebugLog => 'Registro de Depuración de la App';
@@ -1092,6 +1102,11 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get channels_public => 'Público'; String get channels_public => 'Público';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Privado'; String get channels_private => 'Privado';
@@ -1453,6 +1468,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get chat_successes => 'Éxitos'; String get chat_successes => 'Éxitos';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Eliminar ruta'; String get chat_removePath => 'Eliminar ruta';
@@ -1554,6 +1572,12 @@ class AppLocalizationsEs extends AppLocalizations {
return 'Sin leer: $count'; return 'Sin leer: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => '¿Abrir enlace?'; String get chat_openLink => '¿Abrir enlace?';
@@ -1635,6 +1659,24 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get map_flags => 'Banderas'; String get map_flags => 'Banderas';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Compartir marcador aquí'; String get map_shareMarkerHere => 'Compartir marcador aquí';
@@ -1720,6 +1762,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get map_sharedPin => 'Pin compartido'; String get map_sharedPin => 'Pin compartido';
@override
String get map_sharedAt => 'Compartido';
@override @override
String get map_joinRoom => 'Únete a la sala'; String get map_joinRoom => 'Únete a la sala';
@@ -3235,6 +3280,37 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Terreno'; String get losLegendTerrain => 'Terreno';
@override
String get losBlockedSpotsTitle => 'Espacios ocupados';
@override
String get losBlockedSpotsHint =>
'Seleccione un punto bloqueado para resaltarlo en el mapa.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Obstrucción seleccionada';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Bloqueado por $obstruction a una altura de $heightUnit, a $distanceFromA metros de A y a $distanceFromB metros de B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Frecuencia'; String get losFrequencyLabel => 'Frecuencia';
@@ -3704,4 +3780,40 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Idioma del sistema'; String get translation_systemLanguage => 'Idioma del sistema';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+115 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get common_close => 'Fermer'; String get common_close => 'Fermer';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Modifier'; String get common_edit => 'Modifier';
@@ -447,6 +450,13 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get settings_actions => 'Actions'; String get settings_actions => 'Actions';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'S\'annoncer'; String get settings_sendAdvertisement => 'S\'annoncer';
@@ -488,11 +498,11 @@ class AppLocalizationsFr extends AppLocalizations {
String get settings_debug => 'Déboguer'; String get settings_debug => 'Déboguer';
@override @override
String get settings_bleDebugLog => 'Journal de débogage BLE'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'Commandes BLE, réponses et données brutes'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'Journal de débogage de l\'application'; String get settings_appDebugLog => 'Journal de débogage de l\'application';
@@ -1097,6 +1107,11 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get channels_public => 'Public'; String get channels_public => 'Public';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Privé'; String get channels_private => 'Privé';
@@ -1460,6 +1475,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get chat_successes => 'Succès'; String get chat_successes => 'Succès';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Supprimer le chemin'; String get chat_removePath => 'Supprimer le chemin';
@@ -1563,6 +1581,12 @@ class AppLocalizationsFr extends AppLocalizations {
return 'Non lu : $count'; return 'Non lu : $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Ouvrir le lien ?'; String get chat_openLink => 'Ouvrir le lien ?';
@@ -1645,6 +1669,24 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get map_flags => 'Drapeaux'; String get map_flags => 'Drapeaux';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Partager le marqueur ici'; String get map_shareMarkerHere => 'Partager le marqueur ici';
@@ -1730,6 +1772,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get map_sharedPin => 'Clé partagée'; String get map_sharedPin => 'Clé partagée';
@override
String get map_sharedAt => 'Partagé';
@override @override
String get map_joinRoom => 'Rejoindre le room server'; String get map_joinRoom => 'Rejoindre le room server';
@@ -3253,6 +3298,37 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Terrain'; String get losLegendTerrain => 'Terrain';
@override
String get losBlockedSpotsTitle => 'Places occupés';
@override
String get losBlockedSpotsHint =>
'Sélectionnez un emplacement bloqué pour le mettre en évidence sur la carte.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Obstruction sélectionnée';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Bloqué par $obstruction, à une hauteur de $heightUnit, à une distance de $distanceFromA par rapport à A et à une distance de $distanceFromB par rapport à B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Fréquence'; String get losFrequencyLabel => 'Fréquence';
@@ -3728,4 +3804,40 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Langue du système'; String get translation_systemLanguage => 'Langue du système';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+115 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get common_close => 'Bezárás'; String get common_close => 'Bezárás';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Szerkesztés'; String get common_edit => 'Szerkesztés';
@@ -445,6 +448,13 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get settings_actions => 'Tevékenységek'; String get settings_actions => 'Tevékenységek';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Hirdetés küldése'; String get settings_sendAdvertisement => 'Hirdetés küldése';
@@ -486,11 +496,11 @@ class AppLocalizationsHu extends AppLocalizations {
String get settings_debug => 'Hibakeresés'; String get settings_debug => 'Hibakeresés';
@override @override
String get settings_bleDebugLog => 'BLE hibaelhárítási napló'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'BLE parancsok, válaszok és alapvető adatok'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'App-debug log'; String get settings_appDebugLog => 'App-debug log';
@@ -1097,6 +1107,11 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get channels_public => 'A nyilvánosság számára'; String get channels_public => 'A nyilvánosság számára';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Személyes'; String get channels_private => 'Személyes';
@@ -1465,6 +1480,9 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get chat_successes => 'sikerek'; String get chat_successes => 'sikerek';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Törölje a elérési útvonalat'; String get chat_removePath => 'Törölje a elérési útvonalat';
@@ -1565,6 +1583,12 @@ class AppLocalizationsHu extends AppLocalizations {
return 'Olvasatlan: $count'; return 'Olvasatlan: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Nyisd meg a linket?'; String get chat_openLink => 'Nyisd meg a linket?';
@@ -1647,6 +1671,24 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get map_flags => 'Zászló'; String get map_flags => 'Zászló';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Osztja ezt a tartalmat itt'; String get map_shareMarkerHere => 'Osztja ezt a tartalmat itt';
@@ -1733,6 +1775,9 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get map_sharedPin => 'Gemeinsames PIN-kód'; String get map_sharedPin => 'Gemeinsames PIN-kód';
@override
String get map_sharedAt => 'Megosztva';
@override @override
String get map_joinRoom => 'Csatlakozás a szobához'; String get map_joinRoom => 'Csatlakozás a szobához';
@@ -3249,6 +3294,37 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Terület'; String get losLegendTerrain => 'Terület';
@override
String get losBlockedSpotsTitle => 'Foglalhatatlan területek';
@override
String get losBlockedSpotsHint =>
'A blokkolt területet megjelölve, hogy a térképen kiemeljük.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Kiválasztott akadály';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Elakadt a $obstruction miatt, $heightUnit magasságban, $distanceFromA méterrel A-tól és $distanceFromB méterrel B-től ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Hatósság'; String get losFrequencyLabel => 'Hatósság';
@@ -3720,4 +3796,40 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Rendszer nyelvé'; String get translation_systemLanguage => 'Rendszer nyelvé';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+115 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get common_close => 'Chiudi'; String get common_close => 'Chiudi';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Modifica'; String get common_edit => 'Modifica';
@@ -445,6 +448,13 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get settings_actions => 'Azioni'; String get settings_actions => 'Azioni';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Invia Annuncio'; String get settings_sendAdvertisement => 'Invia Annuncio';
@@ -486,11 +496,11 @@ class AppLocalizationsIt extends AppLocalizations {
String get settings_debug => 'Risoluzione dei problemi'; String get settings_debug => 'Risoluzione dei problemi';
@override @override
String get settings_bleDebugLog => 'Log di Debug BLE'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'Comandi, risposte e dati grezzi BLE'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'Log di Debug dell\'App'; String get settings_appDebugLog => 'Log di Debug dell\'App';
@@ -1093,6 +1103,11 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get channels_public => 'Pubblico'; String get channels_public => 'Pubblico';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Privato'; String get channels_private => 'Privato';
@@ -1454,6 +1469,9 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get chat_successes => 'successi'; String get chat_successes => 'successi';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Rimuovi percorso'; String get chat_removePath => 'Rimuovi percorso';
@@ -1556,6 +1574,12 @@ class AppLocalizationsIt extends AppLocalizations {
return 'Non letti: $count'; return 'Non letti: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Aprire il link?'; String get chat_openLink => 'Aprire il link?';
@@ -1637,6 +1661,24 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get map_flags => 'Bandiere'; String get map_flags => 'Bandiere';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Condividi marcatore qui'; String get map_shareMarkerHere => 'Condividi marcatore qui';
@@ -1721,6 +1763,9 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get map_sharedPin => 'Condividi PIN'; String get map_sharedPin => 'Condividi PIN';
@override
String get map_sharedAt => 'Condiviso';
@override @override
String get map_joinRoom => 'Unisciti alla stanza'; String get map_joinRoom => 'Unisciti alla stanza';
@@ -3238,6 +3283,37 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Terreno'; String get losLegendTerrain => 'Terreno';
@override
String get losBlockedSpotsTitle => 'Posti occupati';
@override
String get losBlockedSpotsHint =>
'Tocca un punto bloccato sulla mappa per evidenziarlo.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Ostacolo selezionato';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Frequenza'; String get losFrequencyLabel => 'Frequenza';
@@ -3707,4 +3783,40 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Lingua del sistema'; String get translation_systemLanguage => 'Lingua del sistema';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+114 -2
View File
@@ -44,6 +44,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get common_close => '閉じる'; String get common_close => '閉じる';
@override
String get common_done => 'Done';
@override @override
String get common_edit => '編集'; String get common_edit => '編集';
@@ -422,6 +425,13 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get settings_actions => '行動'; String get settings_actions => '行動';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => '広告を送信する'; String get settings_sendAdvertisement => '広告を送信する';
@@ -460,10 +470,11 @@ class AppLocalizationsJa extends AppLocalizations {
String get settings_debug => 'デバッグ'; String get settings_debug => 'デバッグ';
@override @override
String get settings_bleDebugLog => 'BLE デバッグログ'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => 'BLEコマンド、応答、および生のデータ'; String get settings_companionDebugLogSubtitle =>
'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'アプリケーションのデバッグログ'; String get settings_appDebugLog => 'アプリケーションのデバッグログ';
@@ -1039,6 +1050,11 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get channels_public => '一般の人々'; String get channels_public => '一般の人々';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => '個人の'; String get channels_private => '個人の';
@@ -1392,6 +1408,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get chat_successes => '成功事例'; String get chat_successes => '成功事例';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'パスを削除する'; String get chat_removePath => 'パスを削除する';
@@ -1487,6 +1506,12 @@ class AppLocalizationsJa extends AppLocalizations {
return '未読: $count'; return '未読: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'リンクを開く?'; String get chat_openLink => 'リンクを開く?';
@@ -1565,6 +1590,24 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get map_flags => ''; String get map_flags => '';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'この場所でシェア'; String get map_shareMarkerHere => 'この場所でシェア';
@@ -1648,6 +1691,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get map_sharedPin => '共有パスワード'; String get map_sharedPin => '共有パスワード';
@override
String get map_sharedAt => '共有済み';
@override @override
String get map_joinRoom => '部屋に参加する'; String get map_joinRoom => '部屋に参加する';
@@ -3090,6 +3136,36 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get losLegendTerrain => '地形'; String get losLegendTerrain => '地形';
@override
String get losBlockedSpotsTitle => '利用できない場所';
@override
String get losBlockedSpotsHint => '地図上で、特定された場所を強調するために、該当する場所をタップしてください。';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => '選択された障害';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return '$obstruction によって $heightUnit の高さで、A地点から $distanceFromA、B地点から $distanceFromB ($distanceUnit) の距離で塞がれています。';
}
@override @override
String get losFrequencyLabel => '周波数'; String get losFrequencyLabel => '周波数';
@@ -3526,4 +3602,40 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'システム言語'; String get translation_systemLanguage => 'システム言語';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+114 -2
View File
@@ -44,6 +44,9 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get common_close => '닫기'; String get common_close => '닫기';
@override
String get common_done => 'Done';
@override @override
String get common_edit => '수정'; String get common_edit => '수정';
@@ -422,6 +425,13 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get settings_actions => '행동'; String get settings_actions => '행동';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => '광고 전송'; String get settings_sendAdvertisement => '광고 전송';
@@ -460,10 +470,11 @@ class AppLocalizationsKo extends AppLocalizations {
String get settings_debug => '디버깅'; String get settings_debug => '디버깅';
@override @override
String get settings_bleDebugLog => 'BLE 디버그 로그'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => 'BLE 명령어, 응답 및 원시 데이터'; String get settings_companionDebugLogSubtitle =>
'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => '앱 디버깅 로그'; String get settings_appDebugLog => '앱 디버깅 로그';
@@ -1034,6 +1045,11 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get channels_public => '대중의'; String get channels_public => '대중의';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => '사립'; String get channels_private => '사립';
@@ -1388,6 +1404,9 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get chat_successes => '성공 사례'; String get chat_successes => '성공 사례';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => '경로 제거'; String get chat_removePath => '경로 제거';
@@ -1483,6 +1502,12 @@ class AppLocalizationsKo extends AppLocalizations {
return '읽지 않음: $count'; return '읽지 않음: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => '링크를 열기?'; String get chat_openLink => '링크를 열기?';
@@ -1561,6 +1586,24 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get map_flags => '깃발'; String get map_flags => '깃발';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => '여기에서 마커 공유'; String get map_shareMarkerHere => '여기에서 마커 공유';
@@ -1644,6 +1687,9 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get map_sharedPin => '공유 비밀번호'; String get map_sharedPin => '공유 비밀번호';
@override
String get map_sharedAt => '공유됨';
@override @override
String get map_joinRoom => '방에 참여'; String get map_joinRoom => '방에 참여';
@@ -3090,6 +3136,36 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get losLegendTerrain => '지형'; String get losLegendTerrain => '지형';
@override
String get losBlockedSpotsTitle => '차단된 공간';
@override
String get losBlockedSpotsHint => '지도에서 특정 위치를 강조하려면 해당 위치를 클릭하세요.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => '선택된 장애물';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => '빈도'; String get losFrequencyLabel => '빈도';
@@ -3525,4 +3601,40 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get translation_systemLanguage => '시스템 언어'; String get translation_systemLanguage => '시스템 언어';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+115 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get common_close => 'Sluiten'; String get common_close => 'Sluiten';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Bewerken'; String get common_edit => 'Bewerken';
@@ -440,6 +443,13 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get settings_actions => 'Acties'; String get settings_actions => 'Acties';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Verzend Advertentie'; String get settings_sendAdvertisement => 'Verzend Advertentie';
@@ -480,11 +490,11 @@ class AppLocalizationsNl extends AppLocalizations {
String get settings_debug => 'Debug'; String get settings_debug => 'Debug';
@override @override
String get settings_bleDebugLog => 'BLE Debug Log'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'BLE commando\'s, antwoorden en ruwe data'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'App Debug Log'; String get settings_appDebugLog => 'App Debug Log';
@@ -1082,6 +1092,11 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get channels_public => 'Openbaar'; String get channels_public => 'Openbaar';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Privé'; String get channels_private => 'Privé';
@@ -1442,6 +1457,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get chat_successes => 'Succesvol'; String get chat_successes => 'Succesvol';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Pad verwijderen'; String get chat_removePath => 'Pad verwijderen';
@@ -1543,6 +1561,12 @@ class AppLocalizationsNl extends AppLocalizations {
return 'Nieuw: $count'; return 'Nieuw: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Link openen?'; String get chat_openLink => 'Link openen?';
@@ -1624,6 +1648,24 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get map_flags => 'Vlaggen'; String get map_flags => 'Vlaggen';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Deel marker hier'; String get map_shareMarkerHere => 'Deel marker hier';
@@ -1709,6 +1751,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get map_sharedPin => 'Gedeelde pin'; String get map_sharedPin => 'Gedeelde pin';
@override
String get map_sharedAt => 'Gedeeld';
@override @override
String get map_joinRoom => 'Kamer Toetreden'; String get map_joinRoom => 'Kamer Toetreden';
@@ -3220,6 +3265,37 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Terrein'; String get losLegendTerrain => 'Terrein';
@override
String get losBlockedSpotsTitle => 'Geplande plaatsen';
@override
String get losBlockedSpotsHint =>
'Tik op een geblokkeerd gebied om het op de kaart te markeren.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Geselecteerde obstakel';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Frequentie'; String get losFrequencyLabel => 'Frequentie';
@@ -3684,4 +3760,40 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Taal van het systeem'; String get translation_systemLanguage => 'Taal van het systeem';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+115 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get common_close => 'Zamknij'; String get common_close => 'Zamknij';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Edytuj'; String get common_edit => 'Edytuj';
@@ -448,6 +451,13 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get settings_actions => 'Działania'; String get settings_actions => 'Działania';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Wyślij rozgłoszenie'; String get settings_sendAdvertisement => 'Wyślij rozgłoszenie';
@@ -488,11 +498,11 @@ class AppLocalizationsPl extends AppLocalizations {
String get settings_debug => 'Debug'; String get settings_debug => 'Debug';
@override @override
String get settings_bleDebugLog => 'Dziennik debugowania BLE'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'Polecenia BLE, odpowiedzi i surowe dane'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'Dziennik debugowania aplikacji'; String get settings_appDebugLog => 'Dziennik debugowania aplikacji';
@@ -1102,6 +1112,11 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get channels_public => 'Publiczny'; String get channels_public => 'Publiczny';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Prywatny'; String get channels_private => 'Prywatny';
@@ -1466,6 +1481,9 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get chat_successes => 'Sukcesy'; String get chat_successes => 'Sukcesy';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Usuń ścieżkę'; String get chat_removePath => 'Usuń ścieżkę';
@@ -1567,6 +1585,12 @@ class AppLocalizationsPl extends AppLocalizations {
return 'Nieprzeczytane: $count'; return 'Nieprzeczytane: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Otworzyć link?'; String get chat_openLink => 'Otworzyć link?';
@@ -1648,6 +1672,24 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get map_flags => 'Flagi'; String get map_flags => 'Flagi';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Udostępnij znacznik tutaj'; String get map_shareMarkerHere => 'Udostępnij znacznik tutaj';
@@ -1733,6 +1775,9 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get map_sharedPin => 'Udostępniona pinezka'; String get map_sharedPin => 'Udostępniona pinezka';
@override
String get map_sharedAt => 'Udostępnione';
@override @override
String get map_joinRoom => 'Dołącz do pokoju'; String get map_joinRoom => 'Dołącz do pokoju';
@@ -3245,6 +3290,37 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Teren'; String get losLegendTerrain => 'Teren';
@override
String get losBlockedSpotsTitle => 'Zablokowane miejsca';
@override
String get losBlockedSpotsHint =>
'Kliknij zablokowane miejsce, aby je zaznaczyć na mapie.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Wybór przeszkody';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Częstotliwość'; String get losFrequencyLabel => 'Częstotliwość';
@@ -3715,4 +3791,40 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Język systemu'; String get translation_systemLanguage => 'Język systemu';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+115 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get common_close => 'Fechar'; String get common_close => 'Fechar';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Editar'; String get common_edit => 'Editar';
@@ -444,6 +447,13 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get settings_actions => 'Ações'; String get settings_actions => 'Ações';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Enviar Publicidade'; String get settings_sendAdvertisement => 'Enviar Publicidade';
@@ -486,11 +496,11 @@ class AppLocalizationsPt extends AppLocalizations {
String get settings_debug => 'Depurar'; String get settings_debug => 'Depurar';
@override @override
String get settings_bleDebugLog => 'Log de Depuração BLE'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'Comandos, respostas e dados brutos do BLE'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'Log de Depuração do Aplicativo'; String get settings_appDebugLog => 'Log de Depuração do Aplicativo';
@@ -1093,6 +1103,11 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get channels_public => 'Público'; String get channels_public => 'Público';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Privado'; String get channels_private => 'Privado';
@@ -1453,6 +1468,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get chat_successes => 'Sucessos'; String get chat_successes => 'Sucessos';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Remover caminho'; String get chat_removePath => 'Remover caminho';
@@ -1554,6 +1572,12 @@ class AppLocalizationsPt extends AppLocalizations {
return 'Não lido: $count'; return 'Não lido: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Abrir link?'; String get chat_openLink => 'Abrir link?';
@@ -1636,6 +1660,24 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get map_flags => 'Bandeiras'; String get map_flags => 'Bandeiras';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Compartilhar marcador aqui'; String get map_shareMarkerHere => 'Compartilhar marcador aqui';
@@ -1721,6 +1763,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get map_sharedPin => 'Pin compartilhado'; String get map_sharedPin => 'Pin compartilhado';
@override
String get map_sharedAt => 'Compartilhado';
@override @override
String get map_joinRoom => 'Junte-se à Sala'; String get map_joinRoom => 'Junte-se à Sala';
@@ -3234,6 +3279,37 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Terreno'; String get losLegendTerrain => 'Terreno';
@override
String get losBlockedSpotsTitle => 'Locais ocupados';
@override
String get losBlockedSpotsHint =>
'Toque em um ponto bloqueado para destacá-lo no mapa.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Obstrução selecionada';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Frequência'; String get losFrequencyLabel => 'Frequência';
@@ -3698,4 +3774,40 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Idioma do sistema'; String get translation_systemLanguage => 'Idioma do sistema';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+116 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get common_close => 'Закрыть'; String get common_close => 'Закрыть';
@override
String get common_done => 'Готово';
@override @override
String get common_edit => 'Изменить'; String get common_edit => 'Изменить';
@@ -444,6 +447,13 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get settings_actions => 'Действия'; String get settings_actions => 'Действия';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Отправить анонсирование'; String get settings_sendAdvertisement => 'Отправить анонсирование';
@@ -485,11 +495,11 @@ class AppLocalizationsRu extends AppLocalizations {
String get settings_debug => 'Отладка'; String get settings_debug => 'Отладка';
@override @override
String get settings_bleDebugLog => 'Журнал отладки BLE'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'Команды BLE, ответы и сырые данные'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'Журнал отладки приложения'; String get settings_appDebugLog => 'Журнал отладки приложения';
@@ -1093,6 +1103,11 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get channels_public => 'Публичный'; String get channels_public => 'Публичный';
@override
String channels_via(String path) {
return 'через $path';
}
@override @override
String get channels_private => 'Приватный'; String get channels_private => 'Приватный';
@@ -1456,6 +1471,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get chat_successes => 'успешно'; String get chat_successes => 'успешно';
@override
String get chat_score => 'Оценка';
@override @override
String get chat_removePath => 'Удалить маршрут'; String get chat_removePath => 'Удалить маршрут';
@@ -1558,6 +1576,12 @@ class AppLocalizationsRu extends AppLocalizations {
return 'Непрочитанных: $count'; return 'Непрочитанных: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Открыть ссылку?'; String get chat_openLink => 'Открыть ссылку?';
@@ -1639,6 +1663,24 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get map_flags => 'Флаги'; String get map_flags => 'Флаги';
@override
String get map_type => 'Тип';
@override
String get map_path => 'Путь';
@override
String get map_location => 'Местоположение';
@override
String get map_estLocation => 'Прибл. местоположение';
@override
String get map_publicKey => 'Публичный ключ';
@override
String get map_publicKeyPrefixHint => 'напр. ab12';
@override @override
String get map_shareMarkerHere => 'Поделиться меткой здесь'; String get map_shareMarkerHere => 'Поделиться меткой здесь';
@@ -1724,6 +1766,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get map_sharedPin => 'Общая метка'; String get map_sharedPin => 'Общая метка';
@override
String get map_sharedAt => 'Поделено';
@override @override
String get map_joinRoom => 'Присоединиться к комнате'; String get map_joinRoom => 'Присоединиться к комнате';
@@ -3239,6 +3284,38 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Рельеф'; String get losLegendTerrain => 'Рельеф';
@override
String get losBlockedSpotsTitle => 'Зарезервированные места';
@override
String get losBlockedSpotsHint =>
'Щелкните по заблокированной области, чтобы выделить ее на карте.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle =>
'Выбранный объект, препятствующий движению';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Частота'; String get losFrequencyLabel => 'Частота';
@@ -3712,4 +3789,40 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Язык системы'; String get translation_systemLanguage => 'Язык системы';
@override
String get background_serviceTitle => 'MeshCore работает';
@override
String get background_serviceText => 'Поддерживает BLE-соединение';
@override
String appSettings_translationModelDeleted(String name) {
return 'Удалено $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Не удалось удалить: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Не удалось обновить канал: $error';
}
@override
String get contact_typeChat => 'Чат';
@override
String get contact_typeRepeater => 'Ретранслятор';
@override
String get contact_typeRoom => 'Комната';
@override
String get contact_typeSensor => 'Датчик';
@override
String get contact_typeUnknown => 'Неизвестно';
} }
+115 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get common_close => 'Zavrieť'; String get common_close => 'Zavrieť';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Upraviť'; String get common_edit => 'Upraviť';
@@ -439,6 +442,13 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get settings_actions => 'Možné akcie'; String get settings_actions => 'Možné akcie';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Odoslať reklamu'; String get settings_sendAdvertisement => 'Odoslať reklamu';
@@ -480,11 +490,11 @@ class AppLocalizationsSk extends AppLocalizations {
String get settings_debug => 'Ladenie'; String get settings_debug => 'Ladenie';
@override @override
String get settings_bleDebugLog => 'Log BLE Debug'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'Príkazy BLE, odpovede a surové dáta'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'Záznam ladenia aplikácie'; String get settings_appDebugLog => 'Záznam ladenia aplikácie';
@@ -1082,6 +1092,11 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get channels_public => 'Veľké verejné'; String get channels_public => 'Veľké verejné';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Osobné'; String get channels_private => 'Osobné';
@@ -1443,6 +1458,9 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get chat_successes => 'Úspechy'; String get chat_successes => 'Úspechy';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Odstrániť cestu'; String get chat_removePath => 'Odstrániť cestu';
@@ -1544,6 +1562,12 @@ class AppLocalizationsSk extends AppLocalizations {
return 'Nezriadené: $count'; return 'Nezriadené: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Otvoriť odkaz?'; String get chat_openLink => 'Otvoriť odkaz?';
@@ -1625,6 +1649,24 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get map_flags => 'Zástavy'; String get map_flags => 'Zástavy';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Zdieľte značku tu'; String get map_shareMarkerHere => 'Zdieľte značku tu';
@@ -1710,6 +1752,9 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get map_sharedPin => 'Zdieľaný PIN'; String get map_sharedPin => 'Zdieľaný PIN';
@override
String get map_sharedAt => 'Zdieľané';
@override @override
String get map_joinRoom => 'Pripojiť miestnosť'; String get map_joinRoom => 'Pripojiť miestnosť';
@@ -3214,6 +3259,37 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Terén'; String get losLegendTerrain => 'Terén';
@override
String get losBlockedSpotsTitle => 'Zablokované miesta';
@override
String get losBlockedSpotsHint =>
'Kliknite na zablokované miesto, aby ste ho zvýraznili na mape.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Vybraná prekážka';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Frekvencia'; String get losFrequencyLabel => 'Frekvencia';
@@ -3678,4 +3754,40 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Jazyk systému'; String get translation_systemLanguage => 'Jazyk systému';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+115 -3
View File
@@ -44,6 +44,9 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get common_close => 'Zapri'; String get common_close => 'Zapri';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Uredi'; String get common_edit => 'Uredi';
@@ -438,6 +441,13 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get settings_actions => 'Akcije'; String get settings_actions => 'Akcije';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Pošlji Oglas'; String get settings_sendAdvertisement => 'Pošlji Oglas';
@@ -478,11 +488,11 @@ class AppLocalizationsSl extends AppLocalizations {
String get settings_debug => 'Debug'; String get settings_debug => 'Debug';
@override @override
String get settings_bleDebugLog => 'BLE debug log (razhroščevanje)'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => String get settings_companionDebugLogSubtitle =>
'BLE ukazi, odgovori in surovi podatki'; 'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'Logi aplikacije'; String get settings_appDebugLog => 'Logi aplikacije';
@@ -1080,6 +1090,11 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get channels_public => 'Javni'; String get channels_public => 'Javni';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Zasebni'; String get channels_private => 'Zasebni';
@@ -1440,6 +1455,9 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get chat_successes => 'Uspešni'; String get chat_successes => 'Uspešni';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Izbriši pot'; String get chat_removePath => 'Izbriši pot';
@@ -1539,6 +1557,12 @@ class AppLocalizationsSl extends AppLocalizations {
return 'Nerešeno: $count'; return 'Nerešeno: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Odpreti povezavo?'; String get chat_openLink => 'Odpreti povezavo?';
@@ -1621,6 +1645,24 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get map_flags => 'Zapestnice'; String get map_flags => 'Zapestnice';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Delite točke tukaj.'; String get map_shareMarkerHere => 'Delite točke tukaj.';
@@ -1705,6 +1747,9 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get map_sharedPin => 'Deljeno naslovno geslo'; String get map_sharedPin => 'Deljeno naslovno geslo';
@override
String get map_sharedAt => 'Deljeno';
@override @override
String get map_joinRoom => 'Pridružiti sobo'; String get map_joinRoom => 'Pridružiti sobo';
@@ -3215,6 +3260,37 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Teren'; String get losLegendTerrain => 'Teren';
@override
String get losBlockedSpotsTitle => 'Zasedena parkirišča';
@override
String get losBlockedSpotsHint =>
'Dotaknite blokirano točko, da jo označite na zemljeplati.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Izbrano ovire';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Frekvenca'; String get losFrequencyLabel => 'Frekvenca';
@@ -3682,4 +3758,40 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Jezik sistema'; String get translation_systemLanguage => 'Jezik sistema';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+115 -2
View File
@@ -44,6 +44,9 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get common_close => 'Stänga'; String get common_close => 'Stänga';
@override
String get common_done => 'Done';
@override @override
String get common_edit => 'Redigera'; String get common_edit => 'Redigera';
@@ -436,6 +439,13 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get settings_actions => 'Åtgärder'; String get settings_actions => 'Åtgärder';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => 'Skicka Annons'; String get settings_sendAdvertisement => 'Skicka Annons';
@@ -475,10 +485,11 @@ class AppLocalizationsSv extends AppLocalizations {
String get settings_debug => 'Felsök'; String get settings_debug => 'Felsök';
@override @override
String get settings_bleDebugLog => 'BLE-felsökning'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => 'BLE-kommandon, svar och rådata'; String get settings_companionDebugLogSubtitle =>
'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => 'Appfelsökning'; String get settings_appDebugLog => 'Appfelsökning';
@@ -1073,6 +1084,11 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get channels_public => 'Offentligt'; String get channels_public => 'Offentligt';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => 'Privat'; String get channels_private => 'Privat';
@@ -1435,6 +1451,9 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get chat_successes => 'framgångar'; String get chat_successes => 'framgångar';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => 'Ta bort sökväg'; String get chat_removePath => 'Ta bort sökväg';
@@ -1533,6 +1552,12 @@ class AppLocalizationsSv extends AppLocalizations {
return 'Olästa: $count'; return 'Olästa: $count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => 'Öppna länk?'; String get chat_openLink => 'Öppna länk?';
@@ -1614,6 +1639,24 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get map_flags => 'Flaggor'; String get map_flags => 'Flaggor';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => 'Dela markeringen här'; String get map_shareMarkerHere => 'Dela markeringen här';
@@ -1699,6 +1742,9 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get map_sharedPin => 'Delad PIN'; String get map_sharedPin => 'Delad PIN';
@override
String get map_sharedAt => 'Delad';
@override @override
String get map_joinRoom => 'Gå med i rum'; String get map_joinRoom => 'Gå med i rum';
@@ -3197,6 +3243,37 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get losLegendTerrain => 'Terräng'; String get losLegendTerrain => 'Terräng';
@override
String get losBlockedSpotsTitle => 'Reserverade platser';
@override
String get losBlockedSpotsHint =>
'Klicka på en markerad plats för att framhäva den på kartan.';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => 'Vald hinder';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => 'Frekvens'; String get losFrequencyLabel => 'Frekvens';
@@ -3660,4 +3737,40 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get translation_systemLanguage => 'Språk för systemet'; String get translation_systemLanguage => 'Språk för systemet';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
File diff suppressed because it is too large Load Diff
+114 -2
View File
@@ -44,6 +44,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get common_close => '关闭'; String get common_close => '关闭';
@override
String get common_done => 'Done';
@override @override
String get common_edit => '编辑'; String get common_edit => '编辑';
@@ -416,6 +419,13 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get settings_actions => '操作'; String get settings_actions => '操作';
@override
String get settings_deleteAllPaths => 'Delete All Paths';
@override
String get settings_deleteAllPathsSubtitle =>
'Clear all path data from contacts.';
@override @override
String get settings_sendAdvertisement => '发送广播'; String get settings_sendAdvertisement => '发送广播';
@@ -453,10 +463,11 @@ class AppLocalizationsZh extends AppLocalizations {
String get settings_debug => '调试'; String get settings_debug => '调试';
@override @override
String get settings_bleDebugLog => 'BLE 调试日志'; String get settings_companionDebugLog => 'Companion Debug Log';
@override @override
String get settings_bleDebugLogSubtitle => 'BLE 命令、响应和原始数据'; String get settings_companionDebugLogSubtitle =>
'BLE/TCP/USB commands, responses, and raw data';
@override @override
String get settings_appDebugLog => '应用调试日志'; String get settings_appDebugLog => '应用调试日志';
@@ -1021,6 +1032,11 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get channels_public => '公共'; String get channels_public => '公共';
@override
String channels_via(String path) {
return 'via $path';
}
@override @override
String get channels_private => '私有'; String get channels_private => '私有';
@@ -1368,6 +1384,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get chat_successes => '成功'; String get chat_successes => '成功';
@override
String get chat_score => 'Score';
@override @override
String get chat_removePath => '移除路径'; String get chat_removePath => '移除路径';
@@ -1455,6 +1474,12 @@ class AppLocalizationsZh extends AppLocalizations {
return '未读:$count'; return '未读:$count';
} }
@override
String get chat_markAsUnread => 'Mark as Unread';
@override
String get chat_newMessages => 'New messages';
@override @override
String get chat_openLink => '打开链接?'; String get chat_openLink => '打开链接?';
@@ -1533,6 +1558,24 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get map_flags => '标志'; String get map_flags => '标志';
@override
String get map_type => 'Type';
@override
String get map_path => 'Path';
@override
String get map_location => 'Location';
@override
String get map_estLocation => 'Est. Location';
@override
String get map_publicKey => 'Public Key';
@override
String get map_publicKeyPrefixHint => 'e.g. ab12';
@override @override
String get map_shareMarkerHere => '在此分享标记'; String get map_shareMarkerHere => '在此分享标记';
@@ -1616,6 +1659,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get map_sharedPin => '共享标记'; String get map_sharedPin => '共享标记';
@override
String get map_sharedAt => '已分享';
@override @override
String get map_joinRoom => '加入房间'; String get map_joinRoom => '加入房间';
@@ -3014,6 +3060,36 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get losLegendTerrain => '地形'; String get losLegendTerrain => '地形';
@override
String get losBlockedSpotsTitle => '被占用区域';
@override
String get losBlockedSpotsHint => '点击地图上的某个被遮盖的区域,以突出显示该区域。';
@override
String losBlockedSpotChip(
String distance,
String distanceUnit,
String obstruction,
String heightUnit,
) {
return '$distance $distanceUnit$obstruction $heightUnit';
}
@override
String get losSelectedObstructionTitle => '选择性阻碍';
@override
String losSelectedObstructionDetails(
String obstruction,
String heightUnit,
String distanceFromA,
String distanceUnit,
String distanceFromB,
) {
return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).';
}
@override @override
String get losFrequencyLabel => '频率'; String get losFrequencyLabel => '频率';
@@ -3422,4 +3498,40 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get translation_systemLanguage => '系统语言'; String get translation_systemLanguage => '系统语言';
@override
String get background_serviceTitle => 'MeshCore running';
@override
String get background_serviceText => 'Keeping BLE connected';
@override
String appSettings_translationModelDeleted(String name) {
return 'Deleted $name';
}
@override
String appSettings_translationModelDeleteFailed(String error) {
return 'Failed to delete: $error';
}
@override
String channels_channelUpdateFailed(String error) {
return 'Failed to update channel: $error';
}
@override
String get contact_typeChat => 'Chat';
@override
String get contact_typeRepeater => 'Repeater';
@override
String get contact_typeRoom => 'Room';
@override
String get contact_typeSensor => 'Sensor';
@override
String get contact_typeUnknown => 'Unknown';
} }
+84 -1
View File
@@ -104,6 +104,8 @@
"settings_privacyModeEnabled": "Privacy modus is ingeschakeld", "settings_privacyModeEnabled": "Privacy modus is ingeschakeld",
"settings_privacyModeDisabled": "Privacy modus is uitgeschakeld", "settings_privacyModeDisabled": "Privacy modus is uitgeschakeld",
"settings_actions": "Acties", "settings_actions": "Acties",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Verzend Advertentie", "settings_sendAdvertisement": "Verzend Advertentie",
"settings_sendAdvertisementSubtitle": "Nu aanwezigheid uitzenden", "settings_sendAdvertisementSubtitle": "Nu aanwezigheid uitzenden",
"settings_advertisementSent": "Advertentie verzonden", "settings_advertisementSent": "Advertentie verzonden",
@@ -2066,5 +2068,86 @@
"room_guest": "Informatie over de server", "room_guest": "Informatie over de server",
"chat_sendMessage": "Verzend bericht", "chat_sendMessage": "Verzend bericht",
"repeater_guest": "Informatie over herhalingsapparatuur", "repeater_guest": "Informatie over herhalingsapparatuur",
"settings_multiAck": "Meerdere bevestigingen" "common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"settings_multiAck": "Meerdere bevestigingen",
"map_sharedAt": "Gedeeld",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losSelectedObstructionTitle": "Geselecteerde obstakel",
"losBlockedSpotsHint": "Tik op een geblokkeerd gebied om het op de kaart te markeren.",
"losBlockedSpotsTitle": "Geplande plaatsen",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})."
} }
+84 -1
View File
@@ -104,6 +104,8 @@
"settings_privacyModeEnabled": "Tryb prywatności włączony", "settings_privacyModeEnabled": "Tryb prywatności włączony",
"settings_privacyModeDisabled": "Tryb prywatności wyłączony", "settings_privacyModeDisabled": "Tryb prywatności wyłączony",
"settings_actions": "Działania", "settings_actions": "Działania",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Wyślij rozgłoszenie", "settings_sendAdvertisement": "Wyślij rozgłoszenie",
"settings_sendAdvertisementSubtitle": "Nadaj obecność teraz", "settings_sendAdvertisementSubtitle": "Nadaj obecność teraz",
"settings_advertisementSent": "Rozgłoszenie wysłane", "settings_advertisementSent": "Rozgłoszenie wysłane",
@@ -2104,5 +2106,86 @@
"repeater_guestTools": "Narzędzia dla gości", "repeater_guestTools": "Narzędzia dla gości",
"repeater_guest": "Informacje dotyczące urządzenia powtarzającego", "repeater_guest": "Informacje dotyczące urządzenia powtarzającego",
"room_guest": "Informacje o serwerze", "room_guest": "Informacje o serwerze",
"settings_multiAck": "Wielokrotne potwierdzenia odbioru" "common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"settings_multiAck": "Wielokrotne potwierdzenia odbioru",
"map_sharedAt": "Udostępnione",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losSelectedObstructionTitle": "Wybór przeszkody",
"losBlockedSpotsTitle": "Zablokowane miejsca",
"losBlockedSpotsHint": "Kliknij zablokowane miejsce, aby je zaznaczyć na mapie.",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})."
} }
+84 -1
View File
@@ -104,6 +104,8 @@
"settings_privacyModeEnabled": "Modo de privacidade ativado", "settings_privacyModeEnabled": "Modo de privacidade ativado",
"settings_privacyModeDisabled": "Modo de privacidade desativado", "settings_privacyModeDisabled": "Modo de privacidade desativado",
"settings_actions": "Ações", "settings_actions": "Ações",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Enviar Publicidade", "settings_sendAdvertisement": "Enviar Publicidade",
"settings_sendAdvertisementSubtitle": "Presença de transmissão agora", "settings_sendAdvertisementSubtitle": "Presença de transmissão agora",
"settings_advertisementSent": "Anúncio enviado", "settings_advertisementSent": "Anúncio enviado",
@@ -2066,5 +2068,86 @@
"room_guest": "Informações do Servidor", "room_guest": "Informações do Servidor",
"chat_sendMessage": "Enviar mensagem", "chat_sendMessage": "Enviar mensagem",
"repeater_guest": "Informações sobre repetidores", "repeater_guest": "Informações sobre repetidores",
"repeater_guestTools": "Ferramentas para hóspedes" "repeater_guestTools": "Ferramentas para hóspedes",
"common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"map_sharedAt": "Compartilhado",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losBlockedSpotsTitle": "Locais ocupados",
"losBlockedSpotsHint": "Toque em um ponto bloqueado para destacá-lo no mapa.",
"losSelectedObstructionTitle": "Obstrução selecionada",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})."
} }
+84 -1
View File
@@ -81,6 +81,8 @@
"settings_privacyModeEnabled": "Режим конфиденциальности включен", "settings_privacyModeEnabled": "Режим конфиденциальности включен",
"settings_privacyModeDisabled": "Режим конфиденциальности выключен", "settings_privacyModeDisabled": "Режим конфиденциальности выключен",
"settings_actions": "Действия", "settings_actions": "Действия",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Отправить анонсирование", "settings_sendAdvertisement": "Отправить анонсирование",
"settings_sendAdvertisementSubtitle": "Отправить анонсирование о присутствии сейчас", "settings_sendAdvertisementSubtitle": "Отправить анонсирование о присутствии сейчас",
"settings_advertisementSent": "Анонсирование отправлено", "settings_advertisementSent": "Анонсирование отправлено",
@@ -1306,5 +1308,86 @@
"repeater_guest": "Информация о ретрансляторе", "repeater_guest": "Информация о ретрансляторе",
"room_guest": "Информация о сервере", "room_guest": "Информация о сервере",
"repeater_guestTools": "Инструменты для гостей", "repeater_guestTools": "Инструменты для гостей",
"settings_multiAck": "Несколько подтверждений" "common_done": "Готово",
"background_serviceTitle": "MeshCore работает",
"background_serviceText": "Поддерживает BLE-соединение",
"appSettings_translationModelDeleted": "Удалено {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Не удалось удалить: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Не удалось обновить канал: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Тип",
"map_path": "Путь",
"map_location": "Местоположение",
"map_estLocation": "Прибл. местоположение",
"map_publicKey": "Публичный ключ",
"map_publicKeyPrefixHint": "напр. ab12",
"contact_typeChat": "Чат",
"contact_typeRepeater": "Ретранслятор",
"contact_typeRoom": "Комната",
"contact_typeSensor": "Датчик",
"contact_typeUnknown": "Неизвестно",
"channels_via": "через {path}",
"chat_score": "Оценка",
"settings_multiAck": "Несколько подтверждений",
"map_sharedAt": "Поделено",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losBlockedSpotsHint": "Щелкните по заблокированной области, чтобы выделить ее на карте.",
"losBlockedSpotsTitle": "Зарезервированные места",
"losSelectedObstructionTitle": "Выбранный объект, препятствующий движению",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})."
} }
+84 -1
View File
@@ -104,6 +104,8 @@
"settings_privacyModeEnabled": "Ochranný režim je povolený.", "settings_privacyModeEnabled": "Ochranný režim je povolený.",
"settings_privacyModeDisabled": "Ochranný režim je vypnutý", "settings_privacyModeDisabled": "Ochranný režim je vypnutý",
"settings_actions": "Možné akcie", "settings_actions": "Možné akcie",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Odoslať reklamu", "settings_sendAdvertisement": "Odoslať reklamu",
"settings_sendAdvertisementSubtitle": "Momentálne priezornejšie.", "settings_sendAdvertisementSubtitle": "Momentálne priezornejšie.",
"settings_advertisementSent": "Reklama odeslaná", "settings_advertisementSent": "Reklama odeslaná",
@@ -2066,5 +2068,86 @@
"chat_sendMessage": "Odoslať správu", "chat_sendMessage": "Odoslať správu",
"repeater_guest": "Informácie o opakovači", "repeater_guest": "Informácie o opakovači",
"room_guest": "Informácie o serveri", "room_guest": "Informácie o serveri",
"repeater_guestTools": "Nástroje pre hostí" "repeater_guestTools": "Nástroje pre hostí",
"common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"map_sharedAt": "Zdieľané",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losBlockedSpotsTitle": "Zablokované miesta",
"losBlockedSpotsHint": "Kliknite na zablokované miesto, aby ste ho zvýraznili na mape.",
"losSelectedObstructionTitle": "Vybraná prekážka",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})."
} }
+84 -1
View File
@@ -104,6 +104,8 @@
"settings_privacyModeEnabled": "Privatni način je omogočen.", "settings_privacyModeEnabled": "Privatni način je omogočen.",
"settings_privacyModeDisabled": "Privatni način je onemogočen.", "settings_privacyModeDisabled": "Privatni način je onemogočen.",
"settings_actions": "Akcije", "settings_actions": "Akcije",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Pošlji Oglas", "settings_sendAdvertisement": "Pošlji Oglas",
"settings_sendAdvertisementSubtitle": "Trenutna prisotnost v oddajah", "settings_sendAdvertisementSubtitle": "Trenutna prisotnost v oddajah",
"settings_advertisementSent": "Oglas poslan", "settings_advertisementSent": "Oglas poslan",
@@ -2066,5 +2068,86 @@
"chat_sendMessage": "Pošlji sporočilo", "chat_sendMessage": "Pošlji sporočilo",
"room_guest": "Informacije o strežniku", "room_guest": "Informacije o strežniku",
"repeater_guestTools": "Naložila za goste", "repeater_guestTools": "Naložila za goste",
"settings_multiAck": "Več potrdil" "common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"settings_multiAck": "Več potrdil",
"map_sharedAt": "Deljeno",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losBlockedSpotsHint": "Dotaknite blokirano točko, da jo označite na zemljeplati.",
"losSelectedObstructionTitle": "Izbrano ovire",
"losBlockedSpotsTitle": "Zasedena parkirišča",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})."
} }
+84 -1
View File
@@ -104,6 +104,8 @@
"settings_privacyModeEnabled": "Privatläget är aktiverat", "settings_privacyModeEnabled": "Privatläget är aktiverat",
"settings_privacyModeDisabled": "Privatläge är avstängt", "settings_privacyModeDisabled": "Privatläge är avstängt",
"settings_actions": "Åtgärder", "settings_actions": "Åtgärder",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "Skicka Annons", "settings_sendAdvertisement": "Skicka Annons",
"settings_sendAdvertisementSubtitle": "Sändning finns nu", "settings_sendAdvertisementSubtitle": "Sändning finns nu",
"settings_advertisementSent": "Annons skickad", "settings_advertisementSent": "Annons skickad",
@@ -2066,5 +2068,86 @@
"chat_sendMessage": "Skicka meddelande", "chat_sendMessage": "Skicka meddelande",
"repeater_guestTools": "Gästverktyg", "repeater_guestTools": "Gästverktyg",
"room_guest": "Information om servern", "room_guest": "Information om servern",
"settings_multiAck": "Flera bekräftelser" "common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"settings_multiAck": "Flera bekräftelser",
"map_sharedAt": "Delad",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losBlockedSpotsTitle": "Reserverade platser",
"losSelectedObstructionTitle": "Vald hinder",
"losBlockedSpotsHint": "Klicka på en markerad plats för att framhäva den på kartan.",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})."
} }
+239 -177
View File
@@ -1,5 +1,5 @@
{ {
"channels_channelDeleteFailed": "Не вдалося видалити канал \"{name}\"", "channels_channelDeleteFailed": "Не вдалось видалити канал \"{name}\"",
"@channels_channelDeleteFailed": { "@channels_channelDeleteFailed": {
"placeholders": { "placeholders": {
"name": { "name": {
@@ -18,6 +18,7 @@
"common_save": "Зберегти", "common_save": "Зберегти",
"common_delete": "Видалити", "common_delete": "Видалити",
"common_close": "Закрити", "common_close": "Закрити",
"common_done": "Готово",
"common_edit": "Редагувати", "common_edit": "Редагувати",
"common_add": "Додати", "common_add": "Додати",
"common_settings": "Налаштування", "common_settings": "Налаштування",
@@ -26,7 +27,7 @@
"common_disconnected": "Відключено", "common_disconnected": "Відключено",
"common_create": "Створити", "common_create": "Створити",
"common_continue": "Продовжити", "common_continue": "Продовжити",
"common_share": "Поділитися", "common_share": "Поділитись",
"common_copy": "Копіювати", "common_copy": "Копіювати",
"common_retry": "Повторити", "common_retry": "Повторити",
"common_hide": "Приховати", "common_hide": "Приховати",
@@ -81,7 +82,7 @@
"device_meshcore": "MeshCore", "device_meshcore": "MeshCore",
"settings_title": "Налаштування", "settings_title": "Налаштування",
"settings_deviceInfo": "Інформація про пристрій", "settings_deviceInfo": "Інформація про пристрій",
"settings_appSettings": "Налаштування програми", "settings_appSettings": "Налаштування застосунку",
"settings_appSettingsSubtitle": "Сповіщення, повідомлення та налаштування карти", "settings_appSettingsSubtitle": "Сповіщення, повідомлення та налаштування карти",
"settings_nodeSettings": "Налаштування вузла", "settings_nodeSettings": "Налаштування вузла",
"settings_nodeName": "Ім'я вузла", "settings_nodeName": "Ім'я вузла",
@@ -91,19 +92,21 @@
"settings_radioSettings": "Налаштування радіо", "settings_radioSettings": "Налаштування радіо",
"settings_radioSettingsSubtitle": "Частота, потужність, коефіцієнт розширення", "settings_radioSettingsSubtitle": "Частота, потужність, коефіцієнт розширення",
"settings_radioSettingsUpdated": "Налаштування радіо оновлено", "settings_radioSettingsUpdated": "Налаштування радіо оновлено",
"settings_location": "Розташування", "settings_location": "Геопозиція",
"settings_locationSubtitle": "GPS координати", "settings_locationSubtitle": "GPS-координати",
"settings_locationUpdated": "Розташування оновлено", "settings_locationUpdated": "Геопозицію оновлено",
"settings_locationBothRequired": "Введіть широту та довготу.", "settings_locationBothRequired": "Введіть широту та довготу.",
"settings_locationInvalid": "Некоректна широта або довгота.", "settings_locationInvalid": "Некоректна широта або довгота.",
"settings_latitude": "Широта", "settings_latitude": "Широта",
"settings_longitude": "Довгота", "settings_longitude": "Довгота",
"settings_privacyMode": "Режим приватності", "settings_privacyMode": "Режим приватності",
"settings_privacyModeSubtitle": "Приховати ім'я/розташування в оголошеннях", "settings_privacyModeSubtitle": "Приховати ім'я/геопозицію в оголошеннях",
"settings_privacyModeToggle": "Увімкніть режим приватності, щоб приховати своє ім'я та місцезнаходження в оголошеннях.", "settings_privacyModeToggle": "Увімкніть режим приватності, щоб приховати своє ім'я та геопозицію в оголошеннях.",
"settings_privacyModeEnabled": "Режим приватності увімкнено", "settings_privacyModeEnabled": "Режим приватності увімкнено",
"settings_privacyModeDisabled": "Режим приватності вимкнено", "settings_privacyModeDisabled": "Режим приватності вимкнено",
"settings_actions": "Дії", "settings_actions": "Дії",
"settings_deleteAllPaths": "Видалити всі шляхи",
"settings_deleteAllPathsSubtitle": "Очистити всі дані шляхів у контактах.",
"settings_sendAdvertisement": "Оголосити себе", "settings_sendAdvertisement": "Оголосити себе",
"settings_sendAdvertisementSubtitle": "Транслювати присутність зараз", "settings_sendAdvertisementSubtitle": "Транслювати присутність зараз",
"settings_advertisementSent": "Оголошення надіслано", "settings_advertisementSent": "Оголошення надіслано",
@@ -118,9 +121,9 @@
"settings_debug": "Налагодження", "settings_debug": "Налагодження",
"settings_bleDebugLog": "Журнал налагодження BLE", "settings_bleDebugLog": "Журнал налагодження BLE",
"settings_bleDebugLogSubtitle": "Команди BLE, відповіді та необроблені дані", "settings_bleDebugLogSubtitle": "Команди BLE, відповіді та необроблені дані",
"settings_appDebugLog": "Журнал налагодження програми", "settings_appDebugLog": "Журнал налагодження застосунку",
"settings_appDebugLogSubtitle": "Повідомлення налагодження програми", "settings_appDebugLogSubtitle": "Повідомлення налагодження застосунку",
"settings_about": "Про програму", "settings_about": "Про застосунок",
"settings_aboutVersion": "MeshCore Open v{version}", "settings_aboutVersion": "MeshCore Open v{version}",
"@settings_aboutVersion": { "@settings_aboutVersion": {
"placeholders": { "placeholders": {
@@ -156,7 +159,7 @@
} }
} }
}, },
"appSettings_title": "Налаштування програми", "appSettings_title": "Налаштування застосунку",
"appSettings_appearance": "Вигляд", "appSettings_appearance": "Вигляд",
"appSettings_theme": "Тема", "appSettings_theme": "Тема",
"appSettings_themeSystem": "Системна", "appSettings_themeSystem": "Системна",
@@ -194,9 +197,9 @@
"appSettings_clearPathOnMaxRetry": "Очищати шлях після макс. спроб", "appSettings_clearPathOnMaxRetry": "Очищати шлях після макс. спроб",
"appSettings_clearPathOnMaxRetrySubtitle": "Скидати шлях до контакту після 5 невдалих спроб надсилання", "appSettings_clearPathOnMaxRetrySubtitle": "Скидати шлях до контакту після 5 невдалих спроб надсилання",
"appSettings_pathsWillBeCleared": "Шляхи будуть очищені після 5 невдалих спроб.", "appSettings_pathsWillBeCleared": "Шляхи будуть очищені після 5 невдалих спроб.",
"appSettings_pathsWillNotBeCleared": "Шляхи не будуть очищатися автоматично.", "appSettings_pathsWillNotBeCleared": "Шляхи не будуть очищатись автоматично.",
"appSettings_autoRouteRotation": "Авторотація маршруту", "appSettings_autoRouteRotation": "Авторотація маршруту",
"appSettings_autoRouteRotationSubtitle": "Чергувати найкращі шляхи та режим «на всю мережу» (flood)", "appSettings_autoRouteRotationSubtitle": "Чергувати найкращі шляхи та режим «через всю мережу» (flood)",
"appSettings_autoRouteRotationEnabled": "Авторотація маршрутизації увімкнена", "appSettings_autoRouteRotationEnabled": "Авторотація маршрутизації увімкнена",
"appSettings_autoRouteRotationDisabled": "Авторотація маршрутизації вимкнена", "appSettings_autoRouteRotationDisabled": "Авторотація маршрутизації вимкнена",
"appSettings_battery": "Батарея", "appSettings_battery": "Батарея",
@@ -251,10 +254,10 @@
} }
}, },
"appSettings_debugCard": "Налагодження", "appSettings_debugCard": "Налагодження",
"appSettings_appDebugLogging": "Логування налагодження програми", "appSettings_appDebugLogging": "Журналювання налагодження застосунку",
"appSettings_appDebugLoggingSubtitle": "Записувати повідомлення налагодження програми в лог для усунення несправностей.", "appSettings_appDebugLoggingSubtitle": "Записувати повідомлення налагодження застосунку в журнал для усунення несправностей.",
"appSettings_appDebugLoggingEnabled": "Логування налагодження програми увімкнено", "appSettings_appDebugLoggingEnabled": "Журналювання налагодження застосунку увімкнено",
"appSettings_appDebugLoggingDisabled": "Налагодження програми вимкнено.", "appSettings_appDebugLoggingDisabled": "Журналювання налагодження застосунку вимкнено.",
"contacts_title": "Контакти", "contacts_title": "Контакти",
"contacts_noContacts": "Контактів не знайдено.", "contacts_noContacts": "Контактів не знайдено.",
"contacts_contactsWillAppear": "Контакти з'являться, коли пристрої надішлють оголошення.", "contacts_contactsWillAppear": "Контакти з'являться, коли пристрої надішлють оголошення.",
@@ -338,8 +341,9 @@
} }
} }
}, },
"channels_hashtagChannel": "Канал з хештегом", "channels_hashtagChannel": "Хештег-канал",
"channels_public": "Публічний", "channels_public": "Публічний",
"channels_via": "через {path}",
"channels_private": "Приватний", "channels_private": "Приватний",
"channels_publicChannel": "Публічний канал", "channels_publicChannel": "Публічний канал",
"channels_privateChannel": "Приватний канал", "channels_privateChannel": "Приватний канал",
@@ -371,7 +375,7 @@
"channels_pskHex": "PSK (Hex)", "channels_pskHex": "PSK (Hex)",
"channels_generateRandomPsk": "Згенерувати випадковий ключ PSK", "channels_generateRandomPsk": "Згенерувати випадковий ключ PSK",
"channels_enterChannelName": "Будь ласка, введіть назву каналу", "channels_enterChannelName": "Будь ласка, введіть назву каналу",
"channels_pskMustBe32Hex": "PSK має складатися з 32 шістнадцяткових символів.", "channels_pskMustBe32Hex": "PSK має складатись з 32 шістнадцяткових символів.",
"channels_channelAdded": "Канал «{name}» додано", "channels_channelAdded": "Канал «{name}» додано",
"@channels_channelAdded": { "@channels_channelAdded": {
"placeholders": { "placeholders": {
@@ -422,7 +426,7 @@
} }
} }
}, },
"chat_location": "Розташування", "chat_location": "Геопозиція",
"chat_sendMessageTo": "Надіслати повідомлення {contactName}", "chat_sendMessageTo": "Надіслати повідомлення {contactName}",
"@chat_sendMessageTo": { "@chat_sendMessageTo": {
"placeholders": { "placeholders": {
@@ -466,17 +470,17 @@
"gifPicker_searchHint": "Пошук GIF...", "gifPicker_searchHint": "Пошук GIF...",
"gifPicker_poweredBy": "На базі GIPHY", "gifPicker_poweredBy": "На базі GIPHY",
"gifPicker_noGifsFound": "GIF не знайдено", "gifPicker_noGifsFound": "GIF не знайдено",
"gifPicker_failedLoad": "Не вдалося завантажити GIF-файли", "gifPicker_failedLoad": "Не вдалось завантажити GIF-файли",
"gifPicker_failedSearch": "Пошук GIF не вдався", "gifPicker_failedSearch": "Пошук GIF не вдався",
"gifPicker_noInternet": "Немає інтернет-з'єднання", "gifPicker_noInternet": "Немає інтернет-з'єднання",
"debugLog_appTitle": "Журнал налагодження програми", "debugLog_appTitle": "Журнал налагодження застосунку",
"debugLog_bleTitle": "Журнал налагодження BLE", "debugLog_bleTitle": "Журнал налагодження BLE",
"debugLog_copyLog": "Копіювати журнал", "debugLog_copyLog": "Копіювати журнал",
"debugLog_clearLog": "Очистити журнал", "debugLog_clearLog": "Очистити журнал",
"debugLog_copied": "Журнал налагодження скопійовано", "debugLog_copied": "Журнал налагодження скопійовано",
"debugLog_bleCopied": "Журнал BLE скопійовано", "debugLog_bleCopied": "Журнал BLE скопійовано",
"debugLog_noEntries": "Поки що немає записів журналу налагодження.", "debugLog_noEntries": "Поки що немає записів журналу налагодження.",
"debugLog_enableInSettings": "Увімкніть налагодження програми в налаштуваннях", "debugLog_enableInSettings": "Увімкніть налагодження застосунку в налаштуваннях",
"debugLog_frames": "Кадри", "debugLog_frames": "Кадри",
"debugLog_rawLogRx": "Необроблений лог - RX", "debugLog_rawLogRx": "Необроблений лог - RX",
"debugLog_noBleActivity": "Поки що немає активності BLE.", "debugLog_noBleActivity": "Поки що немає активності BLE.",
@@ -546,12 +550,12 @@
"chat_pathManagement": "Керування шляхами", "chat_pathManagement": "Керування шляхами",
"chat_routingMode": "Режим маршрутизації", "chat_routingMode": "Режим маршрутизації",
"chat_autoUseSavedPath": "Авто (використовувати збережений шлях)", "chat_autoUseSavedPath": "Авто (використовувати збережений шлях)",
"chat_forceFloodMode": "Примусово на всю мережу", "chat_forceFloodMode": "Примусово через всю мережу",
"chat_recentAckPaths": "Недавні шляхи ACK (натисніть, щоб використати):", "chat_recentAckPaths": "Підтверджені шляхи (натисніть, щоб використати):",
"chat_pathHistoryFull": "Історія шляхів заповнена. Видаліть записи, щоб додати нові.", "chat_pathHistoryFull": "Історія шляхів заповнена. Видаліть записи, щоб додати нові.",
"chat_hopSingular": "Стрибок", "chat_hopSingular": "Перехід",
"chat_hopPlural": "стрибків", "chat_hopPlural": "переходів",
"chat_hopsCount": "{count} {count, plural, =1{стрибок} few{стрибки} many{стрибків} other{стрибків}}", "chat_hopsCount": "{count} {count, plural, =1{перехід} few{переходи} many{переходів} other{переходів}}",
"@chat_hopsCount": { "@chat_hopsCount": {
"placeholders": { "placeholders": {
"count": { "count": {
@@ -560,6 +564,7 @@
} }
}, },
"chat_successes": "Успішно", "chat_successes": "Успішно",
"chat_score": "Оцінка",
"chat_removePath": "Видалити шлях", "chat_removePath": "Видалити шлях",
"chat_noPathHistoryYet": "Історія шляхів недоступна.\nНадішліть повідомлення, щоб виявити шляхи.", "chat_noPathHistoryYet": "Історія шляхів недоступна.\nНадішліть повідомлення, щоб виявити шляхи.",
"chat_pathActions": "Дії зі шляхом:", "chat_pathActions": "Дії зі шляхом:",
@@ -568,11 +573,11 @@
"chat_clearPath": "Очистити шлях", "chat_clearPath": "Очистити шлях",
"chat_clearPathSubtitle": "Примусово повторити пошук при наступному надсиланні", "chat_clearPathSubtitle": "Примусово повторити пошук при наступному надсиланні",
"chat_pathCleared": "Шлях очищено. Наступне повідомлення оновить маршрут.", "chat_pathCleared": "Шлях очищено. Наступне повідомлення оновить маршрут.",
"chat_floodModeSubtitle": "Використовувати перемикач маршрутизації в панелі програми", "chat_floodModeSubtitle": "Використовувати перемикач маршрутизації в панелі застосунку",
"chat_floodModeEnabled": "Увімкнено режим «на всю мережу». Перемикайте через іконку маршрутизації на панелі інструментів.", "chat_floodModeEnabled": "Увімкнено режим «через всю мережу». Перемикайте через іконку маршрутизації на панелі інструментів.",
"chat_fullPath": "Повний шлях", "chat_fullPath": "Повний шлях",
"chat_pathDetailsNotAvailable": "Деталі шляху ще недоступні. Спробуйте надіслати повідомлення для оновлення.", "chat_pathDetailsNotAvailable": "Деталі шляху ще недоступні. Спробуйте надіслати повідомлення для оновлення.",
"chat_pathSetHops": "Шлях встановлено: {hopCount} {hopCount, plural, =1{стрибок} few{стрибки} many{стрибків} other{стрибків}} - {status}", "chat_pathSetHops": "Шлях встановлено: {hopCount} {hopCount, plural, =1{перехід} few{переходи} many{переходів} other{переходів}} - {status}",
"@chat_pathSetHops": { "@chat_pathSetHops": {
"placeholders": { "placeholders": {
"hopCount": { "hopCount": {
@@ -590,9 +595,9 @@
"chat_path": "Шлях", "chat_path": "Шлях",
"chat_publicKey": "Відкритий ключ", "chat_publicKey": "Відкритий ключ",
"chat_compressOutgoingMessages": "Стискати вихідні повідомлення", "chat_compressOutgoingMessages": "Стискати вихідні повідомлення",
"chat_floodForced": "На всю мережу (примусово)", "chat_floodForced": "Через всю мережу (примусово)",
"chat_directForced": "Прямий (примусово)", "chat_directForced": "Напряму (примусово)",
"chat_hopsForced": "{count} стрибків (примусово)", "chat_hopsForced": "{count} переходів (примусово)",
"@chat_hopsForced": { "@chat_hopsForced": {
"placeholders": { "placeholders": {
"count": { "count": {
@@ -600,9 +605,9 @@
} }
} }
}, },
"chat_floodAuto": "На всю мережу (авто)", "chat_floodAuto": "Через всю мережу (авто)",
"chat_direct": "Прямий", "chat_direct": "Напряму",
"chat_poiShared": "Точкою інтересу поділилися", "chat_poiShared": "Поділилися точкою інтересу",
"chat_unread": "Непрочитано: {count}", "chat_unread": "Непрочитано: {count}",
"@chat_unread": { "@chat_unread": {
"placeholders": { "placeholders": {
@@ -614,7 +619,7 @@
"chat_openLink": "Відкрити посилання?", "chat_openLink": "Відкрити посилання?",
"chat_openLinkConfirmation": "Ви хочете відкрити це посилання у браузері?", "chat_openLinkConfirmation": "Ви хочете відкрити це посилання у браузері?",
"chat_open": "Відкрити", "chat_open": "Відкрити",
"chat_couldNotOpenLink": "Не вдалося відкрити посилання: {url}", "chat_couldNotOpenLink": "Не вдалось відкрити посилання: {url}",
"@chat_couldNotOpenLink": { "@chat_couldNotOpenLink": {
"placeholders": { "placeholders": {
"url": { "url": {
@@ -624,8 +629,8 @@
}, },
"chat_invalidLink": "Невірний формат посилання", "chat_invalidLink": "Невірний формат посилання",
"map_title": "Карта вузлів", "map_title": "Карта вузлів",
"map_noNodesWithLocation": "Немає вузлів з даними про розташування", "map_noNodesWithLocation": "Немає вузлів з даними про геопозицію",
"map_nodesNeedGps": "Вузли повинні надавати свої GPS координати,\nщоб з'явитися на карті.", "map_nodesNeedGps": "Вузли мають надавати свої GPS координати,\nщоб з'явитись на карті.",
"map_nodesCount": "Вузли: {count}", "map_nodesCount": "Вузли: {count}",
"@map_nodesCount": { "@map_nodesCount": {
"placeholders": { "placeholders": {
@@ -650,19 +655,25 @@
"map_pinPrivate": "Замок (Приватний)", "map_pinPrivate": "Замок (Приватний)",
"map_pinPublic": "Ключ (Публічний)", "map_pinPublic": "Ключ (Публічний)",
"map_lastSeen": "Останній раз бачили", "map_lastSeen": "Останній раз бачили",
"map_disconnectConfirm": "Ви впевнені, що хочете відключитися від цього пристрою?", "map_disconnectConfirm": "Ви впевнені, що хочете відключитись від цього пристрою?",
"map_from": "Від", "map_from": "Від",
"map_source": "Джерело", "map_source": "Джерело",
"map_flags": "Прапорці", "map_flags": "Прапорці",
"map_shareMarkerHere": "Поділитися маркером тут", "map_type": "Тип",
"map_path": "Шлях",
"map_location": "Геопозиція",
"map_estLocation": "Орієнтовна геопозиція",
"map_publicKey": "Публічний ключ",
"map_publicKeyPrefixHint": "напр. ab12",
"map_shareMarkerHere": "Поділитись маркером тут",
"map_pinLabel": "Мітка піна", "map_pinLabel": "Мітка піна",
"map_label": "Мітка", "map_label": "Мітка",
"map_pointOfInterest": "Точка інтересу", "map_pointOfInterest": "Точка інтересу",
"map_sendToContact": "Надіслати контакту", "map_sendToContact": "Надіслати контакту",
"map_sendToChannel": "Надіслати в канал", "map_sendToChannel": "Надіслати в канал",
"map_noChannelsAvailable": "Немає доступних каналів", "map_noChannelsAvailable": "Немає доступних каналів",
"map_publicLocationShare": "Поділитися в публічному місці", "map_publicLocationShare": "Поділитись в публічному місці",
"map_publicLocationShareConfirm": "Ви збираєтеся поділитися розташуванням у {channelLabel}. Цей канал публічний, і кожен, хто має ключ PSK, може це побачити.", "map_publicLocationShareConfirm": "Ви збираєтесь поділитись геопозицією у {channelLabel}. Цей канал публічний, і кожен, хто має ключ PSK, може це побачити.",
"@map_publicLocationShareConfirm": { "@map_publicLocationShareConfirm": {
"placeholders": { "placeholders": {
"channelLabel": { "channelLabel": {
@@ -670,7 +681,7 @@
} }
} }
}, },
"map_connectToShareMarkers": "Підключіться до пристрою, щоб поділитися маркерами", "map_connectToShareMarkers": "Підключіться до пристрою, щоб поділитись маркерами",
"map_filterNodes": "Фільтрувати вузли", "map_filterNodes": "Фільтрувати вузли",
"map_nodeTypes": "Типи вузлів", "map_nodeTypes": "Типи вузлів",
"map_chatNodes": "Вузли чату", "map_chatNodes": "Вузли чату",
@@ -683,7 +694,7 @@
"map_showSharedMarkers": "Показувати спільні маркери", "map_showSharedMarkers": "Показувати спільні маркери",
"map_lastSeenTime": "Час останньої активності", "map_lastSeenTime": "Час останньої активності",
"map_sharedPin": "Спільний пін", "map_sharedPin": "Спільний пін",
"map_joinRoom": "Приєднатися до кімнати", "map_joinRoom": "Приєднатись до кімнати",
"map_manageRepeater": "Керувати ретранслятором", "map_manageRepeater": "Керувати ретранслятором",
"mapCache_title": "Офлайн-кеш карти", "mapCache_title": "Офлайн-кеш карти",
"mapCache_selectAreaFirst": "Спершу виберіть область для кешування", "mapCache_selectAreaFirst": "Спершу виберіть область для кешування",
@@ -806,7 +817,7 @@
"time_minutes": "хвилин", "time_minutes": "хвилин",
"time_allTime": "Весь час", "time_allTime": "Весь час",
"dialog_disconnect": "Відключити", "dialog_disconnect": "Відключити",
"dialog_disconnectConfirm": "Ви впевнені, що хочете відключитися від цього пристрою?", "dialog_disconnectConfirm": "Ви впевнені, що хочете відключитись від цього пристрою?",
"login_repeaterLogin": "Вхід у ретранслятор", "login_repeaterLogin": "Вхід у ретранслятор",
"login_roomLogin": "Вхід у кімнату", "login_roomLogin": "Вхід у кімнату",
"login_password": "Пароль", "login_password": "Пароль",
@@ -817,8 +828,8 @@
"login_roomDescription": "Введіть пароль кімнати для доступу до налаштувань та статусу.", "login_roomDescription": "Введіть пароль кімнати для доступу до налаштувань та статусу.",
"login_routing": "Маршрутизація", "login_routing": "Маршрутизація",
"login_routingMode": "Режим маршрутизації", "login_routingMode": "Режим маршрутизації",
"login_autoUseSavedPath": "Авто (використовувати збережений шлях)", "login_autoUseSavedPath": "Авто (збережений шлях)",
"login_forceFloodMode": "Примусово на всю мережу", "login_forceFloodMode": "Примусово через всю мережу",
"login_managePaths": "Керувати шляхами", "login_managePaths": "Керувати шляхами",
"login_login": "Вхід", "login_login": "Вхід",
"login_attempt": "Спроба {current}/{max}", "login_attempt": "Спроба {current}/{max}",
@@ -851,7 +862,7 @@
} }
} }
}, },
"path_usingHopsPath": "Використання шляху з {count} {count, plural, =1{стрибком} few{стрибками} many{стрибками} other{стрибками}}", "path_usingHopsPath": "Використання шляху з {count} {count, plural, =1{переходом} few{переходами} many{переходами} other{переходами}}",
"@path_usingHopsPath": { "@path_usingHopsPath": {
"placeholders": { "placeholders": {
"count": { "count": {
@@ -861,10 +872,10 @@
}, },
"path_enterCustomPath": "Ввести власний шлях", "path_enterCustomPath": "Ввести власний шлях",
"path_currentPathLabel": "Поточний шлях", "path_currentPathLabel": "Поточний шлях",
"path_hexPrefixInstructions": "Введіть 2-символьні hex-префікси для кожного стрибка, розділені комами.", "path_hexPrefixInstructions": "Введіть 2-символьні hex-префікси для кожного переходу, розділені комами.",
"path_hexPrefixExample": "Приклад: A1,F2,3C (кожен вузол використовує перший байт свого відкритого ключа).", "path_hexPrefixExample": "Приклад: A1,F2,3C (кожен вузол використовує перший байт свого відкритого ключа).",
"path_labelHexPrefixes": "Hex-префікси", "path_labelHexPrefixes": "Hex-префікси",
"path_helperMaxHops": "Макс. 64 стрибки. Кожен префікс - 2 шістнадцяткові символи (1 байт)", "path_helperMaxHops": "Макс. 64 переходи. Кожен префікс 2 шістнадцяткові символи (1 байт)",
"path_selectFromContacts": "Вибрати з контактів:", "path_selectFromContacts": "Вибрати з контактів:",
"path_noRepeatersFound": "Ретрансляторів або серверів кімнат не знайдено.", "path_noRepeatersFound": "Ретрансляторів або серверів кімнат не знайдено.",
"path_customPathsRequire": "Власні шляхи вимагають проміжних вузлів, які можуть передавати повідомлення.", "path_customPathsRequire": "Власні шляхи вимагають проміжних вузлів, які можуть передавати повідомлення.",
@@ -876,7 +887,7 @@
} }
} }
}, },
"path_tooLong": "Шлях занадто довгий. Максимум 64 стрибки.", "path_tooLong": "Шлях занадто довгий. Максимум 64 переходи.",
"path_setPath": "Встановити шлях", "path_setPath": "Встановити шлях",
"repeater_management": "Керування ретранслятором", "repeater_management": "Керування ретранслятором",
"repeater_managementTools": "Інструменти керування", "repeater_managementTools": "Інструменти керування",
@@ -891,7 +902,7 @@
"repeater_statusTitle": "Статус ретранслятора", "repeater_statusTitle": "Статус ретранслятора",
"repeater_routingMode": "Режим маршрутизації", "repeater_routingMode": "Режим маршрутизації",
"repeater_autoUseSavedPath": "Авто (використовувати збережений шлях)", "repeater_autoUseSavedPath": "Авто (використовувати збережений шлях)",
"repeater_forceFloodMode": "Примусово на всю мережу", "repeater_forceFloodMode": "Примусово через всю мережу",
"repeater_pathManagement": "Керування шляхами", "repeater_pathManagement": "Керування шляхами",
"repeater_refresh": "Оновити", "repeater_refresh": "Оновити",
"repeater_statusRequestTimeout": "Час очікування запиту статусу вичерпано.", "repeater_statusRequestTimeout": "Час очікування запиту статусу вичерпано.",
@@ -936,7 +947,7 @@
} }
} }
}, },
"repeater_packetTxTotal": "Всього: {total}, На всю мережу: {flood}, Прямі: {direct}", "repeater_packetTxTotal": "Всього: {total}, Через всю мережу: {flood}, Прямі: {direct}",
"@repeater_packetTxTotal": { "@repeater_packetTxTotal": {
"placeholders": { "placeholders": {
"total": { "total": {
@@ -950,7 +961,7 @@
} }
} }
}, },
"repeater_packetRxTotal": "Всього: {total}, На всю мережу: {flood}, Прямі: {direct}", "repeater_packetRxTotal": "Всього: {total}, Через всю мережу: {flood}, Прямі: {direct}",
"@repeater_packetRxTotal": { "@repeater_packetRxTotal": {
"placeholders": { "placeholders": {
"total": { "total": {
@@ -964,7 +975,7 @@
} }
} }
}, },
"repeater_duplicatesFloodDirect": "На всю мережу: {flood}, Прямі: {direct}", "repeater_duplicatesFloodDirect": "Через всю мережу: {flood}, Прямі: {direct}",
"@repeater_duplicatesFloodDirect": { "@repeater_duplicatesFloodDirect": {
"placeholders": { "placeholders": {
"flood": { "flood": {
@@ -999,7 +1010,7 @@
"repeater_bandwidth": "Смуга пропускання", "repeater_bandwidth": "Смуга пропускання",
"repeater_spreadingFactor": "Коефіцієнт розширення", "repeater_spreadingFactor": "Коефіцієнт розширення",
"repeater_codingRate": "Швидкість кодування", "repeater_codingRate": "Швидкість кодування",
"repeater_locationSettings": "Налаштування розташування", "repeater_locationSettings": "Налаштування геопозиції",
"repeater_latitude": "Широта", "repeater_latitude": "Широта",
"repeater_latitudeHelper": "Десяткові градуси (наприклад, 37.7749)", "repeater_latitudeHelper": "Десяткові градуси (наприклад, 37.7749)",
"repeater_longitude": "Довгота", "repeater_longitude": "Довгота",
@@ -1010,9 +1021,9 @@
"repeater_guestAccess": "Гостьовий доступ", "repeater_guestAccess": "Гостьовий доступ",
"repeater_guestAccessSubtitle": "Дозволити гостьовий доступ лише для читання", "repeater_guestAccessSubtitle": "Дозволити гостьовий доступ лише для читання",
"repeater_privacyMode": "Режим приватності", "repeater_privacyMode": "Режим приватності",
"repeater_privacyModeSubtitle": "Приховати ім'я/розташування в оголошеннях", "repeater_privacyModeSubtitle": "Приховати ім'я/геопозицію в оголошеннях",
"repeater_advertisementSettings": "Налаштування оголошень", "repeater_advertisementSettings": "Налаштування оголошень",
"repeater_localAdvertInterval": "Інтервал локальних оголошень (0 стрибків)", "repeater_localAdvertInterval": "Інтервал локальних оголошень (без ретрансляції)",
"repeater_localAdvertIntervalMinutes": "{minutes} хвилин", "repeater_localAdvertIntervalMinutes": "{minutes} хвилин",
"@repeater_localAdvertIntervalMinutes": { "@repeater_localAdvertIntervalMinutes": {
"placeholders": { "placeholders": {
@@ -1021,7 +1032,7 @@
} }
} }
}, },
"repeater_floodAdvertInterval": "Інтервал оголошень на всю мережу (flood)", "repeater_floodAdvertInterval": "Інтервал оголошень через всю мережу (flood)",
"repeater_floodAdvertIntervalHours": "{hours} годин", "repeater_floodAdvertIntervalHours": "{hours} годин",
"@repeater_floodAdvertIntervalHours": { "@repeater_floodAdvertIntervalHours": {
"placeholders": { "placeholders": {
@@ -1035,9 +1046,9 @@
"repeater_rebootRepeater": "Перезавантажити ретранслятор", "repeater_rebootRepeater": "Перезавантажити ретранслятор",
"repeater_rebootRepeaterSubtitle": "Скинути пристрій ретранслятора", "repeater_rebootRepeaterSubtitle": "Скинути пристрій ретранслятора",
"repeater_rebootRepeaterConfirm": "Ви впевнені, що хочете перезавантажити цей ретранслятор?", "repeater_rebootRepeaterConfirm": "Ви впевнені, що хочете перезавантажити цей ретранслятор?",
"repeater_regenerateIdentityKey": "Перегенерувати ключ ідентичності", "repeater_regenerateIdentityKey": "Перегенерувати ключ ідентифікації",
"repeater_regenerateIdentityKeySubtitle": "Згенерувати нову пару ключів (публічний/приватний)", "repeater_regenerateIdentityKeySubtitle": "Згенерувати нову пару ключів (публічний/приватний)",
"repeater_regenerateIdentityKeyConfirm": "Це створить нову ідентичність для ретранслятора. Продовжити?", "repeater_regenerateIdentityKeyConfirm": "Це створить нову ідентифікацію для ретранслятора. Продовжити?",
"repeater_eraseFileSystem": "Очистити файлову систему", "repeater_eraseFileSystem": "Очистити файлову систему",
"repeater_eraseFileSystemSubtitle": "Відформатувати файлову систему ретранслятора", "repeater_eraseFileSystemSubtitle": "Відформатувати файлову систему ретранслятора",
"repeater_eraseFileSystemConfirm": "УВАГА: Це видалить всі дані з ретранслятора. Це не можна скасувати!", "repeater_eraseFileSystemConfirm": "УВАГА: Це видалить всі дані з ретранслятора. Це не можна скасувати!",
@@ -1071,7 +1082,7 @@
"repeater_refreshBasicSettings": "Оновити основні налаштування", "repeater_refreshBasicSettings": "Оновити основні налаштування",
"repeater_refreshRadioSettings": "Оновити налаштування радіо", "repeater_refreshRadioSettings": "Оновити налаштування радіо",
"repeater_refreshTxPower": "Оновити потужність TX", "repeater_refreshTxPower": "Оновити потужність TX",
"repeater_refreshLocationSettings": "Оновити налаштування розташування", "repeater_refreshLocationSettings": "Оновити налаштування геопозиції",
"repeater_refreshPacketForwarding": "Оновити пересилання пакетів", "repeater_refreshPacketForwarding": "Оновити пересилання пакетів",
"repeater_refreshGuestAccess": "Оновити гостьовий доступ", "repeater_refreshGuestAccess": "Оновити гостьовий доступ",
"repeater_refreshPrivacyMode": "Оновити режим приватності", "repeater_refreshPrivacyMode": "Оновити режим приватності",
@@ -1128,19 +1139,19 @@
"repeater_cliHelpSetTx": "Встановлює потужність передачі LoRa в дБм (для застосування потрібне перезавантаження).", "repeater_cliHelpSetTx": "Встановлює потужність передачі LoRa в дБм (для застосування потрібне перезавантаження).",
"repeater_cliHelpSetRepeat": "Вмикає або вимикає роль ретранслятора для цього вузла.", "repeater_cliHelpSetRepeat": "Вмикає або вимикає роль ретранслятора для цього вузла.",
"repeater_cliHelpSetAllowReadOnly": "(Сервер кімнати) Якщо «увімкнено», порожній пароль дозволить вхід, але не дозволить публікувати в кімнаті. (тільки читання)", "repeater_cliHelpSetAllowReadOnly": "(Сервер кімнати) Якщо «увімкнено», порожній пароль дозволить вхід, але не дозволить публікувати в кімнаті. (тільки читання)",
"repeater_cliHelpSetFloodMax": "Встановлює максимальну кількість стрибків для вхідних пакетів flood (якщо >= max, пакет не пересилається).", "repeater_cliHelpSetFloodMax": "Встановлює максимальну кількість переходів для вхідних пакетів flood (якщо >= max, пакет не пересилається).",
"repeater_cliHelpSetIntThresh": "Встановлює поріг інтерференції (в дБ). Значення за замовчуванням — 14. Встановлення на 0 вимикає виявлення інтерференції каналу.", "repeater_cliHelpSetIntThresh": "Встановлює поріг інтерференції (в дБ). Значення за замовчуванням — 14. Встановлення на 0 вимикає виявлення інтерференції каналу.",
"repeater_cliHelpSetAgcResetInterval": "Встановлює інтервал скидання автоматичного контролера посилення (AGC). Встановіть 0 для вимкнення.", "repeater_cliHelpSetAgcResetInterval": "Встановлює інтервал скидання автоматичного контролера посилення (AGC). Встановіть 0 для вимкнення.",
"repeater_cliHelpSetMultiAcks": "Вмикає або вимикає функціональність подвійних ACK.", "repeater_cliHelpSetMultiAcks": "Вмикає або вимикає функціональність подвійних ACK.",
"repeater_cliHelpSetAdvertInterval": "Встановлює інтервал таймера для надсилання локального пакету оголошення (без ретрансляції). Встановіть 0 для вимкнення.", "repeater_cliHelpSetAdvertInterval": "Встановлює інтервал таймера для надсилання локального пакету оголошення (без ретрансляції). Встановіть 0 для вимкнення.",
"repeater_cliHelpSetFloodAdvertInterval": "Встановлює інтервал таймера в годинах для надсилання пакету оголошення на всю мережу. Встановіть 0 для вимкнення.", "repeater_cliHelpSetFloodAdvertInterval": "Встановлює інтервал таймера в годинах для надсилання пакету оголошення через всю мережу. Встановіть 0 для вимкнення.",
"repeater_cliHelpSetGuestPassword": "Встановлює/оновлює гостьовий пароль. (для ретрансляторів гостьові підключення можуть надсилати запит «Get Stats»)", "repeater_cliHelpSetGuestPassword": "Встановлює/оновлює гостьовий пароль. (для ретрансляторів гостьові підключення можуть надсилати запит «Get Stats»)",
"repeater_cliHelpSetName": "Встановлює ім'я для оголошення.", "repeater_cliHelpSetName": "Встановлює ім'я для оголошення.",
"repeater_cliHelpSetLat": "Встановлює широту для карти оголошень. (десяткові градуси)", "repeater_cliHelpSetLat": "Встановлює широту для карти оголошень. (десяткові градуси)",
"repeater_cliHelpSetLon": "Встановлює довготу для карти оголошень. (десяткові градуси)", "repeater_cliHelpSetLon": "Встановлює довготу для карти оголошень. (десяткові градуси)",
"repeater_cliHelpSetRadio": "Повністю встановлює нові параметри радіо та зберігає їх у налаштуваннях. Потребує команди «перезавантаження» для застосування.", "repeater_cliHelpSetRadio": "Повністю встановлює нові параметри радіо та зберігає їх у налаштуваннях. Потребує команди «перезавантаження» для застосування.",
"repeater_cliHelpSetRxDelay": "Базові (експериментальні) параметри для застосування невеликої затримки до отриманих пакетів залежно від сили сигналу/оцінки. Встановіть 0 для вимкнення.", "repeater_cliHelpSetRxDelay": "Базові (експериментальні) параметри для застосування невеликої затримки до отриманих пакетів залежно від сили сигналу/оцінки. Встановіть 0 для вимкнення.",
"repeater_cliHelpSetTxDelay": "Встановлює множник для часу роботи в режимі «на всю мережу» (flood) для пакету та системи випадкових слотів, щоб затримати його відправку (для зменшення ймовірності колізій).", "repeater_cliHelpSetTxDelay": "Встановлює множник для часу роботи в режимі «через всю мережу» (flood) для пакету та системи випадкових слотів, щоб затримати його відправку (для зменшення ймовірності колізій).",
"repeater_cliHelpSetDirectTxDelay": "Те саме, що й txdelay, але для застосування випадкової затримки при пересиланні пакетів у прямому режимі.", "repeater_cliHelpSetDirectTxDelay": "Те саме, що й txdelay, але для застосування випадкової затримки при пересиланні пакетів у прямому режимі.",
"repeater_cliHelpSetBridgeEnabled": "Увімкнути/Вимкнути міст.", "repeater_cliHelpSetBridgeEnabled": "Увімкнути/Вимкнути міст.",
"repeater_cliHelpSetBridgeDelay": "Встановити затримку перед пересиланням пакетів.", "repeater_cliHelpSetBridgeDelay": "Встановити затримку перед пересиланням пакетів.",
@@ -1156,7 +1167,7 @@
"repeater_cliHelpLogErase": "Видаляє журнали пакетів з файлової системи.", "repeater_cliHelpLogErase": "Видаляє журнали пакетів з файлової системи.",
"repeater_cliHelpNeighbors": "Показує список інших вузлів-ретрансляторів, почутих через оголошення без ретрансляції. Кожен рядок — id-hex-префікс:timestamp:snr-помножено-на-4", "repeater_cliHelpNeighbors": "Показує список інших вузлів-ретрансляторів, почутих через оголошення без ретрансляції. Кожен рядок — id-hex-префікс:timestamp:snr-помножено-на-4",
"repeater_cliHelpNeighborRemove": "Видаляє перший відповідний запис (за префіксом публічного ключа (hex)) зі списку сусідів.", "repeater_cliHelpNeighborRemove": "Видаляє перший відповідний запис (за префіксом публічного ключа (hex)) зі списку сусідів.",
"repeater_cliHelpRegion": "(тільки серійний) Перелічує всі визначені регіони та поточні дозволи на оголошення «на всю мережу» (flood).", "repeater_cliHelpRegion": "(тільки серійний) Перелічує всі визначені регіони та поточні дозволи на оголошення «через всю мережу» (flood).",
"repeater_cliHelpRegionLoad": "ПРИМІТКА: це спеціальний виклик кількох команд. Кожна наступна команда — це назва регіону (з відступом пробілами для позначення ієрархії батьків, мінімум один пробіл). Завершується надсиланням порожнього рядка/команди.", "repeater_cliHelpRegionLoad": "ПРИМІТКА: це спеціальний виклик кількох команд. Кожна наступна команда — це назва регіону (з відступом пробілами для позначення ієрархії батьків, мінімум один пробіл). Завершується надсиланням порожнього рядка/команди.",
"repeater_cliHelpRegionGet": "Шукає регіон із заданим префіксом назви (або «» для глобальної області). Відповідає: «-> ім'я-регіону (ім'я-батька) 'F'»", "repeater_cliHelpRegionGet": "Шукає регіон із заданим префіксом назви (або «» для глобальної області). Відповідає: «-> ім'я-регіону (ім'я-батька) 'F'»",
"repeater_cliHelpRegionPut": "Додає або оновлює визначення регіону з заданою назвою.", "repeater_cliHelpRegionPut": "Додає або оновлює визначення регіону з заданою назвою.",
@@ -1170,14 +1181,14 @@
"repeater_cliHelpGpsOnOff": "Увімкнути/вимкнути GPS.", "repeater_cliHelpGpsOnOff": "Увімкнути/вимкнути GPS.",
"repeater_cliHelpGpsSync": "Синхронізує час вузла з годинником GPS.", "repeater_cliHelpGpsSync": "Синхронізує час вузла з годинником GPS.",
"repeater_cliHelpGpsSetLoc": "Встановлює позицію вузла за координатами GPS і зберігає в налаштуваннях.", "repeater_cliHelpGpsSetLoc": "Встановлює позицію вузла за координатами GPS і зберігає в налаштуваннях.",
"repeater_cliHelpGpsAdvert": "Надає конфігурацію оголошення розташування вузла:\n- none : не включати розташування в оголошення\n- share : ділитися розташуванням GPS (з SensorManager)\n- prefs : оголошувати розташування, збережене в налаштуваннях", "repeater_cliHelpGpsAdvert": "Надає конфігурацію оголошення геопозиції вузла:\n- none : не включати геопозицію в оголошення\n- share : ділитись геопозицією GPS (з SensorManager)\n- prefs : оголошувати геопозицію, збережену в налаштуваннях",
"repeater_cliHelpGpsAdvertSet": "Встановлює конфігурацію оголошення розташування.", "repeater_cliHelpGpsAdvertSet": "Встановлює конфігурацію оголошення геопозиції.",
"repeater_commandsListTitle": "Список команд", "repeater_commandsListTitle": "Список команд",
"repeater_commandsListNote": "ПРИМІТКА: для різних команд «set»... також існує команда «get»...", "repeater_commandsListNote": "ПРИМІТКА: для різних команд «set»... також існує команда «get»...",
"repeater_general": "Загальні", "repeater_general": "Загальні",
"repeater_settingsCategory": "Налаштування", "repeater_settingsCategory": "Налаштування",
"repeater_bridge": "Міст", "repeater_bridge": "Міст",
"repeater_logging": "Логування", "repeater_logging": "Журналювання",
"repeater_neighborsRepeaterOnly": "Сусіди (Тільки ретранслятор)", "repeater_neighborsRepeaterOnly": "Сусіди (Тільки ретранслятор)",
"repeater_regionManagementRepeaterOnly": "Керування регіонами (Тільки ретранслятор)", "repeater_regionManagementRepeaterOnly": "Керування регіонами (Тільки ретранслятор)",
"repeater_regionNote": "Команди регіонів були введені для керування визначеннями та дозволами регіонів.", "repeater_regionNote": "Команди регіонів були введені для керування визначеннями та дозволами регіонів.",
@@ -1248,7 +1259,7 @@
"channelPath_title": "Шлях пакету", "channelPath_title": "Шлях пакету",
"channelPath_viewMap": "Показати карту", "channelPath_viewMap": "Показати карту",
"channelPath_otherObservedPaths": "Інші спостережувані шляхи", "channelPath_otherObservedPaths": "Інші спостережувані шляхи",
"channelPath_repeaterHops": "Стрибки ретранслятора", "channelPath_repeaterHops": "Переходи через ретранслятори",
"channelPath_noHopDetails": "Деталі відправки не надані для цього пакету.", "channelPath_noHopDetails": "Деталі відправки не надані для цього пакету.",
"channelPath_messageDetails": "Деталі повідомлення", "channelPath_messageDetails": "Деталі повідомлення",
"channelPath_senderLabel": "Відправник", "channelPath_senderLabel": "Відправник",
@@ -1267,7 +1278,7 @@
} }
} }
}, },
"channelPath_noLocationData": "Немає даних про розташування", "channelPath_noLocationData": "Немає даних про геопозицію",
"channelPath_timeWithDate": "{day}/{month} {time}", "channelPath_timeWithDate": "{day}/{month} {time}",
"@channelPath_timeWithDate": { "@channelPath_timeWithDate": {
"placeholders": { "placeholders": {
@@ -1291,9 +1302,9 @@
} }
}, },
"channelPath_unknownPath": "Невідомий", "channelPath_unknownPath": "Невідомий",
"channelPath_floodPath": "На всю мережу", "channelPath_floodPath": "Через всю мережу",
"channelPath_directPath": "Прямий", "channelPath_directPath": "Напряму",
"channelPath_observedZeroOf": "0 з {total} стрибків", "channelPath_observedZeroOf": "0 з {total} переходів",
"@channelPath_observedZeroOf": { "@channelPath_observedZeroOf": {
"placeholders": { "placeholders": {
"total": { "total": {
@@ -1301,7 +1312,7 @@
} }
} }
}, },
"channelPath_observedSomeOf": "{observed} з {total} стрибків", "channelPath_observedSomeOf": "{observed} з {total} переходів",
"@channelPath_observedSomeOf": { "@channelPath_observedSomeOf": {
"placeholders": { "placeholders": {
"observed": { "observed": {
@@ -1342,12 +1353,12 @@
} }
} }
}, },
"channelPath_noHopDetailsAvailable": "Деталі стрибків недоступні для цього пакету.", "channelPath_noHopDetailsAvailable": "Деталі переходів недоступні для цього пакету.",
"channelPath_unknownRepeater": "Невідомий ретранслятор", "channelPath_unknownRepeater": "Невідомий ретранслятор",
"listFilter_tooltip": "Фільтр та сортування", "listFilter_tooltip": "Фільтр та сортування",
"listFilter_sortBy": "Сортувати за", "listFilter_sortBy": "Сортувати за",
"listFilter_latestMessages": "Останні повідомлення", "listFilter_latestMessages": "Останні повідомлення",
"listFilter_heardRecently": "Нещодавно чули", "listFilter_heardRecently": "Нещодавно почуті",
"listFilter_az": "А-Я", "listFilter_az": "А-Я",
"listFilter_filters": "Фільтри", "listFilter_filters": "Фільтри",
"listFilter_all": "Все", "listFilter_all": "Все",
@@ -1364,20 +1375,20 @@
} }
}, },
"repeater_neighbors": "Сусіди", "repeater_neighbors": "Сусіди",
"repeater_neighborsSubtitle": "Показати сусідів нульового стрибка.", "repeater_neighborsSubtitle": "Показати сусідів, доступних без ретрансляції.",
"neighbors_receivedData": "Дані сусідів отримано", "neighbors_receivedData": "Дані сусідів отримано",
"neighbors_requestTimedOut": "Час запиту сусідів вичерпано.", "neighbors_requestTimedOut": "Час запиту сусідів вичерпано.",
"neighbors_errorLoading": "Помилка завантаження сусідів: {error}", "neighbors_errorLoading": "Помилка завантаження сусідів: {error}",
"neighbors_repeatersNeighbors": "Ретранслятори-сусіди", "neighbors_repeatersNeighbors": "Ретранслятори-сусіди",
"neighbors_noData": "Дані про сусідів недоступні.", "neighbors_noData": "Дані про сусідів недоступні.",
"channels_createPrivateChannelDesc": "Захищено секретним ключем.", "channels_createPrivateChannelDesc": "Захищено секретним ключем.",
"channels_joinPrivateChannel": "Приєднатися до приватного каналу", "channels_joinPrivateChannel": "Приєднатись до приватного каналу",
"channels_createPrivateChannel": "Створити приватний канал", "channels_createPrivateChannel": "Створити приватний канал",
"channels_joinPrivateChannelDesc": "Ввести секретний ключ вручну.", "channels_joinPrivateChannelDesc": "Ввести секретний ключ вручну.",
"channels_joinPublicChannel": "Приєднатися до публічного каналу", "channels_joinPublicChannel": "Приєднатись до публічного каналу",
"channels_joinPublicChannelDesc": "Будь-хто може приєднатися до цього каналу.", "channels_joinPublicChannelDesc": "Будь-хто може приєднатись до цього каналу.",
"channels_joinHashtagChannel": "Приєднатися до каналу з хештегом", "channels_joinHashtagChannel": "Приєднатись до хештег-каналу",
"channels_joinHashtagChannelDesc": "Будь-хто може приєднатися до каналів #hashtag.", "channels_joinHashtagChannelDesc": "Будь-хто може приєднатись до хештег-каналів.",
"channels_scanQrCode": "Сканувати QR-код", "channels_scanQrCode": "Сканувати QR-код",
"channels_scanQrCodeComingSoon": "Скоро буде", "channels_scanQrCodeComingSoon": "Скоро буде",
"channels_enterHashtag": "Введіть хештег", "channels_enterHashtag": "Введіть хештег",
@@ -1399,7 +1410,7 @@
"neighbors_unknownContact": "Невідомий відкритий ключ {pubkey}", "neighbors_unknownContact": "Невідомий відкритий ключ {pubkey}",
"neighbors_heardAgo": "Почуто: {time} тому", "neighbors_heardAgo": "Почуто: {time} тому",
"settings_locationGPSEnable": "Увімкнути GPS", "settings_locationGPSEnable": "Увімкнути GPS",
"settings_locationGPSEnableSubtitle": "Вмикає автоматичне оновлення місцезнаходження через GPS.", "settings_locationGPSEnableSubtitle": "Вмикає автоматичне оновлення геопозиції через GPS.",
"settings_locationIntervalSec": "Інтервал для GPS (Секунди)", "settings_locationIntervalSec": "Інтервал для GPS (Секунди)",
"settings_locationIntervalInvalid": "Інтервал має бути не менше 60 секунд і менше 86400 секунд.", "settings_locationIntervalInvalid": "Інтервал має бути не менше 60 секунд і менше 86400 секунд.",
"contacts_manageRoom": "Керувати сервером кімнати", "contacts_manageRoom": "Керувати сервером кімнати",
@@ -1463,10 +1474,10 @@
"common_ok": "ОК", "common_ok": "ОК",
"community_title": "Спільнота", "community_title": "Спільнота",
"community_create": "Створити спільноту", "community_create": "Створити спільноту",
"community_createDesc": "Створити нову спільноту та поділитися через QR-код.", "community_createDesc": "Створити нову спільноту та поділитись через QR-код.",
"community_join": "Приєднатися", "community_join": "Приєднатись",
"community_joinTitle": "Приєднатися до спільноти", "community_joinTitle": "Приєднатись до спільноти",
"community_joinConfirmation": "Ви бажаєте приєднатися до спільноти «{name}»?", "community_joinConfirmation": "Ви бажаєте приєднатись до спільноти «{name}»?",
"community_scanQr": "Сканувати QR спільноти", "community_scanQr": "Сканувати QR спільноти",
"community_scanInstructions": "Наведіть камеру на QR-код спільноти.", "community_scanInstructions": "Наведіть камеру на QR-код спільноти.",
"community_showQr": "Показати QR-код", "community_showQr": "Показати QR-код",
@@ -1476,9 +1487,9 @@
"community_enterName": "Введіть назву спільноти", "community_enterName": "Введіть назву спільноти",
"community_created": "Спільноту «{name}» створено", "community_created": "Спільноту «{name}» створено",
"community_joined": "Приєднався до спільноти «{name}»", "community_joined": "Приєднався до спільноти «{name}»",
"community_qrTitle": "Поділитися спільнотою", "community_qrTitle": "Поділитись спільнотою",
"community_qrInstructions": "Відскануйте цей QR-код, щоб приєднатися до {name}", "community_qrInstructions": "Відскануйте цей QR-код, щоб приєднатись до {name}",
"community_hashtagPrivacyHint": "Канали хештегів спільноти доступні лише членам спільноти", "community_hashtagPrivacyHint": "Хештег-канали спільноти доступні лише членам спільноти",
"community_invalidQrCode": "Недійсний QR-код спільноти", "community_invalidQrCode": "Недійсний QR-код спільноти",
"community_alreadyMember": "Вже учасник", "community_alreadyMember": "Вже учасник",
"community_alreadyMemberMessage": "Ви вже є учасником «{name}».", "community_alreadyMemberMessage": "Ви вже є учасником «{name}».",
@@ -1499,10 +1510,10 @@
}, },
"community_deleted": "Спільноту «{name}» покинуто", "community_deleted": "Спільноту «{name}» покинуто",
"community_addHashtagChannel": "Додати хештег спільноти", "community_addHashtagChannel": "Додати хештег спільноти",
"community_addHashtagChannelDesc": "Додати канал хештегу для цієї спільноти", "community_addHashtagChannelDesc": "Додати хештег-канал для цієї спільноти",
"community_selectCommunity": "Вибрати спільноту", "community_selectCommunity": "Вибрати спільноту",
"community_regularHashtag": "Звичайний хештег", "community_regularHashtag": "Звичайний хештег",
"community_regularHashtagDesc": "Публічний хештег (будь-хто може приєднатися)", "community_regularHashtagDesc": "Публічний хештег (будь-хто може приєднатись)",
"community_communityHashtag": "Хештег спільноти", "community_communityHashtag": "Хештег спільноти",
"community_communityHashtagDesc": "Ексклюзивно для членів спільноти", "community_communityHashtagDesc": "Ексклюзивно для членів спільноти",
"community_forCommunity": "Для {name}", "community_forCommunity": "Для {name}",
@@ -1534,13 +1545,13 @@
} }
} }
}, },
"community_regenerateSecret": "Перегенерувати секрет", "community_regenerateSecret": "Перегенерувати секретний ключ",
"community_regenerateSecretConfirm": "Перегенерувати секретний ключ для «{name}»? Всі учасники повинні будуть відсканувати новий QR-код, щоб продовжити спілкування.", "community_regenerateSecretConfirm": "Перегенерувати секретний ключ для «{name}»? Усі учасники матимуть відсканувати новий QR-код, щоб продовжити спілкування.",
"community_regenerate": "Перегенерувати", "community_regenerate": "Перегенерувати",
"community_secretRegenerated": "Секретний пароль для «{name}» перегенеровано", "community_secretRegenerated": "Секретний ключ для «{name}» перегенеровано",
"community_scanToUpdateSecret": "Відскануйте новий QR-код, щоб оновити пароль для «{name}»", "community_scanToUpdateSecret": "Відскануйте новий QR-код, щоб оновити секретний ключ для «{name}»",
"community_updateSecret": "Оновити секрет", "community_updateSecret": "Оновити секретний ключ",
"community_secretUpdated": "Зміну секрету для «{name}» оновлено", "community_secretUpdated": "Секретний ключ для «{name}» оновлено",
"@contacts_pathTraceTo": { "@contacts_pathTraceTo": {
"placeholders": { "placeholders": {
"name": { "name": {
@@ -1549,76 +1560,76 @@
} }
}, },
"pathTrace_you": "Ви", "pathTrace_you": "Ви",
"pathTrace_failed": "Відстеження шляху не вдалося.", "pathTrace_failed": "Відстеження шляху не вдалось.",
"pathTrace_notAvailable": "Трасування шляху недоступне.", "pathTrace_notAvailable": "Трасування шляху недоступне.",
"pathTrace_refreshTooltip": "Оновити Path Trace", "pathTrace_refreshTooltip": "Оновити трасування шляху",
"contacts_pathTrace": "Трасування шляхів", "contacts_pathTrace": "Трасування шляхів",
"contacts_ping": "Пінгувати", "contacts_ping": "Пінгувати",
"contacts_repeaterPathTrace": "Трасування шляху до повторювача", "contacts_repeaterPathTrace": "Трасування шляху до ретранслятора",
"contacts_repeaterPing": "Пінгувати повторювач", "contacts_repeaterPing": "Пінгувати ретранслятор",
"contacts_roomPathTrace": "Трасування шляху до серверу кімнати", "contacts_roomPathTrace": "Трасування шляху до серверу кімнати",
"contacts_roomPing": "Пінг сервера кімнати", "contacts_roomPing": "Пінг сервера кімнати",
"contacts_chatTraceRoute": "Трасування шляху", "contacts_chatTraceRoute": "Трасування шляху",
"contacts_pathTraceTo": "Відстежити маршрут до {name}", "contacts_pathTraceTo": "Відстежити маршрут до {name}",
"contacts_invalidAdvertFormat": "Недійсні контактні дані", "contacts_invalidAdvertFormat": "Недійсні контактні дані",
"contacts_contactImported": "Контакт було імпортовано.", "contacts_contactImported": "Контакт було імпортовано.",
"contacts_contactImportFailed": "Контакт не вдалося імпортувати", "contacts_contactImportFailed": "Контакт не вдалось імпортувати",
"contacts_zeroHopAdvert": "Реклама без перехоплення", "contacts_zeroHopAdvert": "Оголошення без ретрансляції",
"contacts_floodAdvert": "Залив реклами", "contacts_floodAdvert": "Оголошення з ретрансляцією",
"contacts_copyAdvertToClipboard": "Копіювати оголошення в буфер обміну", "contacts_copyAdvertToClipboard": "Копіювати оголошення",
"contacts_clipboardEmpty": "Буфер обміну порожній", "contacts_clipboardEmpty": "Буфер обміну порожній",
"appSettings_languageRu": "Російська", "appSettings_languageRu": "Російська",
"appSettings_enableMessageTracing": "Увімкнути відстеження повідомлень", "appSettings_enableMessageTracing": "Увімкнути відстеження повідомлень",
"appSettings_enableMessageTracingSubtitle": "Показувати детальні метадані про маршрутизацію та час для повідомлень", "appSettings_enableMessageTracingSubtitle": "Показувати детальні метадані про маршрутизацію та час для повідомлень",
"contacts_ShareContact": "Копіювати контакт у буфер обміну", "contacts_ShareContact": "Копіювати контакт у буфер обміну",
"contacts_zeroHopContactAdvertFailed": "Не вдалося надіслати контакт.", "contacts_zeroHopContactAdvertFailed": "Не вдалось надіслати контакт.",
"contacts_contactAdvertCopied": "Рекламу скопійовано до буфера обміну.", "contacts_contactAdvertCopied": "Оголошення скопійовано до буфера обміну.",
"contacts_contactAdvertCopyFailed": "Копіювання оголошення в буфер обміну завершилося невдало", "contacts_contactAdvertCopyFailed": "Копіювання оголошення в буфер обміну завершилось невдало",
"contacts_zeroHopContactAdvertSent": "Відправлено контакт за оголошенням", "contacts_zeroHopContactAdvertSent": "Відправлено контакт за оголошенням",
"contacts_addContactFromClipboard": "Додати контакт з буфера обміну", "contacts_addContactFromClipboard": "Додати контакт з буфера",
"contacts_ShareContactZeroHop": "Поділитися контактом за оголошенням", "contacts_ShareContactZeroHop": "Поділитись контактом за оголошенням",
"notification_activityTitle": "Активність MeshCore", "notification_activityTitle": "Активність MeshCore",
"notification_messagesCount": "{count} {count, plural, =1{повідомлення} few{повідомлення} many{повідомлень} other{повідомлень}}", "notification_messagesCount": "{count} {count, plural, =1{повідомлення} few{повідомлення} many{повідомлень} other{повідомлень}}",
"notification_channelMessagesCount": "{count} {count, plural, =1{повідомлення каналу} few{повідомлення каналу} many{повідомлень каналу} other{повідомлень каналу}}", "notification_channelMessagesCount": "{count} {count, plural, =1{повідомлення каналу} few{повідомлення каналу} many{повідомлень каналу} other{повідомлень каналу}}",
"notification_newNodesCount": "{count} {count, plural, =1{новий вузол} few{нових вузли} many{нових вузлів} other{нових вузлів}}", "notification_newNodesCount": "{count} {count, plural, =1{новий вузол} few{нові вузли} many{нових вузлів} other{нових вузлів}}",
"notification_newTypeDiscovered": "Виявлено новий {contactType}", "notification_newTypeDiscovered": "Виявлено новий {contactType}",
"notification_receivedNewMessage": "Отримано нове повідомлення", "notification_receivedNewMessage": "Отримано нове повідомлення",
"settings_gpxExportRepeaters": "Експортувати ретранслятори / сервер кімнати до GPX", "settings_gpxExportRepeaters": "Експорт ретрансляторів і серверів кімнат у GPX",
"settings_gpxExportRepeatersSubtitle": "Експортує ретранслятори / сервер кімнати з місцезнаходженням у файл GPX.", "settings_gpxExportRepeatersSubtitle": "Експортує ретранслятори та сервери кімнат з геопозицією у файл GPX.",
"settings_gpxExportSuccess": "Успішно експортовано файл GPX.", "settings_gpxExportSuccess": "Успішно експортовано файл GPX.",
"settings_gpxExportNoContacts": "Немає контактів для експорту.", "settings_gpxExportNoContacts": "Немає контактів для експорту.",
"settings_gpxExportNotAvailable": "Не підтримується на вашому пристрої/операційній системі", "settings_gpxExportNotAvailable": "Не підтримується на вашому пристрої/операційній системі",
"settings_gpxExportError": "Сталася помилка під час експорту.", "settings_gpxExportError": "Сталась помилка під час експорту.",
"settings_gpxExportAllSubtitle": "Експортує всі контакти з місцем розташування у файл GPX.", "settings_gpxExportAllSubtitle": "Експортує всі контакти з геопозицією у файл GPX.",
"settings_gpxExportAll": "Експортувати всі контакти до GPX", "settings_gpxExportAll": "Експорт усіх контактів у GPX",
"settings_gpxExportContactsSubtitle": "Експортує супутників з місцезнаходженням у файл GPX.", "settings_gpxExportContactsSubtitle": "Експортує контакти з геопозицією у файл GPX.",
"settings_gpxExportContacts": "Експортувати супутників до GPX", "settings_gpxExportContacts": "Експорт контактів у GPX",
"settings_gpxExportRepeatersRoom": "Місцезнаходження повторювача та сервера кімнати", "settings_gpxExportRepeatersRoom": "Геопозиції ретрансляторів та серверів кімнат",
"settings_gpxExportChat": "Місця супутників", "settings_gpxExportChat": "Геопозиції контактів",
"settings_gpxExportShareText": "Дані карти експортовані з meshcore-open", "settings_gpxExportShareText": "Дані карти експортовані з meshcore-open",
"settings_gpxExportAllContacts": "Усі місця контактів", "settings_gpxExportAllContacts": "Усі місця контактів",
"settings_gpxExportShareSubject": "експорт даних карти meshcore-open у форматі GPX", "settings_gpxExportShareSubject": "експорт даних карти meshcore-open у форматі GPX",
"pathTrace_someHopsNoLocation": "Одне або більше хмелів відсутнє місце розташування!", "pathTrace_someHopsNoLocation": "Один або декілька переходів не мають даних про геопозицію!",
"map_tapToAdd": "Натисніть на вузли, щоб додати їх до шляху", "map_tapToAdd": "Натисніть на вузли, щоб додати їх до шляху",
"map_runTrace": "Виконати трасування шляху", "map_runTrace": "Виконати трасування шляху",
"pathTrace_clearTooltip": "Очистити шлях", "pathTrace_clearTooltip": "Очистити шлях",
"map_removeLast": "Видалити останній", "map_removeLast": "Видалити останній",
"map_pathTraceCancelled": "Відмінується трасування шляху", "map_pathTraceCancelled": "Трасування шляху скасовано.",
"scanner_enableBluetooth": "Увімкніть Bluetooth", "scanner_enableBluetooth": "Увімкніть Bluetooth",
"scanner_bluetoothOffMessage": "Будь ласка, увімкніть Bluetooth, щоб сканувати пристрої.", "scanner_bluetoothOffMessage": "Будь ласка, увімкніть Bluetooth, щоб сканувати пристрої.",
"scanner_chromeRequired": "Потрібен браузер Chrome", "scanner_chromeRequired": "Потрібен браузер Chrome",
"scanner_chromeRequiredMessage": "Для підтримки Bluetooth у цьому веб-додатку потрібен Google Chrome або браузер на базі Chromium.", "scanner_chromeRequiredMessage": "Для підтримки Bluetooth у цьому вебзастосунку потрібен Google Chrome або браузер на базі Chromium.",
"scanner_bluetoothOff": "Bluetooth вимкнено", "scanner_bluetoothOff": "Bluetooth вимкнено",
"snrIndicator_lastSeen": "Останній раз бачили", "snrIndicator_lastSeen": "Останній раз бачили",
"snrIndicator_nearByRepeaters": "Ближні ретранслятори", "snrIndicator_nearByRepeaters": "Найближчі ретранслятори",
"chat_ShowAllPaths": "Показати всі шляхи", "chat_ShowAllPaths": "Показати всі шляхи",
"settings_clientRepeatFreqWarning": "Повтор без підключення до мережі вимагає частоти 433, 869 або 918 МГц.", "settings_clientRepeatFreqWarning": "Повтор без підключення до мережі вимагає частоти 433, 869 або 918 МГц.",
"settings_clientRepeatSubtitle": "Дозвольте цьому пристрою повторювати пакети даних для інших пристроїв.", "settings_clientRepeatSubtitle": "Дозвольте цьому пристрою повторювати пакети даних для інших пристроїв.",
"settings_clientRepeat": "Автономна система", "settings_clientRepeat": "Автономна система",
"settings_aboutOpenMeteoAttribution": "Дані про висоту LOS: Open-Meteo (CC BY 4.0)", "settings_aboutOpenMeteoAttribution": "Дані про висоту LOS: Open-Meteo (CC BY 4.0)",
"appSettings_unitsTitle": "одиниці", "appSettings_unitsTitle": "Одиниці",
"appSettings_unitsMetric": "Метричний (м / км)", "appSettings_unitsMetric": "Метричні (м / км)",
"appSettings_unitsImperial": "Імперська (ft / mi)", "appSettings_unitsImperial": "Імперські (ft / mi)",
"map_lineOfSight": "Пряма видимість", "map_lineOfSight": "Пряма видимість",
"map_losScreenTitle": "Пряма видимість", "map_losScreenTitle": "Пряма видимість",
"losSelectStartEnd": "Виберіть початковий і кінцевий вузли для LOS.", "losSelectStartEnd": "Виберіть початковий і кінцевий вузли для LOS.",
@@ -1636,7 +1647,7 @@
"losMenuSubtitle": "Торкніться вузлів або утримуйте карту, щоб отримати власні точки", "losMenuSubtitle": "Торкніться вузлів або утримуйте карту, щоб отримати власні точки",
"losShowDisplayNodes": "Показати вузли відображення", "losShowDisplayNodes": "Показати вузли відображення",
"losCustomPoints": "Користувальницькі точки", "losCustomPoints": "Користувальницькі точки",
"losCustomPointLabel": "Спеціальний {index}", "losCustomPointLabel": "Власна точка {index}",
"@losCustomPointLabel": { "@losCustomPointLabel": {
"placeholders": { "placeholders": {
"index": { "index": {
@@ -1725,7 +1736,7 @@
}, },
"losErrorElevationUnavailable": "Дані про висоту недоступні для одного чи кількох зразків.", "losErrorElevationUnavailable": "Дані про висоту недоступні для одного чи кількох зразків.",
"losErrorInvalidInput": "Недійсні дані про точки/висоту для розрахунку LOS.", "losErrorInvalidInput": "Недійсні дані про точки/висоту для розрахунку LOS.",
"losRenameCustomPoint": "Перейменуйте спеціальну точку", "losRenameCustomPoint": "Перейменувати власну точку",
"losPointName": "Назва точки", "losPointName": "Назва точки",
"losShowPanelTooltip": "Показати панель LOS", "losShowPanelTooltip": "Показати панель LOS",
"losHidePanelTooltip": "Приховати панель LOS", "losHidePanelTooltip": "Приховати панель LOS",
@@ -1805,19 +1816,19 @@
"contacts_unread": "Непрочитане", "contacts_unread": "Непрочитане",
"settings_contactSettingsSubtitle": "Налаштування для додавання контактів", "settings_contactSettingsSubtitle": "Налаштування для додавання контактів",
"settings_contactSettings": "Налаштування контактів", "settings_contactSettings": "Налаштування контактів",
"contactsSettings_autoAddUsersSubtitle": "Дозволити супутникові автоматично додавати виявлених користувачів", "contactsSettings_autoAddUsersSubtitle": "Дозволити пристрою-компаньйону автоматично додавати виявлених користувачів",
"contactsSettings_autoAddRepeatersTitle": "Автоматично додавати повторювачі", "contactsSettings_autoAddRepeatersTitle": "Автоматично додавати ретранслятори",
"contactsSettings_autoAddRepeatersSubtitle": "Дозволити супутнику автоматично додавати виявлені ретранслятори", "contactsSettings_autoAddRepeatersSubtitle": "Дозволити пристрою-компаньйону автоматично додавати виявлені ретранслятори",
"contactsSettings_autoAddRoomServersTitle": "Автоматично додавати сервери кімнат", "contactsSettings_autoAddRoomServersTitle": "Автоматично додавати сервери кімнат",
"contactsSettings_otherTitle": "Інші налаштування, пов'язані з контактами", "contactsSettings_otherTitle": "Інші налаштування, пов'язані з контактами",
"contactsSettings_autoAddTitle": "Автоматичне виявлення", "contactsSettings_autoAddTitle": "Автоматичне виявлення",
"contactsSettings_autoAddUsersTitle": "Автоматично додавати користувачів", "contactsSettings_autoAddUsersTitle": "Автоматично додавати користувачів",
"contactsSettings_title": "Налаштування контактів", "contactsSettings_title": "Налаштування контактів",
"contactsSettings_autoAddRoomServersSubtitle": "Дозволити супровіднику автоматично додавати виявлені сервери кімнат.", "contactsSettings_autoAddRoomServersSubtitle": "Дозволити пристрою-компаньйону автоматично додавати виявлені сервери кімнат.",
"contactsSettings_autoAddSensorsTitle": "Автоматично додавати датчики", "contactsSettings_autoAddSensorsTitle": "Автоматично додавати сенсори",
"discoveredContacts_searchHint": "Знайти виявлені контакти", "discoveredContacts_searchHint": "Знайти виявлені контакти",
"discoveredContacts_contactAdded": "Контакт додано", "discoveredContacts_contactAdded": "Контакт додано",
"contactsSettings_autoAddSensorsSubtitle": "Дозволити супровіднику автоматично додавати виявлені сенсори", "contactsSettings_autoAddSensorsSubtitle": "Дозволити пристрою-компаньйону автоматично додавати виявлені сенсори",
"contactsSettings_overwriteOldestTitle": "Перезаписати найстаріше", "contactsSettings_overwriteOldestTitle": "Перезаписати найстаріше",
"discoveredContacts_Title": "Виявлені контакти", "discoveredContacts_Title": "Виявлені контакти",
"discoveredContacts_noMatching": "Відповідних контактів не знайдено", "discoveredContacts_noMatching": "Відповідних контактів не знайдено",
@@ -1828,9 +1839,9 @@
"common_deleteAll": "Видалити все", "common_deleteAll": "Видалити все",
"discoveredContacts_deleteContactAll": "Видалити всі виявлені контакти", "discoveredContacts_deleteContactAll": "Видалити всі виявлені контакти",
"discoveredContacts_deleteContactAllContent": "Ви впевнені, що хочете видалити всі виявлені контакти?", "discoveredContacts_deleteContactAllContent": "Ви впевнені, що хочете видалити всі виявлені контакти?",
"map_showGuessedLocations": "Показати місцезнаходження передбачених вузлів", "map_showGuessedLocations": "Показати геопозиції передбачених вузлів",
"map_guessedLocation": "Визначено місцезнаходження", "map_guessedLocation": "Передбачена геопозиція",
"usbScreenSubtitle": "Виберіть виявлене серійне пристрій і підключіть його безпосередньо до вашого вузла MeshCore.", "usbScreenSubtitle": "Виберіть виявлений USB-пристрій і підключіть його безпосередньо до вашого вузла MeshCore.",
"usbScreenTitle": "Підключити через USB", "usbScreenTitle": "Підключити через USB",
"connectionChoiceBluetoothLabel": "Bluetooth", "connectionChoiceBluetoothLabel": "Bluetooth",
"connectionChoiceUsbLabel": "USB", "connectionChoiceUsbLabel": "USB",
@@ -1842,8 +1853,8 @@
"usbErrorInvalidPort": "Виберіть дійсний USB-пристрій.", "usbErrorInvalidPort": "Виберіть дійсний USB-пристрій.",
"usbErrorBusy": "Ще один запит на підключення через USB вже обробляється.", "usbErrorBusy": "Ще один запит на підключення через USB вже обробляється.",
"usbErrorNotConnected": "Немає підключених пристроїв USB.", "usbErrorNotConnected": "Немає підключених пристроїв USB.",
"usbErrorOpenFailed": "Не вдалося відкрити вибране USB-пристрій.", "usbErrorOpenFailed": "Не вдалось відкрити вибране USB-пристрій.",
"usbErrorConnectFailed": "Не вдалося підключитися до вибраного USB-пристрою.", "usbErrorConnectFailed": "Не вдалось підключитись до вибраного USB-пристрою.",
"usbErrorUnsupported": "Підтримка USB-серіального інтерфейсу не реалізована на цій платформі.", "usbErrorUnsupported": "Підтримка USB-серіального інтерфейсу не реалізована на цій платформі.",
"usbErrorAlreadyActive": "USB-з'єднання вже встановлено.", "usbErrorAlreadyActive": "USB-з'єднання вже встановлено.",
"usbErrorNoDeviceSelected": "Не було вибрано жодного пристрою USB.", "usbErrorNoDeviceSelected": "Не було вибрано жодного пристрою USB.",
@@ -1858,9 +1869,9 @@
}, },
"usbStatus_searching": "Пошук пристроїв USB...", "usbStatus_searching": "Пошук пристроїв USB...",
"usbStatus_notConnected": "Виберіть пристрій USB", "usbStatus_notConnected": "Виберіть пристрій USB",
"usbConnectionFailed": "Не вдалося встановити з'єднання через USB: {error}", "usbConnectionFailed": "Не вдалось встановити з'єднання через USB: {error}",
"usbStatus_connecting": "Підключення до USB-пристрою...", "usbStatus_connecting": "Підключення до USB-пристрою...",
"usbErrorConnectTimedOut": "З'єднання не вдалося встановити. Переконайтеся, що пристрій має встановлене програмне забезпечення USB Companion.", "usbErrorConnectTimedOut": "З'єднання не вдалось встановити. Переконайтесь, що пристрій має встановлене програмне забезпечення USB Companion.",
"@tcpStatus_connectingTo": { "@tcpStatus_connectingTo": {
"placeholders": { "placeholders": {
"endpoint": { "endpoint": {
@@ -1878,18 +1889,18 @@
"connectionChoiceTcpLabel": "TCP", "connectionChoiceTcpLabel": "TCP",
"tcpHostHint": "192.168.40.10", "tcpHostHint": "192.168.40.10",
"tcpHostLabel": "IP-адреса", "tcpHostLabel": "IP-адреса",
"tcpScreenTitle": "З'єднатися через протокол TCP", "tcpScreenTitle": "Підключитись через TCP",
"tcpPortLabel": "Порт", "tcpPortLabel": "Порт",
"tcpPortHint": "5000", "tcpPortHint": "5000",
"tcpStatus_notConnected": "Введіть кінцеву точку та підключіться", "tcpStatus_notConnected": "Введіть кінцеву точку та підключіться",
"tcpStatus_connectingTo": "Підключення до {endpoint}...", "tcpStatus_connectingTo": "Підключення до {endpoint}...",
"tcpErrorHostRequired": "Необхідно вказати IP-адресу.", "tcpErrorHostRequired": "Необхідно вказати IP-адресу.",
"tcpErrorPortInvalid": "Порт повинен бути в межах від 1 до 65535.", "tcpErrorPortInvalid": "Порт має бути в межах від 1 до 65535.",
"tcpErrorUnsupported": "Транспорт TCP не підтримується на цій платформі.", "tcpErrorUnsupported": "Транспорт TCP не підтримується на цій платформі.",
"tcpErrorTimedOut": "З'єднання TCP завершилося через закінчення часу очікування.", "tcpErrorTimedOut": "З'єднання TCP завершилось через закінчення часу очікування.",
"tcpConnectionFailed": "Не вдалося встановити з'єднання TCP: {error}", "tcpConnectionFailed": "Не вдалось встановити з'єднання TCP: {error}",
"map_showDiscoveryContacts": "Показати контакти Відкриття", "map_showDiscoveryContacts": "Показати виявлені контакти",
"map_setAsMyLocation": "Встановити моє місцезнаходження", "map_setAsMyLocation": "Встановити мою геопозицію",
"@path_routeWeight": { "@path_routeWeight": {
"placeholders": { "placeholders": {
"weight": { "weight": {
@@ -1900,12 +1911,12 @@
} }
} }
}, },
"settings_privacySubtitle": "Керуйте інформацією, яку буде спільно використовуватися", "settings_privacySubtitle": "Керуйте інформацією, яка буде спільно використовуватись",
"settings_privacy": "Налаштування приватності", "settings_privacy": "Налаштування приватності",
"settings_telemetryBaseMode": "Режим базової телеметрії", "settings_telemetryBaseMode": "Режим базової телеметрії",
"settings_telemetryLocationMode": "Режим місця телеметрії", "settings_telemetryLocationMode": "Режим місця телеметрії",
"settings_advertLocation": "Розміщення реклами", "settings_advertLocation": "Геопозиція в оголошенні",
"settings_advertLocationSubtitle": "Включити місце розташування в оголошення", "settings_advertLocationSubtitle": "Включити геопозицію в оголошення",
"settings_privacySettingsDescription": "Виберіть, яку інформацію ваш пристрій буде передавати іншим.", "settings_privacySettingsDescription": "Виберіть, яку інформацію ваш пристрій буде передавати іншим.",
"settings_allowAll": "Дозволити все", "settings_allowAll": "Дозволити все",
"settings_denyAll": "Відхилити все", "settings_denyAll": "Відхилити все",
@@ -1913,8 +1924,8 @@
"settings_telemetryEnvironmentMode": "Режим середовища телеметрії", "settings_telemetryEnvironmentMode": "Режим середовища телеметрії",
"contact_info": "Контактна інформація", "contact_info": "Контактна інформація",
"contact_teleBaseSubtitle": "Дозволити спільний доступ до рівня заряду батареї та базової телеметрії", "contact_teleBaseSubtitle": "Дозволити спільний доступ до рівня заряду батареї та базової телеметрії",
"contact_teleLoc": "Розташування телеметрії", "contact_teleLoc": "Геопозиція телеметрії",
"contact_teleBase": "Базовий телебачення", "contact_teleBase": "Базова телеметрія",
"contact_teleLocSubtitle": "Дозволити спільне використання даних про місцеположення", "contact_teleLocSubtitle": "Дозволити спільне використання даних про місцеположення",
"contact_settings": "Налаштування контактів", "contact_settings": "Налаштування контактів",
"contact_telemetry": "Телеметрія", "contact_telemetry": "Телеметрія",
@@ -1934,8 +1945,8 @@
"appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале", "appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале",
"path_routeWeight": "{weight}/{max}", "path_routeWeight": "{weight}/{max}",
"settings_telemetryModeUpdated": "Режим телеметрії оновлено", "settings_telemetryModeUpdated": "Режим телеметрії оновлено",
"map_showOverlaps": "Перекриття ключа повторювача", "map_showOverlaps": "Перекриття ключів ретрансляторів",
"map_runTraceWithReturnPath": "Повернутися назад тим же шляхом", "map_runTraceWithReturnPath": "Повернутись назад тим же шляхом",
"@radioStats_noiseFloor": { "@radioStats_noiseFloor": {
"placeholders": { "placeholders": {
"noiseDbm": { "noiseDbm": {
@@ -1979,26 +1990,26 @@
} }
}, },
"chat_sendCooldown": "Будь ласка, зачекайте трохи, перш ніж відправляти знову.", "chat_sendCooldown": "Будь ласка, зачекайте трохи, перш ніж відправляти знову.",
"appSettings_languageHu": "Угорський", "appSettings_languageHu": "Угорська",
"appSettings_jumpToOldestUnreadSubtitle": "При відкритті чату з не прочитаними повідомленнями, прокрутіть до першого не прочитаного повідомлення, а не до останнього.", "appSettings_jumpToOldestUnreadSubtitle": "При відкритті чату з непрочитаними повідомленнями, прокрутіть до першого непрочитаного повідомлення, а не до останнього.",
"appSettings_jumpToOldestUnread": "Перейти до найстарішого непрочитаного повідомлення", "appSettings_jumpToOldestUnread": "Перейти до найстарішого непрочитаного повідомлення",
"appSettings_languageJa": "Японська", "appSettings_languageJa": "Японська",
"appSettings_languageKo": "Кореєська", "appSettings_languageKo": "Корейська",
"radioStats_tooltip": "Статистика радіо та мережі", "radioStats_tooltip": "Статистика радіо та мережі",
"radioStats_screenTitle": "Дані про радіостанції", "radioStats_screenTitle": "Статистика радіо",
"radioStats_notConnected": "Підключіться до пристрою, щоб переглядати статистику радіопередач.", "radioStats_notConnected": "Підключіться до пристрою, щоб переглядати статистику радіо.",
"radioStats_firmwareTooOld": "Статистика радіо приймача вимагає супутнього програмного забезпечення версії 8 або новішої.", "radioStats_firmwareTooOld": "Статистика радіо вимагає прошивки пристрою-компаньйона версії 8 або новішої.",
"radioStats_waiting": "Очікую на отримання даних…", "radioStats_waiting": "Очікування даних…",
"radioStats_noiseFloor": "Рівень шуму: {noiseDbm} дБм", "radioStats_noiseFloor": "Рівень шуму: {noiseDbm} дБм",
"radioStats_lastRssi": "Останній показник RSSI: {rssiDbm} дБм", "radioStats_lastRssi": "Останній показник RSSI: {rssiDbm} дБм",
"radioStats_lastSnr": "Останній показник SNR: {snr} дБ", "radioStats_lastSnr": "Останній показник SNR: {snr} дБ",
"radioStats_txAir": "Час трансляції на телеканалі TX (загальний): {seconds} секунд", "radioStats_txAir": "Час в ефірі TX (загальний): {seconds} секунд",
"radioStats_rxAir": "Загальний час використання RX: {seconds} секунд", "radioStats_rxAir": "Час в ефірі RX (загальний): {seconds} секунд",
"radioStats_chartCaption": "Рівень шуму (дБм) на основі останніх вимірювань.", "radioStats_chartCaption": "Рівень шуму (дБм) на основі останніх вимірювань.",
"radioStats_stripNoise": "Рівень шуму: {noiseDbm} дБм", "radioStats_stripNoise": "Рівень шуму: {noiseDbm} дБм",
"radioStats_stripWaiting": "Отримано статистику радіо…", "radioStats_stripWaiting": "Отримано статистику радіо…",
"radioStats_settingsTile": "Дані про радіостанції", "radioStats_settingsTile": "Статистика радіо",
"radioStats_settingsSubtitle": "Рівень шуму, RSSI, SNR та час, протягом якого пристрій використовує радіоканал.", "radioStats_settingsSubtitle": "Рівень шуму, RSSI, SNR та час в ефірі.",
"@translation_downloadFailed": { "@translation_downloadFailed": {
"placeholders": { "placeholders": {
"error": { "error": {
@@ -2012,9 +2023,9 @@
"translation_enableSubtitle": "Перекладати отримані повідомлення та дозволяти попередній переклад перед відправкою.", "translation_enableSubtitle": "Перекладати отримані повідомлення та дозволяти попередній переклад перед відправкою.",
"translation_composerSubtitle": "Контролює стан ікон перекладу, який використовується за замовчуванням.", "translation_composerSubtitle": "Контролює стан ікон перекладу, який використовується за замовчуванням.",
"translation_targetLanguage": "Цільова мова", "translation_targetLanguage": "Цільова мова",
"translation_useAppLanguage": "Використовуйте мову додатку", "translation_useAppLanguage": "Використовувати мову застосунку",
"translation_downloadedModelLabel": "Завантажений шаблон", "translation_downloadedModelLabel": "Завантажений шаблон",
"translation_presetModelLabel": "Заздалегідь налаштований модель від Hugging Face", "translation_presetModelLabel": "Попередньо налаштована модель з Hugging Face",
"translation_manualUrlLabel": "Посилання на веб-сторінку з інструкцією", "translation_manualUrlLabel": "Посилання на веб-сторінку з інструкцією",
"translation_downloadModel": "Завантажити модель", "translation_downloadModel": "Завантажити модель",
"translation_downloading": "Завантаження...", "translation_downloading": "Завантаження...",
@@ -2025,7 +2036,7 @@
"translation_deleteModel": "Видалити модель", "translation_deleteModel": "Видалити модель",
"translation_modelDownloaded": "Модель перекладу завантажена.", "translation_modelDownloaded": "Модель перекладу завантажена.",
"translation_downloadStopped": "Завантаження призупинено.", "translation_downloadStopped": "Завантаження призупинено.",
"translation_downloadFailed": "Не вдалося завантажити: {error}", "translation_downloadFailed": "Не вдалось завантажити: {error}",
"translation_enterUrlFirst": "Спочатку введіть URL моделі.", "translation_enterUrlFirst": "Спочатку введіть URL моделі.",
"@scanner_linuxPairingPinPrompt": { "@scanner_linuxPairingPinPrompt": {
"placeholders": { "placeholders": {
@@ -2053,7 +2064,7 @@
"scanner_linuxPairingPinPrompt": "Введіть PIN для {deviceName} (залиште порожнім, якщо його немає).", "scanner_linuxPairingPinPrompt": "Введіть PIN для {deviceName} (залиште порожнім, якщо його немає).",
"scanner_linuxPairingHidePin": "Приховати PIN", "scanner_linuxPairingHidePin": "Приховати PIN",
"repeater_cliQuickClockSync": "Синхронізація годинника", "repeater_cliQuickClockSync": "Синхронізація годинника",
"repeater_cliQuickDiscovery": "Відкрити сусідів", "repeater_cliQuickDiscovery": "Виявити сусідів",
"@repeater_clockSyncAfterLogin": { "@repeater_clockSyncAfterLogin": {
"description": "Repeater setting: auto sync device clock after successful login" "description": "Repeater setting: auto sync device clock after successful login"
}, },
@@ -2062,9 +2073,60 @@
}, },
"repeater_clockSyncAfterLoginSubtitle": "Автоматично надсилати повідомлення \"синхронізація годин\" після успішного входу.", "repeater_clockSyncAfterLoginSubtitle": "Автоматично надсилати повідомлення \"синхронізація годин\" після успішного входу.",
"repeater_clockSyncAfterLogin": "Синхронізація годин після входу", "repeater_clockSyncAfterLogin": "Синхронізація годин після входу",
"repeater_guestTools": "Інструменти для гостей", "repeater_guestTools": "Гостьові інструменти",
"repeater_guest": "Інформація про ретранслятор", "repeater_guest": "Інформація про ретранслятор",
"room_guest": "Інформація про сервер кімнати", "room_guest": "Інформація про сервер кімнати",
"chat_sendMessage": "Надіслати повідомлення", "chat_sendMessage": "Надіслати повідомлення",
"settings_multiAck": "Багато підтверджень" "background_serviceTitle": "MeshCore працює",
"background_serviceText": "Підтримує з'єднання BLE",
"appSettings_translationModelDeleted": "Видалено {name}",
"appSettings_translationModelDeleteFailed": "Не вдалось видалити: {error}",
"channels_channelUpdateFailed": "Не вдалось оновити канал: {error}",
"contact_typeChat": "Чат",
"contact_typeRepeater": "Ретранслятор",
"contact_typeRoom": "Кімната",
"contact_typeSensor": "Сенсор",
"contact_typeUnknown": "Невідомо",
"settings_multiAck": "Багато підтверджень",
"map_sharedAt": "Поділено",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losBlockedSpotsHint": "Натисніть на заблоковане місце, щоб виділити його на карті.",
"losBlockedSpotsTitle": "Заблоковані місця",
"losSelectedObstructionTitle": "Вибраний об'єкт перешкоди",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})."
} }
+84 -1
View File
@@ -109,6 +109,8 @@
"settings_privacyModeEnabled": "隐私模式已启用", "settings_privacyModeEnabled": "隐私模式已启用",
"settings_privacyModeDisabled": "隐私模式已关闭", "settings_privacyModeDisabled": "隐私模式已关闭",
"settings_actions": "操作", "settings_actions": "操作",
"settings_deleteAllPaths": "Delete All Paths",
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
"settings_sendAdvertisement": "发送广播", "settings_sendAdvertisement": "发送广播",
"settings_sendAdvertisementSubtitle": "立即发送广播", "settings_sendAdvertisementSubtitle": "立即发送广播",
"settings_advertisementSent": "已发送广播", "settings_advertisementSent": "已发送广播",
@@ -2071,5 +2073,86 @@
"repeater_guestTools": "访客工具", "repeater_guestTools": "访客工具",
"repeater_guest": "重复器信息", "repeater_guest": "重复器信息",
"chat_sendMessage": "发送消息", "chat_sendMessage": "发送消息",
"room_guest": "服务器信息" "room_guest": "服务器信息",
"common_done": "Done",
"background_serviceTitle": "MeshCore running",
"background_serviceText": "Keeping BLE connected",
"appSettings_translationModelDeleted": "Deleted {name}",
"@appSettings_translationModelDeleted": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
"@appSettings_translationModelDeleteFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"channels_channelUpdateFailed": "Failed to update channel: {error}",
"@channels_channelUpdateFailed": {
"placeholders": {
"error": {
"type": "String"
}
}
},
"map_type": "Type",
"map_path": "Path",
"map_location": "Location",
"map_estLocation": "Est. Location",
"map_publicKey": "Public Key",
"map_publicKeyPrefixHint": "e.g. ab12",
"contact_typeChat": "Chat",
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"channels_via": "via {path}",
"chat_score": "Score",
"map_sharedAt": "已分享",
"@losBlockedSpotChip": {
"placeholders": {
"distance": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
}
}
},
"@losSelectedObstructionDetails": {
"placeholders": {
"obstruction": {
"type": "String"
},
"heightUnit": {
"type": "String"
},
"distanceFromA": {
"type": "String"
},
"distanceUnit": {
"type": "String"
},
"distanceFromB": {
"type": "String"
}
}
},
"losBlockedSpotsTitle": "被占用区域",
"losBlockedSpotsHint": "点击地图上的某个被遮盖的区域,以突出显示该区域。",
"losSelectedObstructionTitle": "选择性阻碍",
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})."
} }
+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(); final notificationService = NotificationService();
await notificationService.initialize(); await notificationService.initialize();
await backgroundService.initialize(); await backgroundService.initialize();
backgroundService.setLanguageOverrideProvider(
() => appSettingsService.settings.languageOverride,
);
_registerThirdPartyLicenses(); _registerThirdPartyLicenses();
await chatTextScaleService.initialize(); await chatTextScaleService.initialize();
+32 -15
View File
@@ -17,6 +17,7 @@ class Contact {
final double? longitude; final double? longitude;
final DateTime lastSeen; final DateTime lastSeen;
final DateTime lastMessageAt; final DateTime lastMessageAt;
final DateTime? lastModified;
final bool isActive; final bool isActive;
final bool wasPulled; final bool wasPulled;
final Uint8List? rawPacket; final Uint8List? rawPacket;
@@ -33,6 +34,7 @@ class Contact {
this.latitude, this.latitude,
this.longitude, this.longitude,
required this.lastSeen, required this.lastSeen,
this.lastModified,
DateTime? lastMessageAt, DateTime? lastMessageAt,
this.isActive = true, this.isActive = true,
this.wasPulled = false, this.wasPulled = false,
@@ -41,7 +43,10 @@ class Contact {
String get publicKeyHex => pubKeyToHex(publicKey); 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) { switch (type) {
case advTypeChat: case advTypeChat:
return 'Chat'; return 'Chat';
@@ -56,17 +61,6 @@ class Contact {
} }
} }
String get pathLabel {
if (pathOverride != null) {
if (pathOverride! < 0) return 'Flood (forced)';
if (pathOverride == 0) return 'Direct (forced)';
return '$pathOverride hops (forced)';
}
if (pathLength < 0) return 'Flood';
if (pathLength == 0) return 'Direct';
return '$pathLength hops';
}
bool get hasLocation { bool get hasLocation {
const double epsilon = 1e-6; const double epsilon = 1e-6;
final lat = latitude ?? 0.0; final lat = latitude ?? 0.0;
@@ -94,6 +88,7 @@ class Contact {
double? longitude, double? longitude,
DateTime? lastSeen, DateTime? lastSeen,
DateTime? lastMessageAt, DateTime? lastMessageAt,
DateTime? lastModified,
bool? isActive, bool? isActive,
Uint8List? rawPacket, Uint8List? rawPacket,
}) { }) {
@@ -114,6 +109,7 @@ class Contact {
longitude: longitude ?? this.longitude, longitude: longitude ?? this.longitude,
lastSeen: lastSeen ?? this.lastSeen, lastSeen: lastSeen ?? this.lastSeen,
lastMessageAt: lastMessageAt ?? this.lastMessageAt, lastMessageAt: lastMessageAt ?? this.lastMessageAt,
lastModified: lastModified ?? this.lastModified,
isActive: isActive ?? this.isActive, isActive: isActive ?? this.isActive,
rawPacket: rawPacket ?? this.rawPacket, rawPacket: rawPacket ?? this.rawPacket,
); );
@@ -182,16 +178,34 @@ class Contact {
return null; return null;
} }
final lastMod = reader.readUInt32LE(); // mandatory last_advert_timestamp
final lastAdvertTimestamp = reader.readUInt32LE();
double? lat, lon; double? lat, lon;
if (reader.remaining >= 8) { DateTime? lastModified;
if (reader.remaining >= 12) {
final latRaw = reader.readInt32LE();
final lonRaw = reader.readInt32LE();
final lastModRaw = reader.readUInt32LE();
// TODO: should this be &&?
if (latRaw != 0 || lonRaw != 0) {
lat = latRaw / 1e6;
lon = lonRaw / 1e6;
}
if (lastModRaw != 0) {
lastModified = DateTime.fromMillisecondsSinceEpoch(lastModRaw * 1000);
}
} else if (reader.remaining >= 8) {
// Old layout: gps without lastmod
final latRaw = reader.readInt32LE(); final latRaw = reader.readInt32LE();
final lonRaw = reader.readInt32LE(); final lonRaw = reader.readInt32LE();
if (latRaw != 0 || lonRaw != 0) { if (latRaw != 0 || lonRaw != 0) {
lat = latRaw / 1e6; lat = latRaw / 1e6;
lon = lonRaw / 1e6; lon = lonRaw / 1e6;
} }
appLogger.info(
'Contact ${pubKeyToHex(pubKey).substring(0, 8)} has gps but no lastmod (legacy firmware layout)',
);
} }
return Contact( return Contact(
@@ -203,7 +217,10 @@ class Contact {
path: pathBytes, path: pathBytes,
latitude: lat, latitude: lat,
longitude: lon, longitude: lon,
lastSeen: DateTime.fromMillisecondsSinceEpoch(lastMod * 1000), lastSeen: DateTime.fromMillisecondsSinceEpoch(
lastAdvertTimestamp * 1000,
),
lastModified: lastModified,
isActive: true, isActive: true,
rawPacket: null, rawPacket: null,
); );
+3 -3
View File
@@ -228,7 +228,7 @@ class RadioSettings {
frequencyMHz: 433.0, frequencyMHz: 433.0,
bandwidth: LoRaBandwidth.bw250, bandwidth: LoRaBandwidth.bw250,
spreadingFactor: LoRaSpreadingFactor.sf11, spreadingFactor: LoRaSpreadingFactor.sf11,
codingRate: LoRaCodingRate.cr4_5, codingRate: LoRaCodingRate.cr4_8,
txPowerDbm: 20, txPowerDbm: 20,
), ),
), ),
@@ -238,7 +238,7 @@ class RadioSettings {
frequencyMHz: 869.0, frequencyMHz: 869.0,
bandwidth: LoRaBandwidth.bw250, bandwidth: LoRaBandwidth.bw250,
spreadingFactor: LoRaSpreadingFactor.sf11, spreadingFactor: LoRaSpreadingFactor.sf11,
codingRate: LoRaCodingRate.cr4_5, codingRate: LoRaCodingRate.cr4_8,
txPowerDbm: 14, txPowerDbm: 14,
), ),
), ),
@@ -248,7 +248,7 @@ class RadioSettings {
frequencyMHz: 918.0, frequencyMHz: 918.0,
bandwidth: LoRaBandwidth.bw250, bandwidth: LoRaBandwidth.bw250,
spreadingFactor: LoRaSpreadingFactor.sf11, spreadingFactor: LoRaSpreadingFactor.sf11,
codingRate: LoRaCodingRate.cr4_5, codingRate: LoRaCodingRate.cr4_8,
txPowerDbm: 20, txPowerDbm: 20,
), ),
), ),
+9 -4
View File
@@ -1237,15 +1237,20 @@ class AppSettingsScreen extends StatelessWidget {
if (!context.mounted) return; if (!context.mounted) return;
showDismissibleSnackBar( showDismissibleSnackBar(
context, context,
// TODO: l10n content: Text(
content: Text('Deleted ${translationModelFriendlyName(model)}.'), context.l10n.appSettings_translationModelDeleted(
translationModelFriendlyName(model),
),
),
); );
} catch (error) { } catch (error) {
if (!context.mounted) return; if (!context.mounted) return;
showDismissibleSnackBar( showDismissibleSnackBar(
context, context,
content: Text('Delete failed: $error'), content: Text(
); // TODO: l10n context.l10n.appSettings_translationModelDeleteFailed('$error'),
),
);
} }
} }
+101 -42
View File
@@ -4,7 +4,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:latlong2/latlong.dart'; import 'package:intl/intl.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart'; import '../connector/meshcore_connector.dart';
@@ -32,13 +32,19 @@ import '../widgets/message_translation_button.dart';
import '../widgets/message_status_icon.dart'; import '../widgets/message_status_icon.dart';
import '../widgets/radio_stats_entry.dart'; import '../widgets/radio_stats_entry.dart';
import '../widgets/translated_message_content.dart'; import '../widgets/translated_message_content.dart';
import '../widgets/unread_divider.dart';
import 'channel_message_path_screen.dart'; import 'channel_message_path_screen.dart';
import 'map_screen.dart'; import 'map_screen.dart';
class ChannelChatScreen extends StatefulWidget { class ChannelChatScreen extends StatefulWidget {
final Channel channel; final Channel channel;
final int initialUnreadCount;
const ChannelChatScreen({super.key, required this.channel}); const ChannelChatScreen({
super.key,
required this.channel,
this.initialUnreadCount = 0,
});
@override @override
State<ChannelChatScreen> createState() => _ChannelChatScreenState(); State<ChannelChatScreen> createState() => _ChannelChatScreenState();
@@ -55,32 +61,46 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
MeshCoreConnector? _connector; MeshCoreConnector? _connector;
DateTime? _lastChannelSendAt; DateTime? _lastChannelSendAt;
bool _channelSkipNextBottomSnap = false; bool _channelSkipNextBottomSnap = false;
String? _unreadDividerMessageId;
String? _cachedFormatLocale;
late DateFormat _hmFormat;
late DateFormat _mdFormat;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_textFieldFocusNode.addListener(_onTextFieldFocusChange); _textFieldFocusNode.addListener(_onTextFieldFocusChange);
_scrollController.onScrollNearTop = _loadOlderMessages; _scrollController.onScrollNearTop = _loadOlderMessages;
_scrollController.showJumpToBottom.addListener(_clearDividerAtBottom);
SchedulerBinding.instance.addPostFrameCallback((_) { SchedulerBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
final connector = context.read<MeshCoreConnector>(); final connector = context.read<MeshCoreConnector>();
final settings = context.read<AppSettingsService>().settings; final settings = context.read<AppSettingsService>().settings;
final idx = widget.channel.index; final idx = widget.channel.index;
final unread = connector.getUnreadCountForChannelIndex(idx); final unread = widget.initialUnreadCount;
final messages = connector.getChannelMessages(widget.channel);
ChannelMessage? anchor; ChannelMessage? anchor;
if (settings.jumpToOldestUnread && unread > 0) { if (unread > 0) {
anchor = _findOldestUnreadChannelAnchor( anchor = _findOldestUnreadChannelAnchor(messages, unread);
connector.getChannelMessages(widget.channel),
unread,
);
} }
setState(() {
if (anchor != null) _unreadDividerMessageId = anchor.messageId;
});
connector.setActiveChannel(idx); connector.setActiveChannel(idx);
_connector = connector; _connector = connector;
if (anchor != null) { if (anchor != null && settings.jumpToOldestUnread) {
_channelSkipNextBottomSnap = true; _channelSkipNextBottomSnap = true;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
_scrollToMessage(anchor!.messageId); _scrollController.jumpToEstimatedOffset(
unreadCount: unread,
totalMessages: messages.length,
onJumped: () {
if (!mounted) return;
_scrollToMessage(anchor!.messageId);
},
);
}); });
} }
}); });
@@ -102,6 +122,13 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
return oldest; return oldest;
} }
void _clearDividerAtBottom() {
if (!_scrollController.showJumpToBottom.value &&
_unreadDividerMessageId != null) {
setState(() => _unreadDividerMessageId = null);
}
}
void _onTextFieldFocusChange() { void _onTextFieldFocusChange() {
if (_textFieldFocusNode.hasFocus && mounted) { if (_textFieldFocusNode.hasFocus && mounted) {
_scrollController.handleKeyboardOpen(); _scrollController.handleKeyboardOpen();
@@ -123,6 +150,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
@override @override
void dispose() { void dispose() {
_connector?.setActiveChannel(null); _connector?.setActiveChannel(null);
_scrollController.showJumpToBottom.removeListener(_clearDividerAtBottom);
_textFieldFocusNode.removeListener(_onTextFieldFocusChange); _textFieldFocusNode.removeListener(_onTextFieldFocusChange);
_textFieldFocusNode.dispose(); _textFieldFocusNode.dispose();
_textController.dispose(); _textController.dispose();
@@ -321,6 +349,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
if (!_messageKeys.containsKey(message.messageId)) { if (!_messageKeys.containsKey(message.messageId)) {
_messageKeys[message.messageId] = GlobalKey(); _messageKeys[message.messageId] = GlobalKey();
} }
final isUnreadAnchor =
_unreadDividerMessageId != null &&
message.messageId == _unreadDividerMessageId;
return Container( return Container(
key: _messageKeys[message.messageId]!, key: _messageKeys[message.messageId]!,
child: Builder( child: Builder(
@@ -329,10 +360,17 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
.select<ChatTextScaleService, double>( .select<ChatTextScaleService, double>(
(service) => service.scale, (service) => service.scale,
); );
return _buildMessageBubble( final bubble = _buildMessageBubble(
message, message,
textScale, textScale,
); );
if (isUnreadAnchor) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [const UnreadDivider(), bubble],
);
}
return bubble;
}, },
), ),
); );
@@ -352,12 +390,24 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
); );
} }
void _markAsUnread(ChannelMessage message) {
final connector = context.read<MeshCoreConnector>();
final messages = connector.getChannelMessages(widget.channel);
var count = 0;
var found = false;
for (final m in messages) {
if (m.messageId == message.messageId) found = true;
if (found && !m.isOutgoing) count++;
}
connector.setChannelUnreadCount(widget.channel.index, count);
}
Widget _buildMessageBubble(ChannelMessage message, double textScale) { Widget _buildMessageBubble(ChannelMessage message, double textScale) {
final settingsService = context.watch<AppSettingsService>(); final settingsService = context.watch<AppSettingsService>();
final enableTracing = settingsService.settings.enableMessageTracing; final enableTracing = settingsService.settings.enableMessageTracing;
final isOutgoing = message.isOutgoing; final isOutgoing = message.isOutgoing;
final gifId = GifHelper.parseGif(message.text); final gifId = GifHelper.parseGif(message.text);
final poi = _parsePoiMessage(message.text); final poi = parseMarkerText(message.text);
final translatedDisplayText = final translatedDisplayText =
message.translatedText != null && message.translatedText != null &&
message.translatedText!.trim().isNotEmpty message.translatedText!.trim().isNotEmpty
@@ -445,6 +495,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
poi, poi,
isOutgoing, isOutgoing,
textScale, textScale,
message.senderName,
trailing: (!enableTracing && isOutgoing) trailing: (!enableTracing && isOutgoing)
? Padding( ? Padding(
padding: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.only(bottom: 2),
@@ -555,7 +606,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
? const EdgeInsets.symmetric(horizontal: 8) ? const EdgeInsets.symmetric(horizontal: 8)
: EdgeInsets.zero, : EdgeInsets.zero,
child: Text( child: Text(
'via ${_formatPathPrefixes(displayPath)}', context.l10n.channels_via(
_formatPathPrefixes(displayPath),
),
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: Colors.grey[600], color: Colors.grey[600],
@@ -576,7 +629,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
_formatTime(message.timestamp), _formatTime(context, message.timestamp),
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: Colors.grey[600], color: Colors.grey[600],
@@ -701,7 +754,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
final previewTextColor = colorScheme.onSurface.withValues(alpha: 0.7); final previewTextColor = colorScheme.onSurface.withValues(alpha: 0.7);
final gifId = GifHelper.parseGif(replyText); final gifId = GifHelper.parseGif(replyText);
final poi = _parsePoiMessage(replyText); final poi = parseMarkerText(replyText);
Widget contentPreview; Widget contentPreview;
if (gifId != null) { if (gifId != null) {
@@ -812,24 +865,12 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
); );
} }
_PoiInfo? _parsePoiMessage(String text) {
final trimmed = text.trim();
final match = RegExp(
r'm:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|',
).firstMatch(trimmed);
if (match == null) return null;
final lat = double.tryParse(match.group(1) ?? '');
final lon = double.tryParse(match.group(2) ?? '');
if (lat == null || lon == null) return null;
final label = match.group(3) ?? '';
return _PoiInfo(lat: lat, lon: lon, label: label);
}
Widget _buildPoiMessage( Widget _buildPoiMessage(
BuildContext context, BuildContext context,
_PoiInfo poi, MarkerPayload poi,
bool isOutgoing, bool isOutgoing,
double textScale, { double textScale,
String senderName, {
Widget? trailing, Widget? trailing,
}) { }) {
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
@@ -849,12 +890,22 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 32, minHeight: 32), constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
onPressed: () { onPressed: () {
final selfName = context.read<MeshCoreConnector>().selfName ?? 'Me';
final fromName = isOutgoing ? selfName : senderName;
final key = buildSharedMarkerKey(
sourceId: 'channel:${widget.channel.index}',
label: poi.label,
fromName: fromName,
flags: poi.flags,
isChannel: true,
);
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => MapScreen( builder: (context) => MapScreen(
highlightPosition: LatLng(poi.lat, poi.lon), highlightPosition: poi.position,
highlightLabel: poi.label, highlightLabel: poi.label,
highlightMarkerKey: key,
), ),
), ),
); );
@@ -1225,14 +1276,21 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
); );
} }
String _formatTime(DateTime time) { String _formatTime(BuildContext context, DateTime time) {
final now = DateTime.now(); final now = DateTime.now();
final diff = now.difference(time); final diff = now.difference(time);
final locale = Localizations.localeOf(context).toString();
if (locale != _cachedFormatLocale) {
_cachedFormatLocale = locale;
_hmFormat = DateFormat.Hm(locale);
_mdFormat = DateFormat.Md(locale);
}
final hm = _hmFormat.format(time);
if (diff.inDays > 0) { if (diff.inDays > 0) {
return '${time.day}/${time.month} ${time.hour}:${time.minute.toString().padLeft(2, '0')}'; return '${_mdFormat.format(time)} $hm';
} else { } else {
return '${time.hour}:${time.minute.toString().padLeft(2, '0')}'; return hm;
} }
} }
@@ -1288,6 +1346,15 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
_copyMessageText(message.text); _copyMessageText(message.text);
}, },
), ),
if (!message.isOutgoing)
ListTile(
leading: const Icon(Icons.mark_chat_unread_outlined),
title: Text(context.l10n.chat_markAsUnread),
onTap: () {
Navigator.pop(sheetContext);
_markAsUnread(message);
},
),
ListTile( ListTile(
leading: const Icon(Icons.delete_outline), leading: const Icon(Icons.delete_outline),
title: Text(context.l10n.common_delete), title: Text(context.l10n.common_delete),
@@ -1507,11 +1574,3 @@ class _SwipeReplyBubbleState extends State<_SwipeReplyBubble> {
); );
} }
} }
class _PoiInfo {
final double lat;
final double lon;
final String label;
const _PoiInfo({required this.lat, required this.lon, required this.label});
}
+98 -3
View File
@@ -1,6 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
@@ -304,6 +304,8 @@ class ChannelMessagePathMapScreen extends StatefulWidget {
class _ChannelMessagePathMapScreenState class _ChannelMessagePathMapScreenState
extends State<ChannelMessagePathMapScreen> { extends State<ChannelMessagePathMapScreen> {
static const double _labelZoomThreshold = 8.5; static const double _labelZoomThreshold = 8.5;
static const double _mapMinZoom = 2.0;
static const double _mapMaxZoom = 18.0;
final MapController _mapController = MapController(); final MapController _mapController = MapController();
Uint8List? _selectedPath; Uint8List? _selectedPath;
@@ -330,6 +332,18 @@ class _ChannelMessagePathMapScreenState
} }
} }
@override
void dispose() {
_mapController.dispose();
super.dispose();
}
bool _isDesktopPlatform(TargetPlatform platform) {
return platform == TargetPlatform.linux ||
platform == TargetPlatform.windows ||
platform == TargetPlatform.macOS;
}
double _getPathDistance(List<LatLng> points) { double _getPathDistance(List<LatLng> points) {
double totalDistance = 0.0; double totalDistance = 0.0;
final distanceCalculator = Distance(); final distanceCalculator = Distance();
@@ -357,6 +371,70 @@ class _ChannelMessagePathMapScreenState
}); });
} }
void _zoomMapBy(double delta) {
final camera = _mapController.camera;
final nextZoom = (camera.zoom + delta)
.clamp(_mapMinZoom, _mapMaxZoom)
.toDouble();
_mapController.move(camera.center, nextZoom);
}
void _resetMapView({
required LatLng initialCenter,
required double initialZoom,
required LatLngBounds? bounds,
}) {
if (bounds != null) {
_mapController.fitCamera(
CameraFit.bounds(
bounds: bounds,
padding: const EdgeInsets.all(64),
maxZoom: 16,
),
);
return;
}
_mapController.move(initialCenter, initialZoom);
}
Widget _buildDesktopMapControls({
required LatLng initialCenter,
required double initialZoom,
required LatLngBounds? bounds,
}) {
return Positioned(
left: 16,
top: 16,
child: Card(
elevation: 4,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Zoom in',
onPressed: () => _zoomMapBy(1),
),
IconButton(
icon: const Icon(Icons.remove),
tooltip: 'Zoom out',
onPressed: () => _zoomMapBy(-1),
),
IconButton(
icon: const Icon(Icons.my_location),
tooltip: 'Center map',
onPressed: () => _resetMapView(
initialCenter: initialCenter,
initialZoom: initialZoom,
bounds: bounds,
),
),
],
),
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<MeshCoreConnector>( return Consumer<MeshCoreConnector>(
@@ -372,6 +450,7 @@ class _ChannelMessagePathMapScreenState
primaryPath, primaryPath,
widget.message.pathVariants, widget.message.pathVariants,
); );
final isDesktop = _isDesktopPlatform(defaultTargetPlatform);
final selectedPathTmp = _resolveSelectedPath( final selectedPathTmp = _resolveSelectedPath(
_selectedPath, _selectedPath,
observedPaths, observedPaths,
@@ -451,10 +530,20 @@ class _ChannelMessagePathMapScreenState
padding: const EdgeInsets.all(64), padding: const EdgeInsets.all(64),
maxZoom: 16, maxZoom: 16,
), ),
minZoom: 2.0, minZoom: _mapMinZoom,
maxZoom: 18.0, maxZoom: _mapMaxZoom,
interactionOptions: InteractionOptions( interactionOptions: InteractionOptions(
flags: ~InteractiveFlag.rotate, flags: ~InteractiveFlag.rotate,
scrollWheelVelocity: isDesktop ? 0.012 : 0.005,
cursorKeyboardRotationOptions:
CursorKeyboardRotationOptions.disabled(),
keyboardOptions: isDesktop
? const KeyboardOptions(
enableArrowKeysPanning: true,
enableWASDPanning: true,
enableRFZooming: true,
)
: const KeyboardOptions.disabled(),
), ),
onPositionChanged: (camera, hasGesture) { onPositionChanged: (camera, hasGesture) {
final shouldShow = camera.zoom >= _labelZoomThreshold; final shouldShow = camera.zoom >= _labelZoomThreshold;
@@ -486,6 +575,12 @@ class _ChannelMessagePathMapScreenState
), ),
], ],
), ),
if (isDesktop)
_buildDesktopMapControls(
initialCenter: initialCenter,
initialZoom: initialZoom,
bounds: bounds,
),
if (observedPaths.length > 1) if (observedPaths.length > 1)
_buildPathSelector(context, observedPaths, selectedIndex, ( _buildPathSelector(context, observedPaths, selectedIndex, (
index, index,
+10 -2
View File
@@ -492,13 +492,19 @@ class _ChannelsScreenState extends State<ChannelsScreen>
], ],
), ),
onTap: () async { onTap: () async {
final unread = connector.getUnreadCountForChannelIndex(
channel.index,
);
connector.markChannelRead(channel.index); connector.markChannelRead(channel.index);
await Future.delayed(const Duration(milliseconds: 50)); await Future.delayed(const Duration(milliseconds: 50));
if (context.mounted) { if (context.mounted) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ChannelChatScreen(channel: channel), builder: (context) => ChannelChatScreen(
channel: channel,
initialUnreadCount: unread,
),
), ),
); );
} }
@@ -1492,7 +1498,9 @@ class _ChannelsScreenState extends State<ChannelsScreen>
if (!context.mounted) return; if (!context.mounted) return;
showDismissibleSnackBar( showDismissibleSnackBar(
context, context,
content: Text('Failed to update channel: $e'), content: Text(
context.l10n.channels_channelUpdateFailed('$e'),
),
); );
} }
}, },
+114 -52
View File
@@ -9,7 +9,6 @@ import 'package:meshcore_open/screens/path_trace_map.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../utils/platform_info.dart'; import '../utils/platform_info.dart';
import 'package:latlong2/latlong.dart';
import '../connector/meshcore_connector.dart'; import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart'; import '../connector/meshcore_protocol.dart';
@@ -20,6 +19,7 @@ import '../helpers/gif_helper.dart';
import '../helpers/path_helper.dart'; import '../helpers/path_helper.dart';
import '../models/channel_message.dart'; import '../models/channel_message.dart';
import '../models/contact.dart'; import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../models/message.dart'; import '../models/message.dart';
import '../models/path_history.dart'; import '../models/path_history.dart';
import '../models/translation_support.dart'; import '../models/translation_support.dart';
@@ -44,12 +44,18 @@ import '../widgets/translated_message_content.dart';
import '../utils/app_logger.dart'; import '../utils/app_logger.dart';
import '../l10n/l10n.dart'; import '../l10n/l10n.dart';
import '../helpers/snack_bar_builder.dart'; import '../helpers/snack_bar_builder.dart';
import '../widgets/unread_divider.dart';
import 'telemetry_screen.dart'; import 'telemetry_screen.dart';
class ChatScreen extends StatefulWidget { class ChatScreen extends StatefulWidget {
final Contact contact; final Contact contact;
final int initialUnreadCount;
const ChatScreen({super.key, required this.contact}); const ChatScreen({
super.key,
required this.contact,
this.initialUnreadCount = 0,
});
@override @override
State<ChatScreen> createState() => _ChatScreenState(); State<ChatScreen> createState() => _ChatScreenState();
@@ -63,6 +69,7 @@ class _ChatScreenState extends State<ChatScreen> {
bool _isLoadingOlder = false; bool _isLoadingOlder = false;
MeshCoreConnector? _connector; MeshCoreConnector? _connector;
Message? _pendingUnreadScrollTarget; Message? _pendingUnreadScrollTarget;
String? _unreadDividerMessageId;
DateTime? _lastTextSendAt; DateTime? _lastTextSendAt;
@override @override
@@ -70,34 +77,47 @@ class _ChatScreenState extends State<ChatScreen> {
super.initState(); super.initState();
_textFieldFocusNode.addListener(_onTextFieldFocusChange); _textFieldFocusNode.addListener(_onTextFieldFocusChange);
_scrollController.onScrollNearTop = _loadOlderMessages; _scrollController.onScrollNearTop = _loadOlderMessages;
_scrollController.showJumpToBottom.addListener(_clearDividerAtBottom);
SchedulerBinding.instance.addPostFrameCallback((_) { SchedulerBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
final connector = context.read<MeshCoreConnector>(); final connector = context.read<MeshCoreConnector>();
final settings = context.read<AppSettingsService>().settings; final settings = context.read<AppSettingsService>().settings;
final keyHex = widget.contact.publicKeyHex; final keyHex = widget.contact.publicKeyHex;
final unread = connector.getUnreadCountForContactKey(keyHex); final unread = widget.initialUnreadCount;
final messages = connector.getMessages(widget.contact);
Message? anchor; Message? anchor;
if (settings.jumpToOldestUnread && unread > 0) { if (unread > 0) {
anchor = _findOldestUnreadAnchor( anchor = _findOldestUnreadAnchor(messages, unread);
connector.getMessages(widget.contact),
unread,
);
} }
setState(() {
if (anchor != null) _unreadDividerMessageId = anchor.messageId;
if (anchor != null && settings.jumpToOldestUnread) {
_pendingUnreadScrollTarget = anchor;
}
});
connector.setActiveContact(keyHex); connector.setActiveContact(keyHex);
_connector = connector; _connector = connector;
if (anchor != null) { if (anchor != null && settings.jumpToOldestUnread) {
setState(() => _pendingUnreadScrollTarget = anchor);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
final ctx = _unreadScrollKey.currentContext; _scrollController.jumpToEstimatedOffset(
if (ctx != null) { unreadCount: unread,
Scrollable.ensureVisible( totalMessages: messages.length,
ctx, onJumped: () async {
duration: const Duration(milliseconds: 350), if (!mounted) return;
alignment: 0.15, final ctx = _unreadScrollKey.currentContext;
); if (ctx != null) {
} await Scrollable.ensureVisible(
setState(() => _pendingUnreadScrollTarget = null); ctx,
duration: const Duration(milliseconds: 350),
alignment: 0.15,
);
}
if (mounted) {
setState(() => _pendingUnreadScrollTarget = null);
}
},
);
}); });
} }
}); });
@@ -116,6 +136,13 @@ class _ChatScreenState extends State<ChatScreen> {
return oldest; return oldest;
} }
void _clearDividerAtBottom() {
if (!_scrollController.showJumpToBottom.value &&
_unreadDividerMessageId != null) {
setState(() => _unreadDividerMessageId = null);
}
}
void _onTextFieldFocusChange() { void _onTextFieldFocusChange() {
if (_textFieldFocusNode.hasFocus && mounted) { if (_textFieldFocusNode.hasFocus && mounted) {
_scrollController.handleKeyboardOpen(); _scrollController.handleKeyboardOpen();
@@ -137,6 +164,7 @@ class _ChatScreenState extends State<ChatScreen> {
@override @override
void dispose() { void dispose() {
_connector?.setActiveContact(null); _connector?.setActiveContact(null);
_scrollController.showJumpToBottom.removeListener(_clearDividerAtBottom);
_textFieldFocusNode.removeListener(_onTextFieldFocusChange); _textFieldFocusNode.removeListener(_onTextFieldFocusChange);
_textFieldFocusNode.dispose(); _textFieldFocusNode.dispose();
_textController.dispose(); _textController.dispose();
@@ -479,6 +507,7 @@ class _ChatScreenState extends State<ChatScreen> {
senderName: resolvedContact.type == advTypeRoom senderName: resolvedContact.type == advTypeRoom
? "${contact.name} [$fourByteHex]" ? "${contact.name} [$fourByteHex]"
: contact.name, : contact.name,
sourceId: widget.contact.publicKeyHex,
isRoomServer: resolvedContact.type == advTypeRoom, isRoomServer: resolvedContact.type == advTypeRoom,
textScale: textScale, textScale: textScale,
onTap: () => _openMessagePath(message, contact), onTap: () => _openMessagePath(message, contact),
@@ -486,10 +515,19 @@ class _ChatScreenState extends State<ChatScreen> {
onRetryReaction: (msg, emoji) => onRetryReaction: (msg, emoji) =>
_sendReaction(msg, contact, emoji), _sendReaction(msg, contact, emoji),
); );
final isUnreadAnchor =
_unreadDividerMessageId != null &&
message.messageId == _unreadDividerMessageId;
final child = isUnreadAnchor
? Column(
mainAxisSize: MainAxisSize.min,
children: [const UnreadDivider(), bubble],
)
: bubble;
if (identical(message, _pendingUnreadScrollTarget)) { if (identical(message, _pendingUnreadScrollTarget)) {
return KeyedSubtree(key: _unreadScrollKey, child: bubble); return KeyedSubtree(key: _unreadScrollKey, child: child);
} }
return bubble; return child;
}, },
); );
}, },
@@ -497,6 +535,18 @@ class _ChatScreenState extends State<ChatScreen> {
); );
} }
void _markAsUnread(Message message) {
final connector = context.read<MeshCoreConnector>();
final messages = connector.getMessages(widget.contact);
var count = 0;
var found = false;
for (final m in messages) {
if (m.messageId == message.messageId) found = true;
if (found && !m.isOutgoing && !m.isCli) count++;
}
connector.setContactUnreadCount(widget.contact.publicKeyHex, count);
}
Widget _buildInputBar(MeshCoreConnector connector) { Widget _buildInputBar(MeshCoreConnector connector) {
final maxBytes = maxContactMessageBytes(); final maxBytes = maxContactMessageBytes();
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
@@ -1168,8 +1218,14 @@ class _ChatScreenState extends State<ChatScreen> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildInfoRow(context.l10n.chat_type, contact.typeLabel), _buildInfoRow(
_buildInfoRow(context.l10n.chat_path, contact.pathLabel), context.l10n.chat_type,
contact.typeLabel(context.l10n),
),
_buildInfoRow(
context.l10n.chat_path,
contact.pathLabel(context.l10n),
),
_buildInfoRow( _buildInfoRow(
context.l10n.contact_lastSeen, context.l10n.contact_lastSeen,
_formatContactLastMessage(contact.lastMessageAt), _formatContactLastMessage(contact.lastMessageAt),
@@ -1320,11 +1376,15 @@ class _ChatScreenState extends State<ChatScreen> {
} }
void _openChat(BuildContext context, Contact contact) { void _openChat(BuildContext context, Contact contact) {
// Check if this is a repeater final connector = context.read<MeshCoreConnector>();
context.read<MeshCoreConnector>().markContactRead(contact.publicKeyHex); final unread = connector.getUnreadCountForContactKey(contact.publicKeyHex);
connector.markContactRead(contact.publicKeyHex);
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => ChatScreen(contact: contact)), MaterialPageRoute(
builder: (context) =>
ChatScreen(contact: contact, initialUnreadCount: unread),
),
); );
} }
@@ -1461,6 +1521,15 @@ class _ChatScreenState extends State<ChatScreen> {
_copyMessageText(message.text); _copyMessageText(message.text);
}, },
), ),
if (!message.isOutgoing)
ListTile(
leading: const Icon(Icons.mark_chat_unread_outlined),
title: Text(context.l10n.chat_markAsUnread),
onTap: () {
Navigator.pop(sheetContext);
_markAsUnread(message);
},
),
ListTile( ListTile(
leading: const Icon(Icons.delete_outline), leading: const Icon(Icons.delete_outline),
title: Text(context.l10n.common_delete), title: Text(context.l10n.common_delete),
@@ -1568,10 +1637,12 @@ class _MessageBubble extends StatelessWidget {
final VoidCallback? onLongPress; final VoidCallback? onLongPress;
final void Function(Message message, String emoji)? onRetryReaction; final void Function(Message message, String emoji)? onRetryReaction;
final double textScale; final double textScale;
final String sourceId;
const _MessageBubble({ const _MessageBubble({
required this.message, required this.message,
required this.senderName, required this.senderName,
required this.sourceId,
required this.isRoomServer, required this.isRoomServer,
required this.textScale, required this.textScale,
this.onTap, this.onTap,
@@ -1586,7 +1657,7 @@ class _MessageBubble extends StatelessWidget {
final isOutgoing = message.isOutgoing; final isOutgoing = message.isOutgoing;
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
final gifId = GifHelper.parseGif(message.text); final gifId = GifHelper.parseGif(message.text);
final poi = _parsePoiMessage(message.text); final poi = parseMarkerText(message.text);
final isFailed = message.status == MessageStatus.failed; final isFailed = message.status == MessageStatus.failed;
final bubbleColor = isFailed final bubbleColor = isFailed
? colorScheme.errorContainer ? colorScheme.errorContainer
@@ -1678,6 +1749,7 @@ class _MessageBubble extends StatelessWidget {
textColor, textColor,
metaColor, metaColor,
textScale, textScale,
senderName,
trailing: (!enableTracing && isOutgoing) trailing: (!enableTracing && isOutgoing)
? Padding( ? Padding(
padding: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.only(bottom: 2),
@@ -1859,25 +1931,13 @@ class _MessageBubble extends StatelessWidget {
); );
} }
_PoiInfo? _parsePoiMessage(String text) {
final trimmed = text.trim();
final match = RegExp(
r'^m:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|.*$',
).firstMatch(trimmed);
if (match == null) return null;
final lat = double.tryParse(match.group(1) ?? '');
final lon = double.tryParse(match.group(2) ?? '');
if (lat == null || lon == null) return null;
final label = match.group(3) ?? '';
return _PoiInfo(lat: lat, lon: lon, label: label);
}
Widget _buildPoiMessage( Widget _buildPoiMessage(
BuildContext context, BuildContext context,
_PoiInfo poi, MarkerPayload poi,
Color textColor, Color textColor,
Color metaColor, Color metaColor,
double textScale, { double textScale,
String senderName, {
Widget? trailing, Widget? trailing,
}) { }) {
return Row( return Row(
@@ -1887,13 +1947,23 @@ class _MessageBubble extends StatelessWidget {
icon: Icon(Icons.location_on_outlined, color: textColor), icon: Icon(Icons.location_on_outlined, color: textColor),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 32, minHeight: 32), constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
onPressed: () { onPressed: () async {
final selfName = context.read<MeshCoreConnector>().selfName ?? 'Me';
final fromName = message.isOutgoing ? selfName : senderName;
final key = buildSharedMarkerKey(
sourceId: sourceId,
label: poi.label,
fromName: fromName,
flags: poi.flags,
isChannel: false,
);
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => MapScreen( builder: (context) => MapScreen(
highlightPosition: LatLng(poi.lat, poi.lon), highlightPosition: poi.position,
highlightLabel: poi.label, highlightLabel: poi.label,
highlightMarkerKey: key,
), ),
), ),
); );
@@ -2074,11 +2144,3 @@ class _MessageBubble extends StatelessWidget {
return '$hour:$minute'; return '$hour:$minute';
} }
} }
class _PoiInfo {
final double lat;
final double lon;
final String label;
const _PoiInfo({required this.lat, required this.lon, required this.label});
}
+20 -6
View File
@@ -13,6 +13,7 @@ import '../connector/meshcore_connector.dart';
import '../l10n/l10n.dart'; import '../l10n/l10n.dart';
import '../connector/meshcore_protocol.dart'; import '../connector/meshcore_protocol.dart';
import '../models/contact.dart'; import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../models/contact_group.dart'; import '../models/contact_group.dart';
import '../services/ui_view_state_service.dart'; import '../services/ui_view_state_service.dart';
import '../utils/contact_search.dart'; import '../utils/contact_search.dart';
@@ -930,10 +931,17 @@ class _ContactsScreenState extends State<ContactsScreen>
} else if (contact.type == advTypeRoom) { } else if (contact.type == advTypeRoom) {
_showRoomLogin(context, contact, RoomLoginDestination.chat); _showRoomLogin(context, contact, RoomLoginDestination.chat);
} else { } else {
context.read<MeshCoreConnector>().markContactRead(contact.publicKeyHex); final connector = context.read<MeshCoreConnector>();
final unread = connector.getUnreadCountForContactKey(
contact.publicKeyHex,
);
connector.markContactRead(contact.publicKeyHex);
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => ChatScreen(contact: contact)), MaterialPageRoute(
builder: (context) =>
ChatScreen(contact: contact, initialUnreadCount: unread),
),
); );
} }
} }
@@ -988,7 +996,11 @@ class _ContactsScreenState extends State<ContactsScreen>
builder: (context) => RoomLoginDialog( builder: (context) => RoomLoginDialog(
room: room, room: room,
onLogin: (password, isAdmin) { onLogin: (password, isAdmin) {
context.read<MeshCoreConnector>().markContactRead(room.publicKeyHex); final connector = context.read<MeshCoreConnector>();
final unread = connector.getUnreadCountForContactKey(
room.publicKeyHex,
);
connector.markContactRead(room.publicKeyHex);
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
@@ -999,7 +1011,7 @@ class _ContactsScreenState extends State<ContactsScreen>
password: password, password: password,
isAdmin: isAdmin, isAdmin: isAdmin,
) )
: ChatScreen(contact: room), : ChatScreen(contact: room, initialUnreadCount: unread),
), ),
); );
}, },
@@ -1122,7 +1134,9 @@ class _ContactsScreenState extends State<ContactsScreen>
return CheckboxListTile( return CheckboxListTile(
value: isSelected, value: isSelected,
title: Text(contact.name), title: Text(contact.name),
subtitle: Text(contact.typeLabel), subtitle: Text(
contact.typeLabel(context.l10n),
),
onChanged: (value) { onChanged: (value) {
setDialogState(() { setDialogState(() {
if (value == true) { if (value == true) {
@@ -1464,7 +1478,7 @@ class _ContactTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
contact.pathLabel, contact.pathLabel(context.l10n),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
+497 -28
View File
@@ -1,6 +1,7 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
@@ -56,12 +57,16 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
static const double _maxAntennaFeet = 400.0; static const double _maxAntennaFeet = 400.0;
static const double _maxAntennaMeters = _maxAntennaFeet / _metersToFeet; static const double _maxAntennaMeters = _maxAntennaFeet / _metersToFeet;
static const double _labelZoomThreshold = 8.5; static const double _labelZoomThreshold = 8.5;
static const double _mapMinZoom = 2.0;
static const double _mapMaxZoom = 18.0;
final LineOfSightService _lineOfSightService = LineOfSightService(); final LineOfSightService _lineOfSightService = LineOfSightService();
final MapController _mapController = MapController();
bool _loading = false; bool _loading = false;
String? _error; String? _error;
LineOfSightPathResult? _result; LineOfSightPathResult? _result;
LineOfSightObstruction? _selectedObstruction;
LineOfSightEndpoint? _start; LineOfSightEndpoint? _start;
LineOfSightEndpoint? _end; LineOfSightEndpoint? _end;
final List<LineOfSightEndpoint> _customEndpoints = []; final List<LineOfSightEndpoint> _customEndpoints = [];
@@ -98,10 +103,85 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
@override @override
void dispose() { void dispose() {
_mapController.dispose();
_lineOfSightService.dispose(); _lineOfSightService.dispose();
super.dispose(); super.dispose();
} }
bool _isDesktopPlatform(TargetPlatform platform) {
return platform == TargetPlatform.linux ||
platform == TargetPlatform.windows ||
platform == TargetPlatform.macOS;
}
void _zoomMapBy(double delta) {
final camera = _mapController.camera;
final nextZoom = (camera.zoom + delta)
.clamp(_mapMinZoom, _mapMaxZoom)
.toDouble();
_mapController.move(camera.center, nextZoom);
}
void _resetMapView({
required LatLng initialCenter,
required double initialZoom,
required LatLngBounds? bounds,
}) {
if (bounds != null) {
_mapController.fitCamera(
CameraFit.bounds(
bounds: bounds,
padding: const EdgeInsets.all(64),
maxZoom: 16,
),
);
return;
}
_mapController.move(initialCenter, initialZoom);
}
Widget _buildDesktopMapControls({
required LatLng initialCenter,
required double initialZoom,
required LatLngBounds? bounds,
}) {
final screenHeight = MediaQuery.of(context).size.height;
final topOffset = _showHud
? math.min(screenHeight * 0.52 + 24, screenHeight - 220)
: 12.0;
return Positioned(
top: topOffset,
left: 12,
child: Card(
elevation: 4,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Zoom in',
onPressed: () => _zoomMapBy(1),
),
IconButton(
icon: const Icon(Icons.remove),
tooltip: 'Zoom out',
onPressed: () => _zoomMapBy(-1),
),
IconButton(
icon: const Icon(Icons.my_location),
tooltip: 'Center map',
onPressed: () => _resetMapView(
initialCenter: initialCenter,
initialZoom: initialZoom,
bounds: bounds,
),
),
],
),
),
);
}
Future<void> _runLos() async { Future<void> _runLos() async {
final start = _start; final start = _start;
final end = _end; final end = _end;
@@ -111,6 +191,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
if (start == null || end == null) { if (start == null || end == null) {
setState(() { setState(() {
_result = null; _result = null;
_selectedObstruction = null;
_error = _errorSelectStartEnd; _error = _errorSelectStartEnd;
}); });
return; return;
@@ -142,6 +223,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
} }
setState(() { setState(() {
_result = result; _result = result;
_selectedObstruction = _defaultObstructionFor(result);
}); });
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
@@ -156,6 +238,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
} }
setState(() { setState(() {
_result = null; _result = null;
_selectedObstruction = null;
_error = context.l10n.losRunFailed(e.toString()); _error = context.l10n.losRunFailed(e.toString());
}); });
} finally { } finally {
@@ -184,6 +267,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
void _selectFromMap(LineOfSightEndpoint endpoint) { void _selectFromMap(LineOfSightEndpoint endpoint) {
setState(() { setState(() {
_result = null; _result = null;
_selectedObstruction = null;
_error = null; _error = null;
if (_start == null || (_start != null && _end != null)) { if (_start == null || (_start != null && _end != null)) {
_start = endpoint; _start = endpoint;
@@ -241,6 +325,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
_start = null; _start = null;
_end = null; _end = null;
_result = null; _result = null;
_selectedObstruction = null;
_error = _errorSelectStartEnd; _error = _errorSelectStartEnd;
}); });
} }
@@ -251,6 +336,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
if (identical(_start, endpoint)) _start = null; if (identical(_start, endpoint)) _start = null;
if (identical(_end, endpoint)) _end = null; if (identical(_end, endpoint)) _end = null;
_result = null; _result = null;
_selectedObstruction = null;
}); });
} }
@@ -318,6 +404,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
? LatLngBounds.fromPoints(mapPoints) ? LatLngBounds.fromPoints(mapPoints)
: null; : null;
final initialZoom = mapPoints.length > 1 ? 13.0 : 2.0; final initialZoom = mapPoints.length > 1 ? 13.0 : 2.0;
final isDesktop = _isDesktopPlatform(defaultTargetPlatform);
if (!_didReceivePositionUpdate) { if (!_didReceivePositionUpdate) {
_showMarkerLabels = initialZoom >= _labelZoomThreshold; _showMarkerLabels = initialZoom >= _labelZoomThreshold;
} }
@@ -343,6 +430,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
body: Stack( body: Stack(
children: [ children: [
FlutterMap( FlutterMap(
mapController: _mapController,
options: MapOptions( options: MapOptions(
initialCenter: initialCenter, initialCenter: initialCenter,
initialZoom: initialZoom, initialZoom: initialZoom,
@@ -355,7 +443,19 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
), ),
interactionOptions: InteractionOptions( interactionOptions: InteractionOptions(
flags: ~InteractiveFlag.rotate, flags: ~InteractiveFlag.rotate,
scrollWheelVelocity: isDesktop ? 0.012 : 0.005,
cursorKeyboardRotationOptions:
CursorKeyboardRotationOptions.disabled(),
keyboardOptions: isDesktop
? const KeyboardOptions(
enableArrowKeysPanning: true,
enableWASDPanning: true,
enableRFZooming: true,
)
: const KeyboardOptions.disabled(),
), ),
minZoom: _mapMinZoom,
maxZoom: _mapMaxZoom,
onLongPress: (_, point) => _addCustomPoint(point), onLongPress: (_, point) => _addCustomPoint(point),
onPositionChanged: (camera, hasGesture) { onPositionChanged: (camera, hasGesture) {
final shouldShow = camera.zoom >= _labelZoomThreshold; final shouldShow = camera.zoom >= _labelZoomThreshold;
@@ -377,9 +477,17 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
), ),
if (_result != null && _result!.segments.isNotEmpty) if (_result != null && _result!.segments.isNotEmpty)
PolylineLayer(polylines: _buildSegmentPolylines(_result!)), PolylineLayer(polylines: _buildSegmentPolylines(_result!)),
MarkerLayer(markers: _buildMarkers(endpoints)), MarkerLayer(
markers: _buildMarkers(endpoints, _primaryObstructions()),
),
], ],
), ),
if (isDesktop)
_buildDesktopMapControls(
initialCenter: initialCenter,
initialZoom: initialZoom,
bounds: bounds,
),
if (_showHud) if (_showHud)
Positioned( Positioned(
left: 12, left: 12,
@@ -445,6 +553,8 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
); );
final displayFrequencyMHz = segment?.frequencyMHz ?? reportedFrequencyMHz; final displayFrequencyMHz = segment?.frequencyMHz ?? reportedFrequencyMHz;
final kFactorUsed = segment?.usedKFactor; final kFactorUsed = segment?.usedKFactor;
final obstructions =
segment?.obstructions ?? const <LineOfSightObstruction>[];
final endpoints = _visibleEndpoints(); final endpoints = _visibleEndpoints();
final distanceUnit = isImperial ? 'mi' : 'km'; final distanceUnit = isImperial ? 'mi' : 'km';
final heightUnit = isImperial ? 'ft' : 'm'; final heightUnit = isImperial ? 'ft' : 'm';
@@ -463,31 +573,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (segment != null) if (segment != null)
SizedBox( _buildProfileView(segment, distanceUnit, heightUnit, isImperial)
height: 160,
width: double.infinity,
child: CustomPaint(
painter: _LosProfilePainter(
samples: segment.samples,
distanceUnit: distanceUnit,
heightUnit: heightUnit,
badgeTextStyle:
Theme.of(context).textTheme.labelSmall?.copyWith(
color: Colors.white70,
fontSize: 10,
fontWeight: FontWeight.w600,
) ??
const TextStyle(
color: Colors.white70,
fontSize: 10,
fontWeight: FontWeight.w600,
),
terrainLabel: context.l10n.losLegendTerrain,
losBeamLabel: context.l10n.losLegendLosBeam,
radioHorizonLabel: context.l10n.losLegendRadioHorizon,
),
),
)
else else
SizedBox( SizedBox(
height: 44, height: 44,
@@ -519,6 +605,96 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
if (obstructions.isNotEmpty) ...[
const SizedBox(height: 8),
Text(
context.l10n.losBlockedSpotsTitle,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 4),
Text(
context.l10n.losBlockedSpotsHint,
style: TextStyle(fontSize: 11, color: Colors.grey[700]),
),
const SizedBox(height: 6),
Wrap(
spacing: 6,
runSpacing: 6,
children: [
for (final obstruction in obstructions)
ChoiceChip(
label: Text(
_obstructionChipLabel(obstruction, isImperial),
style: const TextStyle(fontSize: 11),
),
selected:
_selectedObstruction?.sampleIndex ==
obstruction.sampleIndex,
onSelected: (_) => _selectObstruction(obstruction),
),
],
),
if (_selectedObstruction != null) ...[
const SizedBox(height: 8),
DecoratedBox(
decoration: BoxDecoration(
color: Colors.orange.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.deepOrangeAccent.withValues(alpha: 0.45),
),
),
child: Padding(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.l10n.losSelectedObstructionTitle,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 4),
Text(
context.l10n.losSelectedObstructionDetails(
_formatHeightValue(
_selectedObstruction!.obstructionMeters,
isImperial,
),
heightUnit,
_formatDistanceValue(
_selectedObstruction!.distanceMeters,
isImperial,
),
distanceUnit,
_formatDistanceValue(
segment!.totalDistanceMeters -
_selectedObstruction!.distanceMeters,
isImperial,
),
),
style: const TextStyle(fontSize: 11),
),
const SizedBox(height: 4),
Text(
'${_selectedObstruction!.point.latitude.toStringAsFixed(5)}, '
'${_selectedObstruction!.point.longitude.toStringAsFixed(5)}',
style: TextStyle(
fontSize: 11,
color: Colors.grey[700],
),
),
],
),
),
),
],
],
const SizedBox(height: 4), const SizedBox(height: 4),
if (displayFrequencyMHz != null) if (displayFrequencyMHz != null)
Padding( Padding(
@@ -605,6 +781,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
_showDisplayNodes = value; _showDisplayNodes = value;
_sanitizeSelection(); _sanitizeSelection();
_result = null; _result = null;
_selectedObstruction = null;
}); });
}, },
), ),
@@ -655,6 +832,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
setState(() { setState(() {
_start = value; _start = value;
_result = null; _result = null;
_selectedObstruction = null;
}); });
if (_start != null && _end != null) { if (_start != null && _end != null) {
_runLos(); _runLos();
@@ -670,6 +848,7 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
setState(() { setState(() {
_end = value; _end = value;
_result = null; _result = null;
_selectedObstruction = null;
}); });
if (_start != null && _end != null) { if (_start != null && _end != null) {
_runLos(); _runLos();
@@ -769,6 +948,179 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
return _result!.segments.first.result; return _result!.segments.first.result;
} }
List<LineOfSightObstruction> _primaryObstructions() {
return _primarySegmentResult()?.obstructions ?? const [];
}
LineOfSightObstruction? _defaultObstructionFor(
LineOfSightPathResult? result,
) {
if (result == null || result.segments.isEmpty) return null;
final obstructions = result.segments.first.result.obstructions;
if (obstructions.isEmpty) return null;
return obstructions.reduce(
(current, next) =>
next.obstructionMeters > current.obstructionMeters ? next : current,
);
}
void _selectObstruction(LineOfSightObstruction obstruction) {
setState(() {
_selectedObstruction = obstruction;
});
}
String _formatDistanceValue(double meters, bool isImperial) {
final value = isImperial ? (meters / 1000.0) * _kmToMiles : meters / 1000.0;
return value.toStringAsFixed(2);
}
String _formatHeightValue(double meters, bool isImperial) {
final value = isImperial ? meters * _metersToFeet : meters;
return value.toStringAsFixed(1);
}
String _obstructionChipLabel(
LineOfSightObstruction obstruction,
bool isImperial,
) {
final distanceUnit = isImperial ? 'mi' : 'km';
final heightUnit = isImperial ? 'ft' : 'm';
return context.l10n.losBlockedSpotChip(
_formatDistanceValue(obstruction.distanceMeters, isImperial),
distanceUnit,
_formatHeightValue(obstruction.obstructionMeters, isImperial),
heightUnit,
);
}
Widget _buildProfileView(
LineOfSightResult segment,
String distanceUnit,
String heightUnit,
bool isImperial,
) {
if (segment.samples.length < 2) {
return SizedBox(
height: 160,
width: double.infinity,
child: CustomPaint(
painter: _LosProfilePainter(
samples: segment.samples,
distanceUnit: distanceUnit,
heightUnit: heightUnit,
badgeTextStyle:
Theme.of(context).textTheme.labelSmall?.copyWith(
color: Colors.white70,
fontSize: 10,
fontWeight: FontWeight.w600,
) ??
const TextStyle(
color: Colors.white70,
fontSize: 10,
fontWeight: FontWeight.w600,
),
terrainLabel: context.l10n.losLegendTerrain,
losBeamLabel: context.l10n.losLegendLosBeam,
radioHorizonLabel: context.l10n.losLegendRadioHorizon,
selectedSampleIndex: _selectedObstruction?.sampleIndex,
),
),
);
}
return SizedBox(
height: 160,
width: double.infinity,
child: LayoutBuilder(
builder: (context, constraints) {
final size = Size(constraints.maxWidth, 160);
final geometry = _LosProfileGeometry(
samples: segment.samples,
size: size,
);
return Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: CustomPaint(
painter: _LosProfilePainter(
samples: segment.samples,
distanceUnit: distanceUnit,
heightUnit: heightUnit,
badgeTextStyle:
Theme.of(context).textTheme.labelSmall?.copyWith(
color: Colors.white70,
fontSize: 10,
fontWeight: FontWeight.w600,
) ??
const TextStyle(
color: Colors.white70,
fontSize: 10,
fontWeight: FontWeight.w600,
),
terrainLabel: context.l10n.losLegendTerrain,
losBeamLabel: context.l10n.losLegendLosBeam,
radioHorizonLabel: context.l10n.losLegendRadioHorizon,
selectedSampleIndex: _selectedObstruction?.sampleIndex,
),
),
),
for (final obstruction in segment.obstructions)
Builder(
builder: (context) {
final sample = segment.samples[obstruction.sampleIndex];
final position = geometry.mapPoint(
sample.distanceMeters,
sample.terrainMeters,
);
final isSelected =
_selectedObstruction?.sampleIndex ==
obstruction.sampleIndex;
final markerSize = isSelected ? 18.0 : 14.0;
final left = (position.dx - markerSize / 2)
.clamp(0.0, math.max(0.0, size.width - markerSize))
.toDouble();
final top = (position.dy - markerSize / 2)
.clamp(0.0, math.max(0.0, size.height - markerSize))
.toDouble();
return Positioned(
left: left,
top: top,
child: Tooltip(
message: _obstructionChipLabel(obstruction, isImperial),
child: GestureDetector(
onTap: () => _selectObstruction(obstruction),
child: Container(
width: markerSize,
height: markerSize,
decoration: BoxDecoration(
color: isSelected
? Colors.amberAccent
: Colors.deepOrangeAccent,
shape: BoxShape.circle,
border: Border.all(
color: isSelected
? Colors.white
: Colors.black87,
width: isSelected ? 2 : 1.5,
),
boxShadow: const [
BoxShadow(color: Colors.black45, blurRadius: 4),
],
),
),
),
),
);
},
),
],
);
},
),
);
}
String _profileStats(LineOfSightResult result, bool isImperial) { String _profileStats(LineOfSightResult result, bool isImperial) {
final distance = isImperial final distance = isImperial
? (result.totalDistanceMeters / 1000.0) * _kmToMiles ? (result.totalDistanceMeters / 1000.0) * _kmToMiles
@@ -820,8 +1172,51 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
return polylines; return polylines;
} }
List<Marker> _buildMarkers(List<LineOfSightEndpoint> endpoints) { List<Marker> _buildMarkers(
List<LineOfSightEndpoint> endpoints,
List<LineOfSightObstruction> obstructions,
) {
return [ return [
for (final obstruction in obstructions)
Marker(
point: obstruction.point,
width: 52,
height: 52,
child: GestureDetector(
onTap: () => _selectObstruction(obstruction),
child: Center(
child: Container(
width:
_selectedObstruction?.sampleIndex == obstruction.sampleIndex
? 36
: 24,
height:
_selectedObstruction?.sampleIndex == obstruction.sampleIndex
? 36
: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.transparent,
border: Border.all(
color:
_selectedObstruction?.sampleIndex ==
obstruction.sampleIndex
? Colors.amberAccent
: Colors.deepOrangeAccent,
width:
_selectedObstruction?.sampleIndex ==
obstruction.sampleIndex
? 4
: 3,
),
boxShadow: const [
BoxShadow(color: Colors.black26, blurRadius: 6),
],
),
),
),
),
),
for (final endpoint in endpoints) for (final endpoint in endpoints)
Marker( Marker(
point: endpoint.point, point: endpoint.point,
@@ -1010,6 +1405,51 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
} }
} }
class _LosProfileGeometry {
static const horizontalPadding = 12.0;
static const verticalPadding = 12.0;
final List<LineOfSightSample> samples;
final Size size;
late final double minY = samples
.map(
(s) => math.min(
math.min(s.terrainMeters, s.lineHeightMeters),
s.refractedHeightMeters,
),
)
.reduce(math.min);
late final double maxY = samples
.map(
(s) => math.max(
math.max(s.terrainMeters, s.lineHeightMeters),
s.refractedHeightMeters,
),
)
.reduce(math.max);
late final double ySpan = math.max(1.0, maxY - minY);
late final double maxDist = math.max(1.0, samples.last.distanceMeters);
late final double chartWidth = math.max(
1.0,
size.width - horizontalPadding * 2,
);
late final double chartHeight = math.max(
1.0,
size.height - verticalPadding * 2,
);
_LosProfileGeometry({required this.samples, required this.size});
Offset mapPoint(double distanceMeters, double elevationMeters) {
final px = horizontalPadding + (distanceMeters / maxDist) * chartWidth;
final py =
size.height -
verticalPadding -
((elevationMeters - minY) / ySpan) * chartHeight;
return Offset(px, py);
}
}
class _LosProfilePainter extends CustomPainter { class _LosProfilePainter extends CustomPainter {
final List<LineOfSightSample> samples; final List<LineOfSightSample> samples;
final String distanceUnit; final String distanceUnit;
@@ -1018,6 +1458,7 @@ class _LosProfilePainter extends CustomPainter {
final String terrainLabel; final String terrainLabel;
final String losBeamLabel; final String losBeamLabel;
final String radioHorizonLabel; final String radioHorizonLabel;
final int? selectedSampleIndex;
const _LosProfilePainter({ const _LosProfilePainter({
required this.samples, required this.samples,
@@ -1027,6 +1468,7 @@ class _LosProfilePainter extends CustomPainter {
required this.terrainLabel, required this.terrainLabel,
required this.losBeamLabel, required this.losBeamLabel,
required this.radioHorizonLabel, required this.radioHorizonLabel,
this.selectedSampleIndex,
}); });
@override @override
@@ -1212,6 +1654,32 @@ class _LosProfilePainter extends CustomPainter {
..color = horizonFillColor ..color = horizonFillColor
..style = PaintingStyle.fill, ..style = PaintingStyle.fill,
); );
if (selectedSampleIndex != null &&
selectedSampleIndex! >= 0 &&
selectedSampleIndex! < samples.length) {
final selectedSample = samples[selectedSampleIndex!];
final selectedPoint = mapPoint(
selectedSample.distanceMeters,
selectedSample.terrainMeters,
);
canvas.drawLine(
Offset(selectedPoint.dx, verticalPadding),
Offset(selectedPoint.dx, size.height - verticalPadding),
Paint()
..color = Colors.amberAccent.withValues(alpha: 0.7)
..strokeWidth = 1.5,
);
canvas.drawCircle(selectedPoint, 7, Paint()..color = Colors.amberAccent);
canvas.drawCircle(
selectedPoint,
8.5,
Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 1.5,
);
}
} }
@override @override
@@ -1222,7 +1690,8 @@ class _LosProfilePainter extends CustomPainter {
oldDelegate.badgeTextStyle != badgeTextStyle || oldDelegate.badgeTextStyle != badgeTextStyle ||
oldDelegate.terrainLabel != terrainLabel || oldDelegate.terrainLabel != terrainLabel ||
oldDelegate.losBeamLabel != losBeamLabel || oldDelegate.losBeamLabel != losBeamLabel ||
oldDelegate.radioHorizonLabel != radioHorizonLabel; oldDelegate.radioHorizonLabel != radioHorizonLabel ||
oldDelegate.selectedSampleIndex != selectedSampleIndex;
} }
void _drawUnitBadge(Canvas canvas, Size size) { void _drawUnitBadge(Canvas canvas, Size size) {
+78 -4
View File
@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
@@ -18,6 +19,9 @@ class MapCacheScreen extends StatefulWidget {
} }
class _MapCacheScreenState extends State<MapCacheScreen> { class _MapCacheScreenState extends State<MapCacheScreen> {
static const double _mapMinZoom = 2.0;
static const double _mapMaxZoom = 18.0;
final MapController _mapController = MapController(); final MapController _mapController = MapController();
LatLngBounds? _selectedBounds; LatLngBounds? _selectedBounds;
@@ -43,6 +47,61 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
super.dispose(); super.dispose();
} }
bool _isDesktopPlatform(TargetPlatform platform) {
return platform == TargetPlatform.linux ||
platform == TargetPlatform.windows ||
platform == TargetPlatform.macOS;
}
void _zoomMapBy(double delta) {
final camera = _mapController.camera;
final nextZoom = (camera.zoom + delta)
.clamp(_mapMinZoom, _mapMaxZoom)
.toDouble();
_mapController.move(camera.center, nextZoom);
}
void _resetMapView() {
final bounds = _selectedBounds;
if (bounds != null) {
_mapController.fitCamera(
CameraFit.bounds(bounds: bounds, padding: const EdgeInsets.all(48)),
);
return;
}
_mapController.move(const LatLng(0, 0), 2.0);
}
Widget _buildDesktopMapControls() {
return Positioned(
top: 12,
left: 12,
child: Card(
elevation: 4,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Zoom in',
onPressed: () => _zoomMapBy(1),
),
IconButton(
icon: const Icon(Icons.remove),
tooltip: 'Zoom out',
onPressed: () => _zoomMapBy(-1),
),
IconButton(
icon: const Icon(Icons.my_location),
tooltip: 'Center map',
onPressed: _resetMapView,
),
],
),
),
);
}
void _loadSettings() { void _loadSettings() {
final settings = context.read<AppSettingsService>().settings; final settings = context.read<AppSettingsService>().settings;
final bounds = MapTileCacheService.boundsFromJson(settings.mapCacheBounds); final bounds = MapTileCacheService.boundsFromJson(settings.mapCacheBounds);
@@ -222,6 +281,7 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
final tileCache = context.read<MapTileCacheService>(); final tileCache = context.read<MapTileCacheService>();
final selectedBounds = _selectedBounds; final selectedBounds = _selectedBounds;
final l10n = context.l10n; final l10n = context.l10n;
final isDesktop = _isDesktopPlatform(defaultTargetPlatform);
final progressValue = _estimatedTiles == 0 final progressValue = _estimatedTiles == 0
? 0.0 ? 0.0
: (_completedTiles / _estimatedTiles).clamp(0.0, 1.0).toDouble(); : (_completedTiles / _estimatedTiles).clamp(0.0, 1.0).toDouble();
@@ -238,11 +298,24 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
children: [ children: [
FlutterMap( FlutterMap(
mapController: _mapController, mapController: _mapController,
options: const MapOptions( options: MapOptions(
initialCenter: LatLng(0, 0), initialCenter: const LatLng(0, 0),
initialZoom: 2.0, initialZoom: 2.0,
minZoom: 2.0, minZoom: _mapMinZoom,
maxZoom: 18.0, maxZoom: _mapMaxZoom,
interactionOptions: InteractionOptions(
flags: ~InteractiveFlag.rotate,
scrollWheelVelocity: isDesktop ? 0.012 : 0.005,
cursorKeyboardRotationOptions:
CursorKeyboardRotationOptions.disabled(),
keyboardOptions: isDesktop
? const KeyboardOptions(
enableArrowKeysPanning: true,
enableWASDPanning: true,
enableRFZooming: true,
)
: const KeyboardOptions.disabled(),
),
), ),
children: [ children: [
TileLayer( TileLayer(
@@ -265,6 +338,7 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
), ),
], ],
), ),
if (isDesktop) _buildDesktopMapControls(),
Positioned( Positioned(
top: 12, top: 12,
right: 12, right: 12,
+259 -62
View File
@@ -1,6 +1,5 @@
import 'dart:collection'; import 'dart:collection';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -16,6 +15,7 @@ import '../connector/meshcore_protocol.dart';
import '../models/app_settings.dart'; import '../models/app_settings.dart';
import '../models/channel.dart'; import '../models/channel.dart';
import '../models/contact.dart'; import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../services/app_settings_service.dart'; import '../services/app_settings_service.dart';
import '../services/path_history_service.dart'; import '../services/path_history_service.dart';
import '../services/map_marker_service.dart'; import '../services/map_marker_service.dart';
@@ -37,6 +37,7 @@ import 'line_of_sight_map_screen.dart';
class MapScreen extends StatefulWidget { class MapScreen extends StatefulWidget {
final LatLng? highlightPosition; final LatLng? highlightPosition;
final String? highlightLabel; final String? highlightLabel;
final String? highlightMarkerKey;
final double highlightZoom; final double highlightZoom;
final bool hideBackButton; final bool hideBackButton;
@@ -44,6 +45,7 @@ class MapScreen extends StatefulWidget {
super.key, super.key,
this.highlightPosition, this.highlightPosition,
this.highlightLabel, this.highlightLabel,
this.highlightMarkerKey,
this.highlightZoom = 15.0, this.highlightZoom = 15.0,
this.hideBackButton = false, this.hideBackButton = false,
}); });
@@ -55,6 +57,8 @@ class MapScreen extends StatefulWidget {
class _MapScreenState extends State<MapScreen> { class _MapScreenState extends State<MapScreen> {
// Zoom level at which node labels start to appear // Zoom level at which node labels start to appear
static const double _labelZoomThreshold = 14.0; static const double _labelZoomThreshold = 14.0;
static const double _mapMinZoom = 2.0;
static const double _mapMaxZoom = 18.0;
final MapController _mapController = MapController(); final MapController _mapController = MapController();
final MapMarkerService _markerService = MapMarkerService(); final MapMarkerService _markerService = MapMarkerService();
@@ -94,6 +98,19 @@ class _MapScreenState extends State<MapScreen> {
_removedMarkerIds = ids; _removedMarkerIds = ids;
_removedMarkersLoaded = true; _removedMarkersLoaded = true;
}); });
// If this screen was opened to highlight a marker, and that marker
// was previously removed, re-enable it now that we've loaded the saved
// removed IDs.
if (widget.highlightMarkerKey != null &&
_removedMarkerIds.contains(widget.highlightMarkerKey)) {
final updated = Set<String>.from(_removedMarkerIds);
updated.remove(widget.highlightMarkerKey);
if (!mounted) return;
setState(() {
_removedMarkerIds = updated;
});
await _markerService.saveRemovedIds(updated);
}
} }
bool _checkLocationPlausibility(double lat, double lon) { bool _checkLocationPlausibility(double lat, double lon) {
@@ -134,11 +151,62 @@ class _MapScreenState extends State<MapScreen> {
return zoom.clamp(4.0, 15.0); return zoom.clamp(4.0, 15.0);
} }
bool _isDesktopPlatform(TargetPlatform platform) {
return platform == TargetPlatform.linux ||
platform == TargetPlatform.windows ||
platform == TargetPlatform.macOS;
}
void _zoomMapBy(double delta) {
final camera = _mapController.camera;
final nextZoom = (camera.zoom + delta)
.clamp(_mapMinZoom, _mapMaxZoom)
.toDouble();
_mapController.move(camera.center, nextZoom);
}
Widget _buildDesktopMapControls(
BuildContext context, {
required LatLng center,
required double zoom,
required bool hasPathSelector,
}) {
return Positioned(
left: 16,
top: hasPathSelector ? null : 16,
bottom: hasPathSelector ? 16 : null,
child: Card(
elevation: 4,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Zoom in',
onPressed: () => _zoomMapBy(1),
),
IconButton(
icon: const Icon(Icons.remove),
tooltip: 'Zoom out',
onPressed: () => _zoomMapBy(-1),
),
IconButton(
icon: const Icon(Icons.my_location),
tooltip: 'Center map',
onPressed: () => _mapController.move(center, zoom),
),
],
),
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer3<MeshCoreConnector, AppSettingsService, PathHistoryService>( return Consumer3<MeshCoreConnector, AppSettingsService, PathHistoryService>(
builder: (context, connector, settingsService, pathHistory, child) { builder: (context, connector, settingsService, pathHistory, child) {
final tileCache = context.read<MapTileCacheService>(); final tileCache = context.read<MapTileCacheService>();
final isDesktop = _isDesktopPlatform(defaultTargetPlatform);
final settings = settingsService.settings; final settings = settingsService.settings;
final allContacts = connector.allContacts; final allContacts = connector.allContacts;
@@ -229,6 +297,24 @@ class _MapScreenState extends State<MapScreen> {
: <Polyline>[], : <Polyline>[],
); );
// Collect polylines for shared markers' history with dashed lines
final List<Polyline> sharedMarkerPolylines = [];
for (final marker in sharedMarkers) {
if (marker.history.isNotEmpty) {
final points = List<LatLng>.from(marker.history);
points.add(marker.position);
sharedMarkerPolylines.add(
Polyline(
points: points,
color: marker.isChannel
? (marker.isPublicChannel ? Colors.orange : Colors.purple)
: Colors.blue,
strokeWidth: 3,
),
);
}
}
// Calculate center and zoom of all nodes, or default to (0, 0) // Calculate center and zoom of all nodes, or default to (0, 0)
LatLng center = const LatLng(0, 0); LatLng center = const LatLng(0, 0);
double initialZoom = 10.0; double initialZoom = 10.0;
@@ -417,10 +503,20 @@ class _MapScreenState extends State<MapScreen> {
options: MapOptions( options: MapOptions(
initialCenter: center, initialCenter: center,
initialZoom: initialZoom, initialZoom: initialZoom,
minZoom: 2.0, minZoom: _mapMinZoom,
maxZoom: 18.0, maxZoom: _mapMaxZoom,
interactionOptions: InteractionOptions( interactionOptions: InteractionOptions(
flags: ~InteractiveFlag.rotate, flags: ~InteractiveFlag.rotate,
scrollWheelVelocity: isDesktop ? 0.012 : 0.005,
cursorKeyboardRotationOptions:
CursorKeyboardRotationOptions.disabled(),
keyboardOptions: isDesktop
? const KeyboardOptions(
enableArrowKeysPanning: true,
enableWASDPanning: true,
enableRFZooming: true,
)
: const KeyboardOptions.disabled(),
), ),
onTap: (_, latLng) { onTap: (_, latLng) {
if (_isSelectingPoi) { if (_isSelectingPoi) {
@@ -475,6 +571,8 @@ class _MapScreenState extends State<MapScreen> {
), ),
if (_polylines.isNotEmpty && _isBuildingPathTrace) if (_polylines.isNotEmpty && _isBuildingPathTrace)
PolylineLayer(polylines: _polylines), PolylineLayer(polylines: _polylines),
if (sharedMarkerPolylines.isNotEmpty)
PolylineLayer(polylines: sharedMarkerPolylines),
MarkerLayer( MarkerLayer(
markers: [ markers: [
if (highlightPosition != null) if (highlightPosition != null)
@@ -562,6 +660,13 @@ class _MapScreenState extends State<MapScreen> {
sharedMarkers.length, sharedMarkers.length,
guessedLocations.length, guessedLocations.length,
), ),
if (isDesktop)
_buildDesktopMapControls(
context,
center: center,
zoom: initialZoom,
hasPathSelector: _isBuildingPathTrace,
),
if (_isBuildingPathTrace) _buildPathTraceOverlay(), if (_isBuildingPathTrace) _buildPathTraceOverlay(),
], ],
), ),
@@ -1239,28 +1344,39 @@ class _MapScreenState extends State<MapScreen> {
} }
List<_SharedMarker> _collectSharedMarkers(MeshCoreConnector connector) { List<_SharedMarker> _collectSharedMarkers(MeshCoreConnector connector) {
final markers = <_SharedMarker>[]; // Build a _SharedMarker per message (history empty), grouped by dedupe key.
// Afterwards pick the latest per key and fill its history from older ones.
final updatesByKey = <String, List<_SharedMarker>>{};
final selfName = connector.selfName ?? 'Me'; final selfName = connector.selfName ?? 'Me';
void addUpdate(_SharedMarker update) {
(updatesByKey[update.id] ??= <_SharedMarker>[]).add(update);
}
for (final contact in connector.contacts) { for (final contact in connector.contacts) {
final messages = connector.getMessages(contact); final messages = connector.getMessages(contact);
for (final message in messages) { for (final message in messages) {
final payload = _parseMarkerText(message.text); final payload = parseMarkerText(message.text);
if (payload == null) continue; if (payload == null) continue;
final fromName = message.isOutgoing ? selfName : contact.name; final fromName = message.isOutgoing ? selfName : contact.name;
final id = _buildMarkerId( final key = buildSharedMarkerKey(
sourceId: contact.publicKeyHex, sourceId: contact.publicKeyHex,
timestamp: message.timestamp, label: payload.label,
text: message.text, fromName: fromName,
flags: payload.flags,
isChannel: false,
); );
markers.add( addUpdate(
_SharedMarker( _SharedMarker(
id: id, id: key,
position: payload.position, position: payload.position,
label: payload.label, label: payload.label.isEmpty
? context.l10n.map_sharedPin
: payload.label,
flags: payload.flags, flags: payload.flags,
fromName: fromName, fromName: fromName,
sourceLabel: contact.name, sourceLabel: contact.name,
timestamp: message.timestamp,
isChannel: false, isChannel: false,
isPublicChannel: false, isPublicChannel: false,
), ),
@@ -1272,23 +1388,28 @@ class _MapScreenState extends State<MapScreen> {
final isPublic = _isPublicChannel(channel); final isPublic = _isPublicChannel(channel);
final messages = connector.getChannelMessages(channel); final messages = connector.getChannelMessages(channel);
for (final message in messages) { for (final message in messages) {
final payload = _parseMarkerText(message.text); final payload = parseMarkerText(message.text);
if (payload == null) continue; if (payload == null) continue;
final id = _buildMarkerId( final key = buildSharedMarkerKey(
sourceId: 'channel:${channel.index}', sourceId: 'channel:${channel.index}',
timestamp: message.timestamp, label: payload.label,
text: message.text, fromName: message.senderName,
flags: payload.flags,
isChannel: true,
); );
markers.add( addUpdate(
_SharedMarker( _SharedMarker(
id: id, id: key,
position: payload.position, position: payload.position,
label: payload.label, label: payload.label.isEmpty
? context.l10n.map_sharedPin
: payload.label,
flags: payload.flags, flags: payload.flags,
fromName: message.senderName, fromName: message.senderName,
sourceLabel: channel.name.isEmpty sourceLabel: channel.name.isEmpty
? 'Channel ${channel.index}' ? 'Channel ${channel.index}'
: channel.name, : channel.name,
timestamp: message.timestamp,
isChannel: true, isChannel: true,
isPublicChannel: isPublic, isPublicChannel: isPublic,
), ),
@@ -1296,38 +1417,27 @@ class _MapScreenState extends State<MapScreen> {
} }
} }
final markers = <_SharedMarker>[];
updatesByKey.forEach((_, updates) {
updates.sort((a, b) => a.timestamp.compareTo(b.timestamp));
final latest = updates.last;
// History: older positions, drop consecutive duplicates at same position.
final history = <LatLng>[];
for (var i = 0; i < updates.length - 1; i++) {
final p = updates[i].position;
if (history.isEmpty ||
history.last.latitude != p.latitude ||
history.last.longitude != p.longitude) {
history.add(p);
}
}
markers.add(latest.copyWithHistory(history));
});
markers.sort((a, b) => b.timestamp.compareTo(a.timestamp));
return markers; return markers;
} }
_MarkerPayload? _parseMarkerText(String text) {
final trimmed = text.trim();
if (!trimmed.startsWith('m:')) return null;
final parts = trimmed.substring(2).split('|');
if (parts.isEmpty) return null;
final coords = parts[0].split(',');
if (coords.length != 2) return null;
final lat = double.tryParse(coords[0].trim());
final lon = double.tryParse(coords[1].trim());
if (lat == null || lon == null) return null;
final label = parts.length > 1 ? parts[1].trim() : '';
final flags = parts.length > 2 ? parts[2].trim() : '';
return _MarkerPayload(
position: LatLng(lat, lon),
label: label.isEmpty ? context.l10n.map_sharedPin : label,
flags: flags,
);
}
String _buildMarkerId({
required String sourceId,
required DateTime timestamp,
required String text,
}) {
return '$sourceId|${timestamp.millisecondsSinceEpoch}|$text';
}
Marker _buildSharedMarker(_SharedMarker marker) { Marker _buildSharedMarker(_SharedMarker marker) {
final markerColor = marker.isChannel final markerColor = marker.isChannel
? (marker.isPublicChannel ? Colors.orange : Colors.purple) ? (marker.isPublicChannel ? Colors.orange : Colors.purple)
@@ -1337,7 +1447,15 @@ class _MapScreenState extends State<MapScreen> {
width: 60, width: 60,
height: 60, height: 60,
child: GestureDetector( child: GestureDetector(
onTap: () => _showMarkerInfo(marker), onTap: () async {
if (_removedMarkerIds.contains(marker.id)) {
setState(() {
_removedMarkerIds.remove(marker.id);
});
await _markerService.saveRemovedIds(_removedMarkerIds);
}
_showMarkerInfo(marker);
},
child: Column( child: Column(
children: [ children: [
Container( Container(
@@ -1391,11 +1509,17 @@ class _MapScreenState extends State<MapScreen> {
room: room, room: room,
// onLogin(password, isAdmin) isAdmin not used for room caht screen // onLogin(password, isAdmin) isAdmin not used for room caht screen
onLogin: (password, _) { onLogin: (password, _) {
// Navigate to chat screen after successful login final connector = context.read<MeshCoreConnector>();
context.read<MeshCoreConnector>().markContactRead(room.publicKeyHex); final unread = connector.getUnreadCountForContactKey(
room.publicKeyHex,
);
connector.markContactRead(room.publicKeyHex);
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => ChatScreen(contact: room)), MaterialPageRoute(
builder: (context) =>
ChatScreen(contact: room, initialUnreadCount: unread),
),
); );
}, },
), ),
@@ -1425,23 +1549,29 @@ class _MapScreenState extends State<MapScreen> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildInfoRow('Type', contact.typeLabel), _buildInfoRow(
_buildInfoRow('Path', contact.pathLabel), context.l10n.map_type,
contact.typeLabel(context.l10n),
),
_buildInfoRow(
context.l10n.map_path,
contact.pathLabel(context.l10n),
),
if (contact.hasLocation) if (contact.hasLocation)
_buildInfoRow( _buildInfoRow(
'Location', context.l10n.map_location,
'${contact.latitude!.toStringAsFixed(6)}, ${contact.longitude!.toStringAsFixed(6)}', '${contact.latitude!.toStringAsFixed(6)}, ${contact.longitude!.toStringAsFixed(6)}',
) )
else if (guessedPosition != null) else if (guessedPosition != null)
_buildInfoRow( _buildInfoRow(
'Est. Location', context.l10n.map_estLocation,
'~${guessedPosition.latitude.toStringAsFixed(6)}, ${guessedPosition.longitude.toStringAsFixed(6)}', '~${guessedPosition.latitude.toStringAsFixed(6)}, ${guessedPosition.longitude.toStringAsFixed(6)}',
), ),
_buildInfoRow( _buildInfoRow(
context.l10n.map_lastSeen, context.l10n.map_lastSeen,
_formatLastSeen(contact.lastSeen), _formatLastSeen(contact.lastSeen),
), ),
_buildInfoRow('Public Key', contact.publicKeyHex), _buildInfoRow(context.l10n.map_publicKey, contact.publicKeyHex),
], ],
), ),
actions: [ actions: [
@@ -1456,11 +1586,17 @@ class _MapScreenState extends State<MapScreen> {
if (!contact.isActive) { if (!contact.isActive) {
connector.importDiscoveredContact(contact); connector.importDiscoveredContact(contact);
} }
final unread = connector.getUnreadCountForContactKey(
contact.publicKeyHex,
);
Navigator.pop(dialogContext); Navigator.pop(dialogContext);
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ChatScreen(contact: contact), builder: (context) => ChatScreen(
contact: contact,
initialUnreadCount: unread,
),
), ),
); );
}, },
@@ -1542,7 +1678,9 @@ class _MapScreenState extends State<MapScreen> {
showDialog( showDialog(
context: context, context: context,
builder: (dialogContext) => AlertDialog( builder: (dialogContext) => AlertDialog(
title: Text(marker.label), title: Text(
marker.label.isEmpty ? context.l10n.map_sharedPin : marker.label,
),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -1550,7 +1688,11 @@ class _MapScreenState extends State<MapScreen> {
_buildInfoRow(context.l10n.map_from, marker.fromName), _buildInfoRow(context.l10n.map_from, marker.fromName),
_buildInfoRow(context.l10n.map_source, marker.sourceLabel), _buildInfoRow(context.l10n.map_source, marker.sourceLabel),
_buildInfoRow( _buildInfoRow(
'Location', context.l10n.map_sharedAt,
_formatLastSeen(marker.timestamp),
),
_buildInfoRow(
context.l10n.map_location,
'${marker.position.latitude.toStringAsFixed(6)}, ${marker.position.longitude.toStringAsFixed(6)}', '${marker.position.latitude.toStringAsFixed(6)}, ${marker.position.longitude.toStringAsFixed(6)}',
), ),
if (marker.flags.isNotEmpty) if (marker.flags.isNotEmpty)
@@ -1715,6 +1857,10 @@ class _MapScreenState extends State<MapScreen> {
String defaultLabel, String defaultLabel,
) async { ) async {
final controller = TextEditingController(text: defaultLabel); final controller = TextEditingController(text: defaultLabel);
controller.selection = TextSelection(
baseOffset: 0,
extentOffset: controller.text.length,
);
return showDialog<String>( return showDialog<String>(
context: context, context: context,
builder: (dialogContext) => AlertDialog( builder: (dialogContext) => AlertDialog(
@@ -2019,7 +2165,7 @@ class _MapScreenState extends State<MapScreen> {
enabled: settings.mapKeyPrefixEnabled, enabled: settings.mapKeyPrefixEnabled,
decoration: InputDecoration( decoration: InputDecoration(
labelText: context.l10n.map_publicKeyPrefix, labelText: context.l10n.map_publicKeyPrefix,
hintText: 'e.g. ab12', hintText: context.l10n.map_publicKeyPrefixHint,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
isDense: true, isDense: true,
), ),
@@ -2310,18 +2456,50 @@ class _GuessedLocation {
}); });
} }
class _MarkerPayload { class MarkerPayload {
final LatLng position; final LatLng position;
final String label; final String label;
final String flags; final String flags;
_MarkerPayload({ MarkerPayload({
required this.position, required this.position,
required this.label, required this.label,
required this.flags, required this.flags,
}); });
} }
/// Parse a shared marker text message of the form
/// `m:<lat>,<lon>|<label>|<flags>` and return a [MarkerPayload].
MarkerPayload? parseMarkerText(String text) {
final trimmed = text.trim();
final match = RegExp(
r'm:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|(.*)',
).firstMatch(trimmed);
if (match == null) return null;
final lat = double.tryParse(match.group(1) ?? '');
final lon = double.tryParse(match.group(2) ?? '');
if (lat == null || lon == null) return null;
final label = (match.group(3) ?? '').trim();
final flags = (match.group(4) ?? '').trim();
return MarkerPayload(position: LatLng(lat, lon), label: label, flags: flags);
}
/// Build a normalized dedupe key for shared markers.
/// Keeps the same algorithm previously present in both chat and map screens.
String buildSharedMarkerKey({
required String sourceId,
required String label,
required String fromName,
required String flags,
required bool isChannel,
}) {
final normalizedLabel = label.trim().toLowerCase();
final normalizedFrom = fromName.trim().toLowerCase();
final normalizedFlags = flags.trim().toLowerCase();
final scope = isChannel ? 'ch' : 'dm';
return '$scope|$sourceId|$normalizedFrom|$normalizedLabel|$normalizedFlags';
}
class _SharedMarker { class _SharedMarker {
final String id; final String id;
final LatLng position; final LatLng position;
@@ -2329,8 +2507,10 @@ class _SharedMarker {
final String flags; final String flags;
final String fromName; final String fromName;
final String sourceLabel; final String sourceLabel;
final DateTime timestamp;
final bool isChannel; final bool isChannel;
final bool isPublicChannel; final bool isPublicChannel;
final List<LatLng> history;
_SharedMarker({ _SharedMarker({
required this.id, required this.id,
@@ -2339,7 +2519,24 @@ class _SharedMarker {
required this.flags, required this.flags,
required this.fromName, required this.fromName,
required this.sourceLabel, required this.sourceLabel,
required this.timestamp,
required this.isChannel, required this.isChannel,
required this.isPublicChannel, required this.isPublicChannel,
this.history = const [],
}); });
_SharedMarker copyWithHistory(List<LatLng> newHistory) {
return _SharedMarker(
id: id,
position: position,
label: label,
flags: flags,
fromName: fromName,
sourceLabel: sourceLabel,
timestamp: timestamp,
isChannel: isChannel,
isPublicChannel: isPublicChannel,
history: newHistory,
);
}
} }
+85 -3
View File
@@ -76,9 +76,12 @@ class PathTraceMapScreen extends StatefulWidget {
class _PathTraceMapScreenState extends State<PathTraceMapScreen> { class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
static const double _labelZoomThreshold = 8.5; static const double _labelZoomThreshold = 8.5;
static const double _mapMinZoom = 2.0;
static const double _mapMaxZoom = 18.0;
//miles to meters conversion for filtering out repeaters that are too far from the last known GPS hop to be a likely match, to avoid false matches that throw off the inferred positions of other hops in the path //miles to meters conversion for filtering out repeaters that are too far from the last known GPS hop to be a likely match, to avoid false matches that throw off the inferred positions of other hops in the path
static const double _maxRepeaterMatchDistanceMeters = 40 * 1609.344; static const double _maxRepeaterMatchDistanceMeters = 40 * 1609.344;
final MapController _mapController = MapController();
StreamSubscription<Uint8List>? _frameSubscription; StreamSubscription<Uint8List>? _frameSubscription;
Timer? _timeoutTimer; Timer? _timeoutTimer;
@@ -116,11 +119,74 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
@override @override
void dispose() { void dispose() {
_mapController.dispose();
_frameSubscription?.cancel(); _frameSubscription?.cancel();
_timeoutTimer?.cancel(); _timeoutTimer?.cancel();
super.dispose(); super.dispose();
} }
bool _isDesktopPlatform(TargetPlatform platform) {
return platform == TargetPlatform.linux ||
platform == TargetPlatform.windows ||
platform == TargetPlatform.macOS;
}
void _zoomMapBy(double delta) {
final camera = _mapController.camera;
final nextZoom = (camera.zoom + delta)
.clamp(_mapMinZoom, _mapMaxZoom)
.toDouble();
_mapController.move(camera.center, nextZoom);
}
void _resetMapView() {
final bounds = _bounds;
if (bounds != null) {
_mapController.fitCamera(
CameraFit.bounds(
bounds: bounds,
padding: const EdgeInsets.all(64),
maxZoom: 16,
),
);
return;
}
final center = _initialCenter;
if (center != null) {
_mapController.move(center, _initialZoom);
}
}
Widget _buildDesktopMapControls() {
return Positioned(
top: 16,
left: 16,
child: Card(
elevation: 4,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Zoom in',
onPressed: () => _zoomMapBy(1),
),
IconButton(
icon: const Icon(Icons.remove),
tooltip: 'Zoom out',
onPressed: () => _zoomMapBy(-1),
),
IconButton(
icon: const Icon(Icons.my_location),
tooltip: 'Center map',
onPressed: _resetMapView,
),
],
),
),
);
}
Uint8List buildPath(Uint8List pathBytes) { Uint8List buildPath(Uint8List pathBytes) {
Uint8List traceBytes; Uint8List traceBytes;
@@ -519,6 +585,8 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
), ),
if (_hasData) if (_hasData)
_buildMapPathTrace(context, tileCache, _targetContact), _buildMapPathTrace(context, tileCache, _targetContact),
if (_hasData && _isDesktopPlatform(defaultTargetPlatform))
_buildDesktopMapControls(),
if (_points.isEmpty && if (_points.isEmpty &&
!_hasData && !_hasData &&
!_isLoading && !_isLoading &&
@@ -801,10 +869,24 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
MapTileCacheService tileCache, MapTileCacheService tileCache,
Contact? target, Contact? target,
) { ) {
final isDesktop = _isDesktopPlatform(defaultTargetPlatform);
return FlutterMap( return FlutterMap(
key: _mapKey, key: _mapKey,
mapController: _mapController,
options: MapOptions( options: MapOptions(
interactionOptions: InteractionOptions(flags: ~InteractiveFlag.rotate), interactionOptions: InteractionOptions(
flags: ~InteractiveFlag.rotate,
scrollWheelVelocity: isDesktop ? 0.012 : 0.005,
cursorKeyboardRotationOptions:
CursorKeyboardRotationOptions.disabled(),
keyboardOptions: isDesktop
? const KeyboardOptions(
enableArrowKeysPanning: true,
enableWASDPanning: true,
enableRFZooming: true,
)
: const KeyboardOptions.disabled(),
),
initialCenter: _initialCenter!, initialCenter: _initialCenter!,
initialZoom: _initialZoom, initialZoom: _initialZoom,
initialCameraFit: _bounds == null initialCameraFit: _bounds == null
@@ -814,8 +896,8 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
padding: const EdgeInsets.all(64), padding: const EdgeInsets.all(64),
maxZoom: 16, maxZoom: 16,
), ),
minZoom: 2.0, minZoom: _mapMinZoom,
maxZoom: 18.0, maxZoom: _mapMaxZoom,
onPositionChanged: (camera, hasGesture) { onPositionChanged: (camera, hasGesture) {
final shouldShow = camera.zoom >= _labelZoomThreshold; final shouldShow = camera.zoom >= _labelZoomThreshold;
if (shouldShow != _showNodeLabels && mounted) { if (shouldShow != _showNodeLabels && mounted) {
+2 -1
View File
@@ -3,6 +3,7 @@ import 'package:meshcore_open/connector/meshcore_protocol.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../l10n/l10n.dart'; import '../l10n/l10n.dart';
import '../models/contact.dart'; import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../services/app_settings_service.dart'; import '../services/app_settings_service.dart';
import 'repeater_status_screen.dart'; import 'repeater_status_screen.dart';
import 'repeater_cli_screen.dart'; import 'repeater_cli_screen.dart';
@@ -93,7 +94,7 @@ class RepeaterHubScreen extends StatelessWidget {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
repeater.pathLabel, repeater.pathLabel(context.l10n),
style: TextStyle(fontSize: 14, color: Colors.grey[600]), style: TextStyle(fontSize: 14, color: Colors.grey[600]),
), ),
if (repeater.hasLocation) ...[ if (repeater.hasLocation) ...[
+4 -4
View File
@@ -341,9 +341,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
ListTile( ListTile(
leading: const Icon(Icons.delete_outline, color: Colors.red), leading: const Icon(Icons.delete_outline, color: Colors.red),
title: Text("Delete All Paths"), title: Text(l10n.settings_deleteAllPaths),
subtitle: Text( subtitle: Text(
"Clear all path data from contacts.", l10n.settings_deleteAllPathsSubtitle,
style: TextStyle(color: Colors.red[700]), style: TextStyle(color: Colors.red[700]),
), ),
onTap: () => connector.deleteAllPaths(), onTap: () => connector.deleteAllPaths(),
@@ -405,8 +405,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
ListTile( ListTile(
leading: const Icon(Icons.bluetooth_outlined), leading: const Icon(Icons.bluetooth_outlined),
title: Text(l10n.settings_bleDebugLog), title: Text(l10n.settings_companionDebugLog),
subtitle: Text(l10n.settings_bleDebugLogSubtitle), subtitle: Text(l10n.settings_companionDebugLogSubtitle),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () { onTap: () {
Navigator.push( Navigator.push(
+32 -3
View File
@@ -1,8 +1,19 @@
import '../utils/platform_info.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import '../l10n/app_localizations.dart';
import '../utils/platform_info.dart';
class BackgroundService { class BackgroundService {
bool _initialized = false; 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 { Future<void> initialize() async {
if (!PlatformInfo.isAndroid || _initialized) return; if (!PlatformInfo.isAndroid || _initialized) return;
@@ -34,13 +45,31 @@ class BackgroundService {
} }
final running = await FlutterForegroundTask.isRunningService; final running = await FlutterForegroundTask.isRunningService;
if (running) return; if (running) return;
final l10n = await _loadLocalizations();
await FlutterForegroundTask.startService( await FlutterForegroundTask.startService(
notificationTitle: 'MeshCore running', notificationTitle: l10n.background_serviceTitle,
notificationText: 'Keeping BLE connected', notificationText: l10n.background_serviceText,
callback: startCallback, callback: startCallback,
); );
} }
Future<AppLocalizations> _loadLocalizations() async {
final supported = AppLocalizations.supportedLocales;
final override = _languageOverrideProvider?.call();
if (override != null && override.isNotEmpty) {
final overrideLocale = Locale(override);
final isSupported = supported.any(
(l) => l.languageCode == overrideLocale.languageCode,
);
if (isSupported) {
return AppLocalizations.delegate.load(overrideLocale);
}
}
final preferred = WidgetsBinding.instance.platformDispatcher.locales;
final match = basicLocaleListResolution(preferred, supported);
return AppLocalizations.delegate.load(match);
}
Future<void> stop() async { Future<void> stop() async {
if (!PlatformInfo.isAndroid) return; if (!PlatformInfo.isAndroid) return;
final running = await FlutterForegroundTask.isRunningService; final running = await FlutterForegroundTask.isRunningService;
+48 -1
View File
@@ -24,6 +24,26 @@ class LineOfSightSample {
}); });
} }
class LineOfSightObstruction {
final int sampleIndex;
final LatLng point;
final double distanceMeters;
final double clearanceMeters;
final double obstructionMeters;
final double terrainMeters;
final double lineHeightMeters;
const LineOfSightObstruction({
required this.sampleIndex,
required this.point,
required this.distanceMeters,
required this.clearanceMeters,
required this.obstructionMeters,
required this.terrainMeters,
required this.lineHeightMeters,
});
}
class LineOfSightResult { class LineOfSightResult {
final bool hasData; final bool hasData;
final bool isClear; final bool isClear;
@@ -31,6 +51,7 @@ class LineOfSightResult {
final double maxObstructionMeters; final double maxObstructionMeters;
final double? firstObstructionDistanceMeters; final double? firstObstructionDistanceMeters;
final List<LineOfSightSample> samples; final List<LineOfSightSample> samples;
final List<LineOfSightObstruction> obstructions;
final String? errorMessage; final String? errorMessage;
final double usedKFactor; final double usedKFactor;
final double? frequencyMHz; final double? frequencyMHz;
@@ -42,6 +63,7 @@ class LineOfSightResult {
required this.maxObstructionMeters, required this.maxObstructionMeters,
required this.firstObstructionDistanceMeters, required this.firstObstructionDistanceMeters,
required this.samples, required this.samples,
required this.obstructions,
required this.usedKFactor, required this.usedKFactor,
this.frequencyMHz, this.frequencyMHz,
this.errorMessage, this.errorMessage,
@@ -56,7 +78,8 @@ class LineOfSightResult {
isClear = false, isClear = false,
maxObstructionMeters = 0, maxObstructionMeters = 0,
firstObstructionDistanceMeters = null, firstObstructionDistanceMeters = null,
samples = const []; samples = const [],
obstructions = const [];
} }
class LineOfSightPathSegment { class LineOfSightPathSegment {
@@ -191,6 +214,7 @@ class LineOfSightService {
maxObstructionMeters: 0, maxObstructionMeters: 0,
firstObstructionDistanceMeters: null, firstObstructionDistanceMeters: null,
samples: const [], samples: const [],
obstructions: const [],
usedKFactor: kFactor, usedKFactor: kFactor,
frequencyMHz: frequencyMHz, frequencyMHz: frequencyMHz,
); );
@@ -249,7 +273,9 @@ class LineOfSightService {
var maxObstructionMeters = 0.0; var maxObstructionMeters = 0.0;
double? firstObstructionDistanceMeters; double? firstObstructionDistanceMeters;
final samples = <LineOfSightSample>[]; final samples = <LineOfSightSample>[];
final obstructions = <LineOfSightObstruction>[];
var isClear = true; var isClear = true;
LineOfSightObstruction? clusterWorstObstruction;
for (int i = 0; i < points.length; i++) { for (int i = 0; i < points.length; i++) {
final fraction = points.length == 1 ? 0.0 : i / (points.length - 1); final fraction = points.length == 1 ? 0.0 : i / (points.length - 1);
@@ -274,6 +300,23 @@ class LineOfSightService {
maxObstructionMeters = obstruction; maxObstructionMeters = obstruction;
} }
firstObstructionDistanceMeters ??= distanceFromStart; firstObstructionDistanceMeters ??= distanceFromStart;
final candidate = LineOfSightObstruction(
sampleIndex: i,
point: points[i],
distanceMeters: distanceFromStart,
clearanceMeters: clearance,
obstructionMeters: obstruction,
terrainMeters: terrainHeight,
lineHeightMeters: lineHeight,
);
if (clusterWorstObstruction == null ||
candidate.obstructionMeters >
clusterWorstObstruction.obstructionMeters) {
clusterWorstObstruction = candidate;
}
} else if (clusterWorstObstruction != null) {
obstructions.add(clusterWorstObstruction);
clusterWorstObstruction = null;
} }
samples.add( samples.add(
@@ -286,6 +329,9 @@ class LineOfSightService {
), ),
); );
} }
if (clusterWorstObstruction != null) {
obstructions.add(clusterWorstObstruction);
}
return LineOfSightResult( return LineOfSightResult(
hasData: true, hasData: true,
@@ -294,6 +340,7 @@ class LineOfSightService {
maxObstructionMeters: maxObstructionMeters, maxObstructionMeters: maxObstructionMeters,
firstObstructionDistanceMeters: firstObstructionDistanceMeters, firstObstructionDistanceMeters: firstObstructionDistanceMeters,
samples: samples, samples: samples,
obstructions: obstructions,
usedKFactor: kFactor, usedKFactor: kFactor,
frequencyMHz: frequencyMHz, frequencyMHz: frequencyMHz,
); );
+53 -8
View File
@@ -519,12 +519,11 @@ class TranslationService extends ChangeNotifier {
} }
String? _heuristicLanguageCode(String text) { String? _heuristicLanguageCode(String text) {
if (RegExp(r'[іїєґІЇЄҐ]').hasMatch(text)) { final trimmed = text.trim();
return 'uk'; if (trimmed.isEmpty) {
} return null;
if (RegExp(r'[а-яёА-ЯЁ]').hasMatch(text)) {
return 'ru';
} }
if (RegExp(r'[ぁ-んァ-ン]').hasMatch(text)) { if (RegExp(r'[ぁ-んァ-ン]').hasMatch(text)) {
return 'ja'; return 'ja';
} }
@@ -534,9 +533,55 @@ class TranslationService extends ChangeNotifier {
if (RegExp(r'[\u4e00-\u9fff]').hasMatch(text)) { if (RegExp(r'[\u4e00-\u9fff]').hasMatch(text)) {
return 'zh'; return 'zh';
} }
// Latin-script languages can't be reliably distinguished by characters
// alone return null so the translator always attempts translation. final lower = trimmed.toLowerCase();
return null; final patterns = <String, String>{
'uk': r'\b(привіт|дякую|будь|ласка|як|де|не|так|це|є|най|ще|може|для)\b',
'ru':
r'\b(что|это|как|не|да|нет|он|она|они|быть|есть|для|сегодня|если|уже|может)\b',
'bg': r'\b(ще|няма|благодаря|моля|това|какво|тук|ние|вие|не|със|за)\b',
'de':
r'\b(der|die|das|und|ist|nicht|ein|eine|ich|für|mit|auf|zu|auch|als|an|im|am|es|dem|den|sich|von)\b',
'en':
r'\b(the|and|is|you|for|with|from|not|that|this|have|be|are|was|were|but|can|will|your|what|when|how|they)\b',
'es':
r'\b(el|la|los|las|es|que|de|en|con|por|para|no|un|una|se|como|su|al|del|está)\b',
'fr':
r'\b(le|la|les|un|une|et|est|que|qui|pour|dans|pas|avec|sur|ne|vous|il|elle|des|ce|cette|je|tu|nous|vous)\b',
'it':
r'\b(il|la|lo|un|una|che|di|da|in|per|con|non|si|mi|ti|noi|voi|lui|lei)\b',
'pt':
r'\b(os|as|que|de|do|da|em|para|com|por|não|uma|um|se|você|também)\b',
'nl':
r'\b(de|het|een|en|is|niet|dat|wat|je|ik|op|aan|voor|met|als|nog|zijn)\b',
'sv':
r'\b(och|är|det|att|som|en|på|inte|har|var|men|du|jag|vi|ni|den|detta)\b',
'pl':
r'\b(na|się|nie|jest|to|że|do|od|dla|czy|tak|ale|ma|jak|on|ona|my)\b',
'sk': r'\b(je|na|so|že|do|od|za|si|to|ten|tá|tí|ako|má|nie|som|sa)\b',
'sl': r'\b(in|je|na|se|da|za|od|ne|to|ta|so|kako|bo|sem|si)\b',
'hu':
r'\b(az|és|nem|van|volt|hogy|mit|mire|ki|mi|ez|azért|is|de|ha|te|ő|mi|itt)\b',
};
final scores = <String, int>{};
for (final entry in patterns.entries) {
scores[entry.key] = RegExp(
entry.value,
caseSensitive: false,
).allMatches(lower).length;
}
final sorted = scores.entries.toList()
..sort((a, b) => b.value.compareTo(a.value));
if (sorted.isEmpty || sorted.first.value == 0) {
return null;
}
if (sorted.length > 1 && sorted.first.value == sorted[1].value) {
return null;
}
return sorted.first.key;
} }
String _languageLabel(String code) { String _languageLabel(String code) {
+5
View File
@@ -43,6 +43,7 @@ class ContactDiscoveryStore {
'latitude': contact.latitude, 'latitude': contact.latitude,
'longitude': contact.longitude, 'longitude': contact.longitude,
'lastSeen': contact.lastSeen.millisecondsSinceEpoch, 'lastSeen': contact.lastSeen.millisecondsSinceEpoch,
'lastModified': contact.lastModified?.millisecondsSinceEpoch,
'lastMessageAt': contact.lastMessageAt.millisecondsSinceEpoch, 'lastMessageAt': contact.lastMessageAt.millisecondsSinceEpoch,
'rawPacket': contact.rawPacket != null 'rawPacket': contact.rawPacket != null
? base64Encode(contact.rawPacket!) ? base64Encode(contact.rawPacket!)
@@ -53,6 +54,7 @@ class ContactDiscoveryStore {
Contact _fromJson(Map<String, dynamic> json) { Contact _fromJson(Map<String, dynamic> json) {
final lastSeenMs = json['lastSeen'] as int? ?? 0; final lastSeenMs = json['lastSeen'] as int? ?? 0;
final lastMessageMs = json['lastMessageAt'] as int?; final lastMessageMs = json['lastMessageAt'] as int?;
final lastModifiedMs = json['lastModified'] as int?;
return Contact( return Contact(
publicKey: Uint8List.fromList(base64Decode(json['publicKey'] as String)), publicKey: Uint8List.fromList(base64Decode(json['publicKey'] as String)),
name: json['name'] as String? ?? 'Unknown', name: json['name'] as String? ?? 'Unknown',
@@ -71,6 +73,9 @@ class ContactDiscoveryStore {
latitude: (json['latitude'] as num?)?.toDouble(), latitude: (json['latitude'] as num?)?.toDouble(),
longitude: (json['longitude'] as num?)?.toDouble(), longitude: (json['longitude'] as num?)?.toDouble(),
lastSeen: DateTime.fromMillisecondsSinceEpoch(lastSeenMs), lastSeen: DateTime.fromMillisecondsSinceEpoch(lastSeenMs),
lastModified: lastModifiedMs == null
? null
: DateTime.fromMillisecondsSinceEpoch(lastModifiedMs),
lastMessageAt: DateTime.fromMillisecondsSinceEpoch( lastMessageAt: DateTime.fromMillisecondsSinceEpoch(
lastMessageMs ?? lastSeenMs, lastMessageMs ?? lastSeenMs,
), ),
+5
View File
@@ -75,6 +75,7 @@ class ContactStore {
'latitude': contact.latitude, 'latitude': contact.latitude,
'longitude': contact.longitude, 'longitude': contact.longitude,
'lastSeen': contact.lastSeen.millisecondsSinceEpoch, 'lastSeen': contact.lastSeen.millisecondsSinceEpoch,
'lastModified': contact.lastModified?.millisecondsSinceEpoch,
'lastMessageAt': contact.lastMessageAt.millisecondsSinceEpoch, 'lastMessageAt': contact.lastMessageAt.millisecondsSinceEpoch,
'isActive': contact.isActive, 'isActive': contact.isActive,
'rawPacket': contact.rawPacket != null 'rawPacket': contact.rawPacket != null
@@ -86,6 +87,7 @@ class ContactStore {
Contact _fromJson(Map<String, dynamic> json) { Contact _fromJson(Map<String, dynamic> json) {
final lastSeenMs = json['lastSeen'] as int? ?? 0; final lastSeenMs = json['lastSeen'] as int? ?? 0;
final lastMessageMs = json['lastMessageAt'] as int?; final lastMessageMs = json['lastMessageAt'] as int?;
final lastModifiedMs = json['lastModified'] as int?;
return Contact( return Contact(
publicKey: Uint8List.fromList(base64Decode(json['publicKey'] as String)), publicKey: Uint8List.fromList(base64Decode(json['publicKey'] as String)),
name: json['name'] as String? ?? 'Unknown', name: json['name'] as String? ?? 'Unknown',
@@ -104,6 +106,9 @@ class ContactStore {
latitude: (json['latitude'] as num?)?.toDouble(), latitude: (json['latitude'] as num?)?.toDouble(),
longitude: (json['longitude'] as num?)?.toDouble(), longitude: (json['longitude'] as num?)?.toDouble(),
lastSeen: DateTime.fromMillisecondsSinceEpoch(lastSeenMs), lastSeen: DateTime.fromMillisecondsSinceEpoch(lastSeenMs),
lastModified: lastModifiedMs == null
? null
: DateTime.fromMillisecondsSinceEpoch(lastModifiedMs),
lastMessageAt: DateTime.fromMillisecondsSinceEpoch( lastMessageAt: DateTime.fromMillisecondsSinceEpoch(
lastMessageMs ?? lastSeenMs, lastMessageMs ?? lastSeenMs,
), ),
+447
View File
@@ -0,0 +1,447 @@
import 'package:flutter/material.dart';
/// MeshCore redesign palette warm field-journal dark theme with
/// phosphor-green signal accents. Mirrors values from the redesign spec.
class MeshPalette {
MeshPalette._();
// Surfaces (warm near-black, olive undertone)
static const bg = Color(0xFF0F1412);
static const bg1 = Color(0xFF161C19);
static const bg2 = Color(0xFF1D2521);
static const bg3 = Color(0xFF28322D);
static const bg4 = Color(0xFF34403A);
// Lines
static const line = Color(0xFF232C28);
static const line2 = Color(0xFF34403A);
static const line3 = Color(0xFF48564F);
// Ink
static const ink = Color(0xFFEFF3E8);
static const ink2 = Color(0xFFBAC4B5);
static const ink3 = Color(0xFF7C8B82);
static const ink4 = Color(0xFF55635B);
// Signal (phosphor)
static const signal = Color(0xFF7BEFA8);
static const signalDim = Color(0xFF4DC580);
static const signalBg = Color(0x177BEFA8); // ~9% alpha
static const signalLine = Color(0x427BEFA8); // ~26%
static const signalGlow = Color(0x597BEFA8); // ~35%
// Warn (ember)
static const warn = Color(0xFFFFA552);
static const warnDim = Color(0xFFC27E3C);
static const warnBg = Color(0x1CFFA552);
static const warnLine = Color(0x4DFFA552);
// Alert (coral)
static const alert = Color(0xFFFF6A5C);
static const alertBg = Color(0x1CFF6A5C);
static const alertLine = Color(0x52FF6A5C);
// Blue (dusk sky)
static const blue = Color(0xFF7FCBF5);
static const blueBg = Color(0x1C7FCBF5);
static const blueLine = Color(0x477FCBF5);
// Magenta
static const magenta = Color(0xFFDE7FDB);
static const magentaBg = Color(0x1CDE7FDB);
static const magentaLine = Color(0x47DE7FDB);
// Me bubble (mossy)
static const me = Color(0xFF1E3527);
static const meBorder = Color(0xFF2D5039);
static const meInk = Color(0xFFDEF0DC);
// Light variant (used when user explicitly picks light theme)
static const lightBg = Color(0xFFF5F3EC);
static const lightBg1 = Color(0xFFECE9DF);
static const lightBg2 = Color(0xFFE2DED2);
static const lightLine = Color(0xFFCAC5B4);
static const lightInk = Color(0xFF0F1410);
static const lightInk2 = Color(0xFF3D463E);
static const lightInk3 = Color(0xFF6A756D);
static const lightSignal = Color(0xFF1A7A44);
}
/// Named font stacks Flutter falls back to system fonts when the named
/// family isn't installed, keeping things working without bundled assets.
class MeshFonts {
MeshFonts._();
static const sans = 'Inter';
static const mono = 'JetBrains Mono';
static const display = 'Instrument Serif';
static const List<String> sansFallback = [
'system-ui',
'-apple-system',
'Roboto',
'Noto Sans',
'sans-serif',
];
static const List<String> monoFallback = [
'SF Mono',
'Menlo',
'Consolas',
'Roboto Mono',
'monospace',
];
static const List<String> displayFallback = [
'Cormorant Garamond',
'Georgia',
'Times New Roman',
'serif',
];
}
/// Radii used consistently across the app.
class MeshRadii {
MeshRadii._();
static const xs = 6.0;
static const sm = 10.0;
static const md = 14.0;
static const lg = 18.0;
static const xl = 24.0;
static const pill = 999.0;
}
/// Shared helpers exposed via [MeshTheme.of].
class MeshTheme {
MeshTheme._();
static ThemeData dark() {
const scheme = ColorScheme.dark(
primary: MeshPalette.signal,
onPrimary: Color(0xFF0A1810),
primaryContainer: MeshPalette.signalBg,
onPrimaryContainer: MeshPalette.signal,
secondary: MeshPalette.blue,
onSecondary: Color(0xFF0A1520),
tertiary: MeshPalette.magenta,
onTertiary: Color(0xFF201020),
error: MeshPalette.alert,
onError: Color(0xFF1A0A08),
errorContainer: MeshPalette.alertBg,
onErrorContainer: MeshPalette.alert,
surface: MeshPalette.bg,
onSurface: MeshPalette.ink,
surfaceContainerLowest: MeshPalette.bg,
surfaceContainerLow: MeshPalette.bg1,
surfaceContainer: MeshPalette.bg1,
surfaceContainerHigh: MeshPalette.bg2,
surfaceContainerHighest: MeshPalette.bg3,
onSurfaceVariant: MeshPalette.ink2,
outline: MeshPalette.line2,
outlineVariant: MeshPalette.line,
shadow: Colors.black,
scrim: Colors.black54,
inverseSurface: MeshPalette.ink,
onInverseSurface: MeshPalette.bg,
inversePrimary: MeshPalette.signalDim,
);
return _build(scheme, Brightness.dark);
}
static ThemeData light() {
const scheme = ColorScheme.light(
primary: MeshPalette.lightSignal,
onPrimary: Colors.white,
primaryContainer: Color(0xFFD4E8D8),
onPrimaryContainer: MeshPalette.lightSignal,
secondary: Color(0xFF2F6EA8),
onSecondary: Colors.white,
tertiary: Color(0xFF8C4A8A),
onTertiary: Colors.white,
error: Color(0xFFB53D2F),
onError: Colors.white,
surface: MeshPalette.lightBg,
onSurface: MeshPalette.lightInk,
surfaceContainerLowest: MeshPalette.lightBg,
surfaceContainerLow: MeshPalette.lightBg1,
surfaceContainer: MeshPalette.lightBg1,
surfaceContainerHigh: MeshPalette.lightBg2,
surfaceContainerHighest: Color(0xFFD5D0C0),
onSurfaceVariant: MeshPalette.lightInk2,
outline: MeshPalette.lightLine,
outlineVariant: Color(0xFFDBD6C6),
);
return _build(scheme, Brightness.light);
}
static ThemeData _build(ColorScheme scheme, Brightness brightness) {
final baseText =
Typography.material2021(
platform: TargetPlatform.android,
colorScheme: scheme,
).black.apply(
bodyColor: scheme.onSurface,
displayColor: scheme.onSurface,
fontFamily: MeshFonts.sans,
fontFamilyFallback: MeshFonts.sansFallback,
);
return ThemeData(
useMaterial3: true,
brightness: brightness,
colorScheme: scheme,
scaffoldBackgroundColor: scheme.surface,
canvasColor: scheme.surface,
fontFamily: MeshFonts.sans,
fontFamilyFallback: MeshFonts.sansFallback,
textTheme: baseText,
dividerColor: scheme.outlineVariant,
dividerTheme: DividerThemeData(
color: scheme.outlineVariant,
thickness: 1,
space: 1,
),
appBarTheme: AppBarTheme(
backgroundColor: scheme.surface,
foregroundColor: scheme.onSurface,
surfaceTintColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: false,
titleTextStyle: TextStyle(
fontFamily: MeshFonts.sans,
fontFamilyFallback: MeshFonts.sansFallback,
fontSize: 20,
fontWeight: FontWeight.w600,
letterSpacing: -0.2,
color: scheme.onSurface,
),
iconTheme: IconThemeData(color: scheme.onSurface),
shape: Border(
bottom: BorderSide(color: scheme.outlineVariant, width: 1),
),
),
cardTheme: CardThemeData(
color: scheme.surfaceContainerLow,
surfaceTintColor: Colors.transparent,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.md),
side: BorderSide(color: scheme.outlineVariant, width: 1),
),
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 0),
),
listTileTheme: ListTileThemeData(
iconColor: scheme.onSurfaceVariant,
textColor: scheme.onSurface,
tileColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.md),
),
),
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: scheme.primary,
foregroundColor: scheme.onPrimary,
elevation: 0,
focusElevation: 0,
hoverElevation: 0,
highlightElevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.pill),
),
extendedTextStyle: const TextStyle(
fontFamily: MeshFonts.sans,
fontFamilyFallback: MeshFonts.sansFallback,
fontWeight: FontWeight.w700,
letterSpacing: 0.2,
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: scheme.primary,
foregroundColor: scheme.onPrimary,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.pill),
),
textStyle: const TextStyle(
fontFamily: MeshFonts.sans,
fontFamilyFallback: MeshFonts.sansFallback,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: scheme.onSurface,
side: BorderSide(color: scheme.outline),
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.pill),
),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: scheme.primary,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.pill),
),
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: scheme.surfaceContainerHigh,
hintStyle: TextStyle(color: scheme.onSurfaceVariant),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(MeshRadii.md),
borderSide: BorderSide(color: scheme.outline),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(MeshRadii.md),
borderSide: BorderSide(color: scheme.outlineVariant),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(MeshRadii.md),
borderSide: BorderSide(color: scheme.primary, width: 1.5),
),
),
chipTheme: ChipThemeData(
backgroundColor: scheme.surfaceContainerLow,
side: BorderSide(color: scheme.outlineVariant),
labelStyle: TextStyle(
fontFamily: MeshFonts.sans,
fontFamilyFallback: MeshFonts.sansFallback,
fontSize: 12.5,
fontWeight: FontWeight.w600,
color: scheme.onSurfaceVariant,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.pill),
),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
),
navigationBarTheme: NavigationBarThemeData(
backgroundColor: scheme.surfaceContainerLow,
surfaceTintColor: Colors.transparent,
indicatorColor: scheme.primary.withValues(alpha: 0.14),
indicatorShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.md),
),
labelTextStyle: WidgetStateProperty.resolveWith((states) {
final selected = states.contains(WidgetState.selected);
return TextStyle(
fontFamily: MeshFonts.mono,
fontFamilyFallback: MeshFonts.monoFallback,
fontSize: 10,
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
letterSpacing: 0.1,
color: selected ? scheme.primary : scheme.onSurfaceVariant,
);
}),
iconTheme: WidgetStateProperty.resolveWith((states) {
final selected = states.contains(WidgetState.selected);
return IconThemeData(
color: selected ? scheme.primary : scheme.onSurfaceVariant,
size: 22,
);
}),
),
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: scheme.surfaceContainerLow,
surfaceTintColor: Colors.transparent,
modalBackgroundColor: scheme.surfaceContainerLow,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(MeshRadii.lg),
),
),
),
dialogTheme: DialogThemeData(
backgroundColor: scheme.surfaceContainerLow,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.lg),
),
),
snackBarTheme: SnackBarThemeData(
backgroundColor: scheme.surfaceContainerHigh,
contentTextStyle: TextStyle(color: scheme.onSurface),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.md),
),
),
popupMenuTheme: PopupMenuThemeData(
color: scheme.surfaceContainerHigh,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.md),
),
),
iconTheme: IconThemeData(color: scheme.onSurfaceVariant, size: 22),
splashFactory: InkSparkle.splashFactory,
);
}
/// Mono text style sizes default to the body size Inter is using.
static TextStyle mono({
double? fontSize,
FontWeight? fontWeight,
Color? color,
double? letterSpacing,
}) {
return TextStyle(
fontFamily: MeshFonts.mono,
fontFamilyFallback: MeshFonts.monoFallback,
fontSize: fontSize,
fontWeight: fontWeight,
color: color,
letterSpacing: letterSpacing ?? 0.2,
fontFeatures: const [FontFeature.tabularFigures()],
);
}
/// Serif display style.
static TextStyle display({
double? fontSize,
FontWeight? fontWeight,
Color? color,
double? letterSpacing,
}) {
return TextStyle(
fontFamily: MeshFonts.display,
fontFamilyFallback: MeshFonts.displayFallback,
fontSize: fontSize,
fontWeight: fontWeight ?? FontWeight.w400,
color: color,
letterSpacing: letterSpacing ?? -0.2,
);
}
/// Small-caps mono label used for section accents and chip labels.
static TextStyle accentLabel({Color? color, double? fontSize}) {
return TextStyle(
fontFamily: MeshFonts.mono,
fontFamilyFallback: MeshFonts.monoFallback,
fontSize: fontSize ?? 9.5,
fontWeight: FontWeight.w600,
letterSpacing: 1.8,
color: color,
);
}
/// Color-code an SNR value for consistency across the app.
static Color snrColor(num? snr, {required bool blocked}) {
if (blocked) return MeshPalette.alert;
if (snr == null) return MeshPalette.ink3;
if (snr > -5) return MeshPalette.signal;
if (snr > -12) return MeshPalette.warn;
return MeshPalette.alert;
}
}
+3 -3
View File
@@ -72,8 +72,8 @@ class GpxExport {
contact.name, contact.name,
contact.latitude!, contact.latitude!,
contact.longitude!, contact.longitude!,
"Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}",
url, url,
"Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}",
); );
} }
} }
@@ -91,8 +91,8 @@ class GpxExport {
contact.name, contact.name,
contact.latitude!, contact.latitude!,
contact.longitude!, contact.longitude!,
"Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}",
url, url,
"Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}",
); );
} }
} }
@@ -110,8 +110,8 @@ class GpxExport {
contact.name, contact.name,
contact.latitude ?? 0.0, contact.latitude ?? 0.0,
contact.longitude ?? 0.0, contact.longitude ?? 0.0,
"Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}",
url, url,
"Type: ${contact.typeLabelRaw}\nPublic Key: ${contact.publicKeyHex}",
); );
} }
} }
+4 -3
View File
@@ -9,6 +9,7 @@ import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart'; import '../connector/meshcore_connector.dart';
import '../l10n/l10n.dart'; import '../l10n/l10n.dart';
import '../models/contact.dart'; import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../helpers/path_helper.dart'; import '../helpers/path_helper.dart';
import '../services/path_history_service.dart'; import '../services/path_history_service.dart';
import '../helpers/snack_bar_builder.dart'; import '../helpers/snack_bar_builder.dart';
@@ -147,7 +148,7 @@ class _PathManagementDialogState extends State<_PathManagementDialog> {
context, context,
availableContacts: availableContacts, availableContacts: availableContacts,
initialPath: pathForInput.isEmpty ? null : pathForInput, initialPath: pathForInput.isEmpty ? null : pathForInput,
currentPathLabel: currentContact.pathLabel, currentPathLabel: currentContact.pathLabel(l10n),
onRefresh: connector.isConnected ? connector.getContacts : null, onRefresh: connector.isConnected ? connector.getContacts : null,
); );
@@ -236,7 +237,7 @@ class _PathManagementDialogState extends State<_PathManagementDialog> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
l10n.path_currentPath(currentContact.pathLabel), l10n.path_currentPath(currentContact.pathLabel(l10n)),
style: const TextStyle(fontSize: 12, color: Colors.grey), style: const TextStyle(fontSize: 12, color: Colors.grey),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -303,7 +304,7 @@ class _PathManagementDialogState extends State<_PathManagementDialog> {
), ),
isThreeLine: true, isThreeLine: true,
subtitle: Text( subtitle: Text(
'${(path.tripTimeMs / 1000).toStringAsFixed(2)}s • ${_formatRelativeTime(context, path.timestamp)}\n${path.successCount} ${l10n.chat_successes}Score: ${path.routeWeight.toStringAsFixed(1)}', '${(path.tripTimeMs / 1000).toStringAsFixed(2)}s • ${_formatRelativeTime(context, path.timestamp)}\n${path.successCount} ${l10n.chat_successes}${l10n.chat_score}: ${path.routeWeight.toStringAsFixed(1)}',
style: const TextStyle(fontSize: 11), style: const TextStyle(fontSize: 11),
), ),
trailing: Row( trailing: Row(
+2 -1
View File
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:meshcore_open/connector/meshcore_protocol.dart'; import 'package:meshcore_open/connector/meshcore_protocol.dart';
import '../l10n/l10n.dart'; import '../l10n/l10n.dart';
import '../models/contact.dart'; import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../helpers/snack_bar_builder.dart'; import '../helpers/snack_bar_builder.dart';
class PathSelectionDialog extends StatefulWidget { class PathSelectionDialog extends StatefulWidget {
@@ -311,7 +312,7 @@ class _PathSelectionDialogState extends State<PathSelectionDialog> {
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
subtitle: Text( subtitle: Text(
'${contact.typeLabel}${contact.publicKeyHex.substring(0, 2)}', '${contact.typeLabel(l10n)}${contact.publicKeyHex.substring(0, 2)}',
style: const TextStyle(fontSize: 10), style: const TextStyle(fontSize: 10),
), ),
trailing: isSelected trailing: isSelected
+3 -1
View File
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart';
import '../l10n/l10n.dart';
/// A reusable QR code display widget for sharing data. /// A reusable QR code display widget for sharing data.
/// ///
/// Features: /// Features:
@@ -197,7 +199,7 @@ class QrCodeShareDialog extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: FilledButton( child: FilledButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
child: const Text('Done'), child: Text(context.l10n.common_done),
), ),
), ),
], ],
+2 -1
View File
@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../l10n/l10n.dart'; import '../l10n/l10n.dart';
import '../models/contact.dart'; import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../services/storage_service.dart'; import '../services/storage_service.dart';
import '../connector/meshcore_connector.dart'; import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart'; import '../connector/meshcore_protocol.dart';
@@ -467,7 +468,7 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
repeater.pathLabel, repeater.pathLabel(context.l10n),
style: const TextStyle(fontSize: 11, color: Colors.grey), style: const TextStyle(fontSize: 11, color: Colors.grey),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
+2 -1
View File
@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../l10n/l10n.dart'; import '../l10n/l10n.dart';
import '../models/contact.dart'; import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../services/storage_service.dart'; import '../services/storage_service.dart';
import '../connector/meshcore_connector.dart'; import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart'; import '../connector/meshcore_protocol.dart';
@@ -393,7 +394,7 @@ class _RoomLoginDialogState extends State<RoomLoginDialog> {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
repeater.pathLabel, repeater.pathLabel(context.l10n),
style: const TextStyle(fontSize: 11, color: Colors.grey), style: const TextStyle(fontSize: 11, color: Colors.grey),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
+32
View File
@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import '../l10n/l10n.dart';
class UnreadDivider extends StatelessWidget {
const UnreadDivider({super.key});
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.primary;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Expanded(child: Divider(color: color)),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
context.l10n.chat_newMessages,
style: TextStyle(
color: color,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
Expanded(child: Divider(color: color)),
],
),
);
}
}
-1
View File
@@ -8,7 +8,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
flserial flserial
jni
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)
+93
View File
@@ -0,0 +1,93 @@
import 'dart:typed_data';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:meshcore_open/connector/meshcore_protocol.dart';
import 'package:meshcore_open/l10n/app_localizations.dart';
import 'package:meshcore_open/l10n/contact_localization.dart';
import 'package:meshcore_open/models/contact.dart';
Contact _contact({
int type = advTypeChat,
int pathLength = 0,
int? pathOverride,
}) {
return Contact(
publicKey: Uint8List.fromList(List.generate(32, (i) => i + 1)),
name: 'Node',
type: type,
pathLength: pathLength,
path: Uint8List(0),
pathOverride: pathOverride,
lastSeen: DateTime.fromMillisecondsSinceEpoch(0),
);
}
void main() {
late AppLocalizations l10n;
setUpAll(() async {
l10n = await AppLocalizations.delegate.load(const Locale('en'));
});
group('Contact.typeLabel', () {
test('chat', () {
expect(_contact(type: advTypeChat).typeLabel(l10n), 'Chat');
});
test('repeater', () {
expect(_contact(type: advTypeRepeater).typeLabel(l10n), 'Repeater');
});
test('room', () {
expect(_contact(type: advTypeRoom).typeLabel(l10n), 'Room');
});
test('sensor', () {
expect(_contact(type: advTypeSensor).typeLabel(l10n), 'Sensor');
});
test('unknown type falls back', () {
expect(_contact(type: 99).typeLabel(l10n), 'Unknown');
});
});
group('Contact.pathLabel (override)', () {
test('override < 0 -> flood (forced)', () {
expect(_contact(pathOverride: -1).pathLabel(l10n), 'Flood (forced)');
});
test('override == 0 -> direct (forced)', () {
expect(_contact(pathOverride: 0).pathLabel(l10n), 'Direct (forced)');
});
test('override > 0 -> hops (forced)', () {
expect(_contact(pathOverride: 3).pathLabel(l10n), '3 hops (forced)');
});
test('override takes precedence over pathLength', () {
expect(
_contact(pathLength: 5, pathOverride: -1).pathLabel(l10n),
'Flood (forced)',
);
});
});
group('Contact.pathLabel (auto)', () {
test('pathLength < 0 -> flood', () {
expect(_contact(pathLength: -1).pathLabel(l10n), 'Flood');
});
test('pathLength == 0 -> direct', () {
expect(_contact(pathLength: 0).pathLabel(l10n), 'Direct');
});
test('pathLength == 1 -> singular hop', () {
expect(_contact(pathLength: 1).pathLabel(l10n), '1 hop');
});
test('pathLength > 1 -> plural hops', () {
expect(_contact(pathLength: 4).pathLabel(l10n), '4 hops');
});
});
}
@@ -23,6 +23,7 @@ void main() {
expect(result.isClear, isTrue); expect(result.isClear, isTrue);
expect(result.maxObstructionMeters, equals(0)); expect(result.maxObstructionMeters, equals(0));
expect(result.firstObstructionDistanceMeters, isNull); expect(result.firstObstructionDistanceMeters, isNull);
expect(result.obstructions, isEmpty);
}); });
test( test(
@@ -44,9 +45,32 @@ void main() {
expect(result.isClear, isFalse); expect(result.isClear, isFalse);
expect(result.maxObstructionMeters, greaterThan(0)); expect(result.maxObstructionMeters, greaterThan(0));
expect(result.firstObstructionDistanceMeters, isNotNull); expect(result.firstObstructionDistanceMeters, isNotNull);
expect(result.obstructions, hasLength(1));
expect(result.obstructions.single.sampleIndex, equals(10));
expect(result.obstructions.single.point, equals(points[10]));
}, },
); );
test('computeFromElevations groups contiguous blocked samples', () {
final points = makePoints(21);
final elevations = List<double>.filled(points.length, 100);
elevations[9] = 220;
elevations[10] = 320;
elevations[11] = 240;
final result = LineOfSightService.computeFromElevations(
points: points,
elevations: elevations,
startAntennaHeightMeters: 1.5,
endAntennaHeightMeters: 1.5,
kFactor: 4.0 / 3.0,
);
expect(result.obstructions, hasLength(1));
expect(result.obstructions.single.sampleIndex, equals(10));
expect(result.obstructions.single.obstructionMeters, greaterThan(0));
});
test('analyzePath summarizes clear and blocked segments', () async { test('analyzePath summarizes clear and blocked segments', () async {
final service = LineOfSightService( final service = LineOfSightService(
elevationDataSource: (points) async { elevationDataSource: (points) async {
+120 -1
View File
@@ -1 +1,120 @@
{} {
"bg": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"de": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"es": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"fr": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"hu": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"it": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"ja": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"ko": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"nl": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"pl": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"pt": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"ru": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"sk": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"sl": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"sv": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"uk": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
],
"zh": [
"settings_companionDebugLog",
"settings_companionDebugLogSubtitle",
"chat_markAsUnread",
"chat_newMessages"
]
}
-1
View File
@@ -11,7 +11,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
flserial flserial
flutter_local_notifications_windows flutter_local_notifications_windows
jni
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)