Compare commits

..

2 Commits

Author SHA1 Message Date
zjs81 9957e1fc1f Merge pull request #474 from zjs81/dev
Merge Dev into main
2026-06-13 02:14:19 -07:00
zjs81 880df84828 Merge pull request #473 from zjs81/dev
Merge Dev into main
2026-06-13 00:42:09 -07:00
42 changed files with 268 additions and 2751 deletions
-3
View File
@@ -120,8 +120,6 @@ On unexpected disconnection, auto-reconnect with exponential backoff:
| 40 | CMD_GET_CUSTOM_VAR | Get custom variables | | 40 | CMD_GET_CUSTOM_VAR | Get custom variables |
| 41 | CMD_SET_CUSTOM_VAR | Set a custom variable | | 41 | CMD_SET_CUSTOM_VAR | Set a custom variable |
| 50 | CMD_SEND_BINARY_REQ | Send binary request | | 50 | CMD_SEND_BINARY_REQ | Send binary request |
| 54 | CMD_SET_FLOOD_SCOPE | Set flood routing scope (v8+) |
| 55 | CMD_SEND_CONTROL_DATA | Send control data (e.g. zero-hop discovery, v8+) |
| 56 | CMD_GET_STATS | Request companion radio stats | | 56 | CMD_GET_STATS | Request companion radio stats |
| 57 | CMD_SEND_ANON_REQ | Send anonymous request | | 57 | CMD_SEND_ANON_REQ | Send anonymous request |
| 58 | CMD_SET_AUTO_ADD_CONFIG | Set auto-add configuration | | 58 | CMD_SET_AUTO_ADD_CONFIG | Set auto-add configuration |
@@ -164,7 +162,6 @@ On unexpected disconnection, auto-reconnect with exponential backoff:
| 0x8A | PUSH_CODE_NEW_ADVERT | New node discovered | | 0x8A | PUSH_CODE_NEW_ADVERT | New node discovered |
| 0x8B | PUSH_CODE_TELEMETRY_RESPONSE | Sensor telemetry data | | 0x8B | PUSH_CODE_TELEMETRY_RESPONSE | Sensor telemetry data |
| 0x8C | PUSH_CODE_BINARY_RESPONSE | Binary data response | | 0x8C | PUSH_CODE_BINARY_RESPONSE | Binary data response |
| 0x8E | PUSH_CODE_CONTROL_DATA | Control data push (e.g. zero-hop discovery response) |
## Data Models ## Data Models
+2 -120
View File
@@ -3,7 +3,6 @@ import 'dart:convert';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:crypto/crypto.dart' as crypto; import 'package:crypto/crypto.dart' as crypto;
import 'package:meshcore_open/storage/region_store.dart';
import 'package:pointycastle/export.dart'; import 'package:pointycastle/export.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart';
@@ -36,7 +35,6 @@ import 'meshcore_connector_tcp.dart';
import '../storage/channel_message_store.dart'; import '../storage/channel_message_store.dart';
import '../storage/channel_order_store.dart'; import '../storage/channel_order_store.dart';
import '../storage/channel_settings_store.dart'; import '../storage/channel_settings_store.dart';
import '../storage/channel_region_store.dart';
import '../storage/channel_store.dart'; import '../storage/channel_store.dart';
import '../storage/contact_discovery_store.dart'; import '../storage/contact_discovery_store.dart';
import '../storage/contact_settings_store.dart'; import '../storage/contact_settings_store.dart';
@@ -268,10 +266,6 @@ class MeshCoreConnector extends ChangeNotifier {
// Serializes path operations (setContactPath/clearContactPath) to prevent // Serializes path operations (setContactPath/clearContactPath) to prevent
// interleaved async calls from leaving in-memory state inconsistent with device. // interleaved async calls from leaving in-memory state inconsistent with device.
Future<void> _pathOpLock = Future.value(); Future<void> _pathOpLock = Future.value();
// Flood scope is a global firmware setting, so scoped channel sends must not
// overlap or a message may inherit another channel's region.
Future<void> _channelScopedSendLock = Future.value();
static const Duration _commandAckTimeout = Duration(seconds: 5);
Map<String, String>? _currentCustomVars; Map<String, String>? _currentCustomVars;
/// Maps repeater pubkey-prefix hex (12 hex chars = first 6 bytes) → the /// Maps repeater pubkey-prefix hex (12 hex chars = first 6 bytes) → the
@@ -303,7 +297,6 @@ class MeshCoreConnector extends ChangeNotifier {
final MessageStore _messageStore = MessageStore(); final MessageStore _messageStore = MessageStore();
final ChannelOrderStore _channelOrderStore = ChannelOrderStore(); final ChannelOrderStore _channelOrderStore = ChannelOrderStore();
final ChannelSettingsStore _channelSettingsStore = ChannelSettingsStore(); final ChannelSettingsStore _channelSettingsStore = ChannelSettingsStore();
final ChannelRegionStore _channelRegionStore = ChannelRegionStore();
final ContactSettingsStore _contactSettingsStore = ContactSettingsStore(); final ContactSettingsStore _contactSettingsStore = ContactSettingsStore();
final ContactStore _contactStore = ContactStore(); final ContactStore _contactStore = ContactStore();
final ContactDiscoveryStore _discoveryContactStore = ContactDiscoveryStore(); final ContactDiscoveryStore _discoveryContactStore = ContactDiscoveryStore();
@@ -313,7 +306,6 @@ class MeshCoreConnector extends ChangeNotifier {
final Map<int, bool> _channelSmazEnabled = {}; final Map<int, bool> _channelSmazEnabled = {};
final Map<int, bool> _channelCyr2LatEnabled = {}; final Map<int, bool> _channelCyr2LatEnabled = {};
final Map<int, String?> _channelCyr2LatProfileId = {}; final Map<int, String?> _channelCyr2LatProfileId = {};
final Map<int, Region> _channelRegions = {};
bool _lastSentWasCliCommand = bool _lastSentWasCliCommand =
false; // Track if last sent message was a CLI command false; // Track if last sent message was a CLI command
final Map<String, bool> _contactSmazEnabled = {}; final Map<String, bool> _contactSmazEnabled = {};
@@ -712,14 +704,6 @@ class MeshCoreConnector extends ChangeNotifier {
return _contactSmazEnabled[contactKeyHex] ?? false; return _contactSmazEnabled[contactKeyHex] ?? false;
} }
bool hasChannelRegion(int channelIndex) {
return (_channelRegions[channelIndex] ?? '').isNotEmpty;
}
Region getChannelRegion(int channelIndex) {
return _channelRegions[channelIndex] ?? '';
}
void ensureContactSmazSettingLoaded(String contactKeyHex) { void ensureContactSmazSettingLoaded(String contactKeyHex) {
_ensureContactSmazSettingLoaded(contactKeyHex); _ensureContactSmazSettingLoaded(contactKeyHex);
} }
@@ -873,14 +857,6 @@ class MeshCoreConnector extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<void> setChannelRegion(int channelIndex, String region) async {
// Update in-memory state and notify synchronously so the UI reflects the
// change immediately; persistence happens in the background.
_channelRegions[channelIndex] = region;
notifyListeners();
await _channelRegionStore.saveRegion(channelIndex, region);
}
Future<void> _loadChannelOrder() async { Future<void> _loadChannelOrder() async {
_channelOrder = await _channelOrderStore.loadChannelOrder(); _channelOrder = await _channelOrderStore.loadChannelOrder();
_applyChannelOrder(); _applyChannelOrder();
@@ -1039,13 +1015,11 @@ class MeshCoreConnector extends ChangeNotifier {
Future<void> loadChannelSettings({int? maxChannels}) async { Future<void> loadChannelSettings({int? maxChannels}) async {
_channelSmazEnabled.clear(); _channelSmazEnabled.clear();
_channelCyr2LatEnabled.clear(); _channelCyr2LatEnabled.clear();
_channelRegions.clear();
final channelCount = maxChannels ?? _maxChannels; final channelCount = maxChannels ?? _maxChannels;
for (int i = 0; i < channelCount; i++) { for (int i = 0; i < channelCount; i++) {
_channelSmazEnabled[i] = await _channelSettingsStore.loadSmazEnabled(i); _channelSmazEnabled[i] = await _channelSettingsStore.loadSmazEnabled(i);
_channelCyr2LatEnabled[i] = await _channelSettingsStore _channelCyr2LatEnabled[i] = await _channelSettingsStore
.loadCyr2LatEnabled(i); .loadCyr2LatEnabled(i);
_channelRegions[i] = await _channelRegionStore.loadRegion(i);
} }
} }
@@ -3409,15 +3383,12 @@ class MeshCoreConnector extends ChangeNotifier {
// Send the reaction to the device (don't add as a visible message) // Send the reaction to the device (don't add as a visible message)
final reactionQueueId = _nextReactionSendQueueId(); final reactionQueueId = _nextReactionSendQueueId();
_pendingChannelSentQueue.add(reactionQueueId); _pendingChannelSentQueue.add(reactionQueueId);
await _runScopedChannelSend(() async {
await _waitForRadioQuiet(lastInboundRxTime: _lastChannelMsgRxTime); await _waitForRadioQuiet(lastInboundRxTime: _lastChannelMsgRxTime);
await _sendFrameAndWaitForCommandAck( await sendFrame(
buildSendChannelTextMsgFrame(channel.index, text), buildSendChannelTextMsgFrame(channel.index, text),
channelSendQueueId: reactionQueueId, channelSendQueueId: reactionQueueId,
expectsGenericAck: true, expectsGenericAck: true,
successCode: respCodeSent,
); );
}, region: getChannelRegion(channel.index));
return; return;
} }
@@ -3434,97 +3405,12 @@ class MeshCoreConnector extends ChangeNotifier {
notifyListeners(); notifyListeners();
final outboundText = prepareChannelOutboundText(channel.index, text); final outboundText = prepareChannelOutboundText(channel.index, text);
await _runScopedChannelSend(() async {
await _waitForRadioQuiet(lastInboundRxTime: _lastChannelMsgRxTime); await _waitForRadioQuiet(lastInboundRxTime: _lastChannelMsgRxTime);
await _sendFrameAndWaitForCommandAck( await sendFrame(
buildSendChannelTextMsgFrame(channel.index, outboundText), buildSendChannelTextMsgFrame(channel.index, outboundText),
channelSendQueueId: message.messageId, channelSendQueueId: message.messageId,
expectsGenericAck: true, expectsGenericAck: true,
successCode: respCodeSent,
); );
}, region: getChannelRegion(channel.index));
}
Future<void> _runScopedChannelSend(
Future<void> Function() action, {
required String region,
}) async {
final prev = _channelScopedSendLock;
final completer = Completer<void>();
_channelScopedSendLock = completer.future;
await prev;
try {
// Only touch the global flood scope for region-scoped channels. Plain
// channels send exactly as before, which also stays compatible with
// firmware that predates CMD_SET_FLOOD_SCOPE. The lock is still held so an
// unscoped send can't interleave with (and inherit the scope of) a
// concurrent scoped send.
if (region.isEmpty) {
await action();
return;
}
await _sendFrameAndWaitForCommandAck(buildSetFloodScopeFrame(region));
try {
await action();
} finally {
if (isConnected) {
await _sendFrameAndWaitForCommandAck(buildSetFloodScopeFrame(''));
}
}
} finally {
completer.complete();
}
}
// Sends [data] and resolves once the device replies. [successCode] is the
// response code that signals success for this frame: SET_FLOOD_SCOPE replies
// with RESP_CODE_OK, whereas a channel text send replies with RESP_CODE_SENT.
// Waiting for the text send's RESP_CODE_SENT before the scope is reset
// guarantees the firmware has already built the packet with the active scope.
Future<void> _sendFrameAndWaitForCommandAck(
Uint8List data, {
String? channelSendQueueId,
bool expectsGenericAck = false,
int successCode = respCodeOk,
}) async {
final completer = Completer<void>();
late final StreamSubscription<Uint8List> subscription;
late final Timer timeout;
void complete() {
if (!completer.isCompleted) completer.complete();
}
void completeError(Object error) {
if (!completer.isCompleted) completer.completeError(error);
}
subscription = receivedFrames.listen((frame) {
if (frame.isEmpty) return;
if (frame[0] == successCode) {
complete();
} else if (frame[0] == respCodeErr) {
final errCode = frame.length > 1 ? frame[1] : -1;
completeError(Exception('Command failed with error code $errCode'));
}
});
timeout = Timer(_commandAckTimeout, () {
completeError(TimeoutException('Command ACK timed out'));
});
try {
await sendFrame(
data,
channelSendQueueId: channelSendQueueId,
expectsGenericAck: expectsGenericAck,
);
await completer.future;
} finally {
timeout.cancel();
await subscription.cancel();
}
} }
Future<void> removeContact(Contact contact) async { Future<void> removeContact(Contact contact) async {
@@ -4131,9 +4017,6 @@ class MeshCoreConnector extends ChangeNotifier {
case pushCodePathUpdated: case pushCodePathUpdated:
_handlePathUpdated(frame); _handlePathUpdated(frame);
break; break;
case pushCodeControlData:
// Optional feature-specific services listen to receivedFrames directly.
break;
case pushCodeLoginSuccess: case pushCodeLoginSuccess:
_handleLoginSuccess(frame); _handleLoginSuccess(frame);
break; break;
@@ -4280,7 +4163,6 @@ class MeshCoreConnector extends ChangeNotifier {
_messageStore.setPublicKeyHex = selfPublicKeyHex; _messageStore.setPublicKeyHex = selfPublicKeyHex;
_channelOrderStore.setPublicKeyHex = selfPublicKeyHex; _channelOrderStore.setPublicKeyHex = selfPublicKeyHex;
_channelSettingsStore.setPublicKeyHex = selfPublicKeyHex; _channelSettingsStore.setPublicKeyHex = selfPublicKeyHex;
_channelRegionStore.setPublicKeyHex = selfPublicKeyHex;
_contactSettingsStore.setPublicKeyHex = selfPublicKeyHex; _contactSettingsStore.setPublicKeyHex = selfPublicKeyHex;
_contactStore.setPublicKeyHex = selfPublicKeyHex; _contactStore.setPublicKeyHex = selfPublicKeyHex;
_channelStore.setPublicKeyHex = selfPublicKeyHex; _channelStore.setPublicKeyHex = selfPublicKeyHex;
-86
View File
@@ -1,7 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:crypto/crypto.dart' as crypto;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
// Buffer Reader - sequential binary data reader with pointer tracking // Buffer Reader - sequential binary data reader with pointer tracking
@@ -207,8 +206,6 @@ const int cmdSendTelemetryReq = 39;
const int cmdGetCustomVar = 40; const int cmdGetCustomVar = 40;
const int cmdSetCustomVar = 41; const int cmdSetCustomVar = 41;
const int cmdSendBinaryReq = 50; const int cmdSendBinaryReq = 50;
const int cmdSetFloodScope = 54;
const int cmdSendControlData = 55;
const int cmdGetStats = 56; const int cmdGetStats = 56;
const int cmdSendAnonReq = 57; const int cmdSendAnonReq = 57;
const int cmdSetAutoAddConfig = 58; const int cmdSetAutoAddConfig = 58;
@@ -233,12 +230,6 @@ Uint8List buildTelemetryBinaryPayload() {
return Uint8List.fromList([reqTypeGetTelemetry, 0x00, 0x00, 0x00, 0x00]); return Uint8List.fromList([reqTypeGetTelemetry, 0x00, 0x00, 0x00, 0x00]);
} }
const int anonReqTypeRegions = 0x01;
// Control data sub-types used by MeshCore discovery packets.
const int controlSubtypeDiscoverReq = 0x08;
const int controlSubtypeDiscoverResp = 0x09;
// Repeater response codes // Repeater response codes
const int respServerLoginOk = 0; const int respServerLoginOk = 0;
@@ -281,7 +272,6 @@ const int pushCodeTraceData = 0x89;
const int pushCodeNewAdvert = 0x8A; const int pushCodeNewAdvert = 0x8A;
const int pushCodeTelemetryResponse = 0x8B; const int pushCodeTelemetryResponse = 0x8B;
const int pushCodeBinaryResponse = 0x8C; const int pushCodeBinaryResponse = 0x8C;
const int pushCodeControlData = 0x8E;
// Contact/advertisement types // Contact/advertisement types
const int advTypeChat = 1; const int advTypeChat = 1;
@@ -876,67 +866,6 @@ Uint8List buildSendBinaryReq(Uint8List repeaterPubKey, {Uint8List? payload}) {
return writer.toBytes(); return writer.toBytes();
} }
Uint8List buildSendControlDataFrame(Uint8List payload) {
final writer = BufferWriter();
writer.writeByte(cmdSendControlData);
writer.writeBytes(payload);
return writer.toBytes();
}
Uint8List buildDiscoveryRequestPayload(
int tag, {
bool prefixOnly = false,
int typeMask = 1 << advTypeRepeater,
}) {
final writer = BufferWriter();
// The high bit must be set for CMD_SEND_CONTROL_DATA; DISCOVER_REQ uses
// subtype 0x8, with the low bit selecting short/full public keys in replies.
writer.writeByte(
(controlSubtypeDiscoverReq << 4) | (prefixOnly ? 0x01 : 0x00),
);
writer.writeByte(typeMask);
writer.writeUInt32LE(tag);
writer.writeUInt32LE(0); // since=0 asks nearby nodes for any recent advert.
return writer.toBytes();
}
Uint8List _reversePathByHop(Uint8List path, int pathHashWidth) {
if (path.isEmpty) return Uint8List(0);
final width = pathHashWidth.clamp(1, 4).toInt();
if (path.length % width != 0) {
return Uint8List.fromList(path.reversed.toList());
}
final reversed = Uint8List(path.length);
final hops = path.length ~/ width;
for (var i = 0; i < hops; i++) {
final from = (hops - 1 - i) * width;
reversed.setRange(i * width, (i + 1) * width, path, from);
}
return reversed;
}
// Build CMD_SEND_ANON_REQ frame.
// Payload format for regions: [anon_req_type][reply_path_len][reply_path...].
Uint8List buildSendAnonReqFrame(
Uint8List repeaterPubKey, {
required int requestType,
Uint8List? replyPath,
int replyHopCount = 0,
int pathHashWidth = pathHashSize,
}) {
final width = pathHashWidth.clamp(1, 4).toInt();
final path = replyPath ?? Uint8List(0);
final encodedPathLen = ((width - 1) << 6) | (replyHopCount & 0x3F);
final writer = BufferWriter();
writer.writeByte(cmdSendAnonReq);
writer.writeBytes(repeaterPubKey);
writer.writeByte(requestType);
writer.writeByte(encodedPathLen);
writer.writeBytes(_reversePathByHop(path, width));
return writer.toBytes();
}
//Build a trace request frame //Build a trace request frame
//[cmd][tag x4][auth x4][flag][payload] //[cmd][tag x4][auth x4][flag][payload]
Uint8List buildTraceReq(int tag, int auth, int flag, {Uint8List? payload}) { Uint8List buildTraceReq(int tag, int auth, int flag, {Uint8List? payload}) {
@@ -1031,18 +960,3 @@ Uint8List buildSendTelemetryReq(Uint8List? pubKey) {
} }
return writer.toBytes(); return writer.toBytes();
} }
//Build CMD_SET_FLOOD_SCOPE
// Format: [cmd][scope]
Uint8List buildSetFloodScopeFrame(String region) {
if (region == '') {
// reset scope
return Uint8List.fromList([cmdSetFloodScope, 0]);
}
final name = region.startsWith('#') ? region : '#$region';
final hash = crypto.sha256.convert(utf8.encode(name)).bytes;
final scope = Uint8List.fromList(hash.sublist(0, 16));
return Uint8List.fromList([cmdSetFloodScope, 0, ...scope]);
}
+1 -11
View File
@@ -1,4 +1,3 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@@ -21,7 +20,6 @@ class LinkHandler {
required String text, required String text,
required TextStyle style, required TextStyle style,
TextStyle? linkStyle, TextStyle? linkStyle,
VoidCallback? onSecondaryTap,
}) { }) {
final effectiveLinkStyle = linkStyle ?? defaultLinkStyle(context, style); final effectiveLinkStyle = linkStyle ?? defaultLinkStyle(context, style);
const options = LinkifyOptions(humanize: false, defaultToHttps: false); const options = LinkifyOptions(humanize: false, defaultToHttps: false);
@@ -29,7 +27,7 @@ class LinkHandler {
void onOpen(LinkableElement link) => handleLinkTap(context, link.url); void onOpen(LinkableElement link) => handleLinkTap(context, link.url);
if (PlatformInfo.isDesktop) { if (PlatformInfo.isDesktop) {
final linkify = SelectableLinkify( return SelectableLinkify(
text: text, text: text,
style: style, style: style,
linkStyle: effectiveLinkStyle, linkStyle: effectiveLinkStyle,
@@ -37,14 +35,6 @@ class LinkHandler {
linkifiers: linkifiers, linkifiers: linkifiers,
onOpen: onOpen, onOpen: onOpen,
); );
if (onSecondaryTap == null) return linkify;
return Listener(
onPointerDown: (event) {
if (event.buttons & kSecondaryMouseButton != 0) onSecondaryTap();
},
behavior: HitTestBehavior.translucent,
child: linkify,
);
} }
return Linkify( return Linkify(
text: text, text: text,
-28
View File
@@ -2488,13 +2488,6 @@
"settings_companionDebugLog": "Debug-Protokoll für die Begleitsoftware", "settings_companionDebugLog": "Debug-Protokoll für die Begleitsoftware",
"settings_companionDebugLogSubtitle": "BLE/TCP/USB-Befehle, Antworten und Rohdaten", "settings_companionDebugLogSubtitle": "BLE/TCP/USB-Befehle, Antworten und Rohdaten",
"repeater_chanUtil": "Nutzung des Kanals", "repeater_chanUtil": "Nutzung des Kanals",
"@settings_deleteRegionConfirm": {
"placeholders": {
"region": {
"type": "String"
}
}
},
"@routing_lastWorked": { "@routing_lastWorked": {
"placeholders": { "placeholders": {
"when": { "when": {
@@ -2502,27 +2495,6 @@
} }
} }
}, },
"@channels_regionSetTo": {
"placeholders": {
"region": {
"type": "String",
"example": "de-mitte"
}
}
},
"settings_regionSettings": "Regionen",
"settings_regionSettingsSubtitle": "Gespeicherte Regionen verwalten",
"settings_regionManagement_screenTitle": "Regions-Verwaltung",
"settings_regionNameHint": "Regions-Namen eingeben",
"settings_regionAddRegion": "Region hinzufügen",
"settings_regionDeleted": "Region entfernt",
"settings_regionName": "Regions-Name",
"settings_deleteRegion": "Region entfernen",
"settings_deleteRegionConfirm": "Region \"{region}\" aus der Liste entfernen?",
"channels_regionNotSet": "Region: keine",
"channels_regionSetTo": "Region: {region}",
"channels_regionSelect_Title": "Region auswählen",
"channels_clearRegion": "Region zurücksetzen",
"@routing_deliveryCounts": { "@routing_deliveryCounts": {
"placeholders": { "placeholders": {
"successes": { "successes": {
-31
View File
@@ -158,25 +158,6 @@
"settings_radioSettings": "Radio Settings", "settings_radioSettings": "Radio Settings",
"settings_radioSettingsSubtitle": "Frequency, power, spreading factor", "settings_radioSettingsSubtitle": "Frequency, power, spreading factor",
"settings_radioSettingsUpdated": "Radio settings updated", "settings_radioSettingsUpdated": "Radio settings updated",
"settings_regionSettings": "Regions",
"settings_regionSettingsSubtitle": "Manage stored regions",
"settings_regionManagement_screenTitle": "Region Management",
"settings_regionNameHint": "Enter region name",
"settings_regionAddRegion": "Add region",
"settings_regionFetchRegions": "Fetch regions from repeaters",
"settings_regionFetchRegionsFail": "No regions were found",
"settings_regionFetchRegionsAlreadyExists": "This region has already been added",
"settings_regionName": "Region Name",
"settings_regionDeleted": "Region deleted",
"settings_deleteRegion": "Delete Region",
"settings_deleteRegionConfirm": "Remove \"{region}\" from region list?",
"@settings_deleteRegionConfirm": {
"placeholders": {
"region": {
"type": "String"
}
}
},
"settings_location": "Location", "settings_location": "Location",
"settings_locationSubtitle": "GPS coordinates", "settings_locationSubtitle": "GPS coordinates",
"settings_locationUpdated": "Location and GPS settings updated", "settings_locationUpdated": "Location and GPS settings updated",
@@ -654,18 +635,6 @@
"channels_scanQrCodeComingSoon": "Coming soon", "channels_scanQrCodeComingSoon": "Coming soon",
"channels_enterHashtag": "Enter hashtag", "channels_enterHashtag": "Enter hashtag",
"channels_hashtagHint": "e.g. #team", "channels_hashtagHint": "e.g. #team",
"channels_regionSetTo": "Region: {region}",
"@channels_regionSetTo": {
"placeholders": {
"region": {
"type": "String",
"example": "de-mitte"
}
}
},
"channels_regionNotSet": "Region: none",
"channels_regionSelect_Title": "Select a region",
"channels_clearRegion": "Clear region",
"chat_noMessages": "No messages yet", "chat_noMessages": "No messages yet",
"chat_sendMessage": "Send message", "chat_sendMessage": "Send message",
"chat_sendMessageTo": "Send a message to {contactName}", "chat_sendMessageTo": "Send a message to {contactName}",
-96
View File
@@ -784,78 +784,6 @@ abstract class AppLocalizations {
/// **'Radio settings updated'** /// **'Radio settings updated'**
String get settings_radioSettingsUpdated; String get settings_radioSettingsUpdated;
/// No description provided for @settings_regionSettings.
///
/// In en, this message translates to:
/// **'Regions'**
String get settings_regionSettings;
/// No description provided for @settings_regionSettingsSubtitle.
///
/// In en, this message translates to:
/// **'Manage stored regions'**
String get settings_regionSettingsSubtitle;
/// No description provided for @settings_regionManagement_screenTitle.
///
/// In en, this message translates to:
/// **'Region Management'**
String get settings_regionManagement_screenTitle;
/// No description provided for @settings_regionNameHint.
///
/// In en, this message translates to:
/// **'Enter region name'**
String get settings_regionNameHint;
/// No description provided for @settings_regionAddRegion.
///
/// In en, this message translates to:
/// **'Add region'**
String get settings_regionAddRegion;
/// No description provided for @settings_regionFetchRegions.
///
/// In en, this message translates to:
/// **'Fetch regions from repeaters'**
String get settings_regionFetchRegions;
/// No description provided for @settings_regionFetchRegionsFail.
///
/// In en, this message translates to:
/// **'No regions were found'**
String get settings_regionFetchRegionsFail;
/// No description provided for @settings_regionFetchRegionsAlreadyExists.
///
/// In en, this message translates to:
/// **'This region has already been added'**
String get settings_regionFetchRegionsAlreadyExists;
/// No description provided for @settings_regionName.
///
/// In en, this message translates to:
/// **'Region Name'**
String get settings_regionName;
/// No description provided for @settings_regionDeleted.
///
/// In en, this message translates to:
/// **'Region deleted'**
String get settings_regionDeleted;
/// No description provided for @settings_deleteRegion.
///
/// In en, this message translates to:
/// **'Delete Region'**
String get settings_deleteRegion;
/// No description provided for @settings_deleteRegionConfirm.
///
/// In en, this message translates to:
/// **'Remove \"{region}\" from region list?'**
String settings_deleteRegionConfirm(String region);
/// No description provided for @settings_location. /// No description provided for @settings_location.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -2530,30 +2458,6 @@ abstract class AppLocalizations {
/// **'e.g. #team'** /// **'e.g. #team'**
String get channels_hashtagHint; String get channels_hashtagHint;
/// No description provided for @channels_regionSetTo.
///
/// In en, this message translates to:
/// **'Region: {region}'**
String channels_regionSetTo(String region);
/// No description provided for @channels_regionNotSet.
///
/// In en, this message translates to:
/// **'Region: none'**
String get channels_regionNotSet;
/// No description provided for @channels_regionSelect_Title.
///
/// In en, this message translates to:
/// **'Select a region'**
String get channels_regionSelect_Title;
/// No description provided for @channels_clearRegion.
///
/// In en, this message translates to:
/// **'Clear region'**
String get channels_clearRegion;
/// No description provided for @chat_noMessages. /// No description provided for @chat_noMessages.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
-53
View File
@@ -367,45 +367,6 @@ class AppLocalizationsBg extends AppLocalizations {
String get settings_radioSettingsUpdated => String get settings_radioSettingsUpdated =>
'Радио настройките са актуализирани'; 'Радио настройките са актуализирани';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Местоположение'; String get settings_location => 'Местоположение';
@@ -1369,20 +1330,6 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'напр. #отбор'; String get channels_hashtagHint => 'напр. #отбор';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Няма съобщения.'; String get chat_noMessages => 'Няма съобщения.';
-54
View File
@@ -369,46 +369,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => 'Funkparameter aktualisiert'; String get settings_radioSettingsUpdated => 'Funkparameter aktualisiert';
@override
String get settings_regionSettings => 'Regionen';
@override
String get settings_regionSettingsSubtitle =>
'Gespeicherte Regionen verwalten';
@override
String get settings_regionManagement_screenTitle => 'Regions-Verwaltung';
@override
String get settings_regionNameHint => 'Regions-Namen eingeben';
@override
String get settings_regionAddRegion => 'Region hinzufügen';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Regions-Name';
@override
String get settings_regionDeleted => 'Region entfernt';
@override
String get settings_deleteRegion => 'Region entfernen';
@override
String settings_deleteRegionConfirm(String region) {
return 'Region \"$region\" aus der Liste entfernen?';
}
@override @override
String get settings_location => 'Ort'; String get settings_location => 'Ort';
@@ -1369,20 +1329,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'z.B. #team'; String get channels_hashtagHint => 'z.B. #team';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: keine';
@override
String get channels_regionSelect_Title => 'Region auswählen';
@override
String get channels_clearRegion => 'Region zurücksetzen';
@override @override
String get chat_noMessages => 'Noch keine Nachrichten.'; String get chat_noMessages => 'Noch keine Nachrichten.';
-53
View File
@@ -363,45 +363,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => 'Radio settings updated'; String get settings_radioSettingsUpdated => 'Radio settings updated';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Location'; String get settings_location => 'Location';
@@ -1339,20 +1300,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'e.g. #team'; String get channels_hashtagHint => 'e.g. #team';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'No messages yet'; String get chat_noMessages => 'No messages yet';
-53
View File
@@ -367,45 +367,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => 'Ajustes de radio actualizados'; String get settings_radioSettingsUpdated => 'Ajustes de radio actualizados';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Ubicación'; String get settings_location => 'Ubicación';
@@ -1366,20 +1327,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'ej. #equipo'; String get channels_hashtagHint => 'ej. #equipo';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Aún no hay mensajes'; String get chat_noMessages => 'Aún no hay mensajes';
-53
View File
@@ -369,45 +369,6 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => 'Paramètres radio mis à jour'; String get settings_radioSettingsUpdated => 'Paramètres radio mis à jour';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Emplacement'; String get settings_location => 'Emplacement';
@@ -1368,20 +1329,6 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'ex. #equipe'; String get channels_hashtagHint => 'ex. #equipe';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Aucun message pour le moment'; String get chat_noMessages => 'Aucun message pour le moment';
-53
View File
@@ -365,45 +365,6 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => 'A rádió beállításai frissítve'; String get settings_radioSettingsUpdated => 'A rádió beállításai frissítve';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'hely'; String get settings_location => 'hely';
@@ -1361,20 +1322,6 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'például #csapat'; String get channels_hashtagHint => 'például #csapat';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Még nincsenek üzenetek'; String get chat_noMessages => 'Még nincsenek üzenetek';
-53
View File
@@ -369,45 +369,6 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => 'Impostazioni radio aggiornate'; String get settings_radioSettingsUpdated => 'Impostazioni radio aggiornate';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Posizione'; String get settings_location => 'Posizione';
@@ -1367,20 +1328,6 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'es. #team'; String get channels_hashtagHint => 'es. #team';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Nessun messaggio ancora'; String get chat_noMessages => 'Nessun messaggio ancora';
-53
View File
@@ -353,45 +353,6 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => 'ラジオの設定が更新されました'; String get settings_radioSettingsUpdated => 'ラジオの設定が更新されました';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => '場所'; String get settings_location => '場所';
@@ -1298,20 +1259,6 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get channels_hashtagHint => '例:#チーム'; String get channels_hashtagHint => '例:#チーム';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'まだメッセージは届いていません'; String get chat_noMessages => 'まだメッセージは届いていません';
-53
View File
@@ -353,45 +353,6 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => '라디오 설정이 업데이트되었습니다.'; String get settings_radioSettingsUpdated => '라디오 설정이 업데이트되었습니다.';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => '위치'; String get settings_location => '위치';
@@ -1300,20 +1261,6 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get channels_hashtagHint => '예: #팀'; String get channels_hashtagHint => '예: #팀';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => '아직 메시지가 없습니다.'; String get chat_noMessages => '아직 메시지가 없습니다.';
-53
View File
@@ -365,45 +365,6 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => 'Radio instellingen bijgewerkt'; String get settings_radioSettingsUpdated => 'Radio instellingen bijgewerkt';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Locatie'; String get settings_location => 'Locatie';
@@ -1355,20 +1316,6 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'bijv. #team'; String get channels_hashtagHint => 'bijv. #team';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Nog geen berichten.'; String get chat_noMessages => 'Nog geen berichten.';
-53
View File
@@ -371,45 +371,6 @@ class AppLocalizationsPl extends AppLocalizations {
String get settings_radioSettingsUpdated => String get settings_radioSettingsUpdated =>
'Ustawienia radia zostały zaktualizowane'; 'Ustawienia radia zostały zaktualizowane';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Lokalizacja'; String get settings_location => 'Lokalizacja';
@@ -1377,20 +1338,6 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'np. #zespół'; String get channels_hashtagHint => 'np. #zespół';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Brak jeszcze wiadomości'; String get chat_noMessages => 'Brak jeszcze wiadomości';
-53
View File
@@ -369,45 +369,6 @@ class AppLocalizationsPt extends AppLocalizations {
String get settings_radioSettingsUpdated => String get settings_radioSettingsUpdated =>
'Configurações de rádio atualizadas'; 'Configurações de rádio atualizadas';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Localização'; String get settings_location => 'Localização';
@@ -1365,20 +1326,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'ex. #equipe'; String get channels_hashtagHint => 'ex. #equipe';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Ainda não existem mensagens.'; String get chat_noMessages => 'Ainda não existem mensagens.';
-53
View File
@@ -368,45 +368,6 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => 'Настройки радио обновлены'; String get settings_radioSettingsUpdated => 'Настройки радио обновлены';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Позиция'; String get settings_location => 'Позиция';
@@ -1366,20 +1327,6 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'например, #команда'; String get channels_hashtagHint => 'например, #команда';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Сообщений пока нет'; String get chat_noMessages => 'Сообщений пока нет';
-53
View File
@@ -367,45 +367,6 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => 'Nastavenia rádia aktualizované'; String get settings_radioSettingsUpdated => 'Nastavenia rádia aktualizované';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Lokalita'; String get settings_location => 'Lokalita';
@@ -1355,20 +1316,6 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'napr. #tím'; String get channels_hashtagHint => 'napr. #tím';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Zatiaľ žiadne správy.'; String get chat_noMessages => 'Zatiaľ žiadne správy.';
-53
View File
@@ -366,45 +366,6 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => 'Radio nastavitve posodobljene'; String get settings_radioSettingsUpdated => 'Radio nastavitve posodobljene';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Lokacija'; String get settings_location => 'Lokacija';
@@ -1353,20 +1314,6 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'npr. #ekipa'; String get channels_hashtagHint => 'npr. #ekipa';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Še ni sporočil.'; String get chat_noMessages => 'Še ni sporočil.';
-53
View File
@@ -365,45 +365,6 @@ class AppLocalizationsSv extends AppLocalizations {
String get settings_radioSettingsUpdated => String get settings_radioSettingsUpdated =>
'Radioinställningarna har uppdaterats'; 'Radioinställningarna har uppdaterats';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Plats'; String get settings_location => 'Plats';
@@ -1345,20 +1306,6 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get channels_hashtagHint => 't.ex. #team'; String get channels_hashtagHint => 't.ex. #team';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Inga meddelanden ännu'; String get chat_noMessages => 'Inga meddelanden ännu';
-53
View File
@@ -367,45 +367,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => 'Налаштування радіо оновлено'; String get settings_radioSettingsUpdated => 'Налаштування радіо оновлено';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => 'Геопозиція'; String get settings_location => 'Геопозиція';
@@ -1359,20 +1320,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get channels_hashtagHint => 'напр. #команда'; String get channels_hashtagHint => 'напр. #команда';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => 'Поки немає повідомлень.'; String get chat_noMessages => 'Поки немає повідомлень.';
-53
View File
@@ -350,45 +350,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get settings_radioSettingsUpdated => '无线电设置已更新'; String get settings_radioSettingsUpdated => '无线电设置已更新';
@override
String get settings_regionSettings => 'Regions';
@override
String get settings_regionSettingsSubtitle => 'Manage stored regions';
@override
String get settings_regionManagement_screenTitle => 'Region Management';
@override
String get settings_regionNameHint => 'Enter region name';
@override
String get settings_regionAddRegion => 'Add region';
@override
String get settings_regionFetchRegions => 'Fetch regions from repeaters';
@override
String get settings_regionFetchRegionsFail => 'No regions were found';
@override
String get settings_regionFetchRegionsAlreadyExists =>
'This region has already been added';
@override
String get settings_regionName => 'Region Name';
@override
String get settings_regionDeleted => 'Region deleted';
@override
String get settings_deleteRegion => 'Delete Region';
@override
String settings_deleteRegionConfirm(String region) {
return 'Remove \"$region\" from region list?';
}
@override @override
String get settings_location => '位置'; String get settings_location => '位置';
@@ -1284,20 +1245,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get channels_hashtagHint => '例如:#团队'; String get channels_hashtagHint => '例如:#团队';
@override
String channels_regionSetTo(String region) {
return 'Region: $region';
}
@override
String get channels_regionNotSet => 'Region: none';
@override
String get channels_regionSelect_Title => 'Select a region';
@override
String get channels_clearRegion => 'Clear region';
@override @override
String get chat_noMessages => '暂无消息'; String get chat_noMessages => '暂无消息';
-4
View File
@@ -27,10 +27,6 @@ class Channel {
bool get isPublicChannel => pskHex == publicChannelPsk; bool get isPublicChannel => pskHex == publicChannelPsk;
bool get isHashtagChannel => name.startsWith('#');
bool get isPrivateChannel => !isPublicChannel && !isHashtagChannel;
static Channel? fromFrame(Uint8List frame) { static Channel? fromFrame(Uint8List frame) {
// CHANNEL_INFO format: // CHANNEL_INFO format:
// [0] = RESP_CODE_CHANNEL_INFO (18) // [0] = RESP_CODE_CHANNEL_INFO (18)
+3 -15
View File
@@ -110,7 +110,8 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
final entry = entries[index]; final entry = entries[index];
final time = final time =
'${entry.timestamp.hour.toString().padLeft(2, '0')}:${entry.timestamp.minute.toString().padLeft(2, '0')}:${entry.timestamp.second.toString().padLeft(2, '0')}'; '${entry.timestamp.hour.toString().padLeft(2, '0')}:${entry.timestamp.minute.toString().padLeft(2, '0')}:${entry.timestamp.second.toString().padLeft(2, '0')}';
Future<void> copyHex() async { return GestureDetector(
onLongPress: () async {
await Clipboard.setData( await Clipboard.setData(
ClipboardData( ClipboardData(
text: entry.payload text: entry.payload
@@ -122,20 +123,7 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
.join(''), .join(''),
), ),
); );
if (context.mounted) { },
showDismissibleSnackBar(
context,
content: Text(
context.l10n.debugLog_bleCopied,
),
);
}
}
return GestureDetector(
onTap: copyHex,
onLongPress: copyHex,
onSecondaryTap: copyHex,
child: Container( child: Container(
color: MeshPalette.bg, color: MeshPalette.bg,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
+4 -141
View File
@@ -42,8 +42,6 @@ import '../theme/mesh_theme.dart';
import '../widgets/mesh_ui.dart'; import '../widgets/mesh_ui.dart';
import 'channel_message_path_screen.dart'; import 'channel_message_path_screen.dart';
import 'map_screen.dart'; import 'map_screen.dart';
import 'region_management_screen.dart';
import '../storage/region_store.dart';
class ChannelChatScreen extends StatefulWidget { class ChannelChatScreen extends StatefulWidget {
final Channel channel; final Channel channel;
@@ -275,10 +273,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: GestureDetector( title: Row(
behavior: HitTestBehavior.opaque,
onTap: () => openRegionSelectDialog(widget.channel),
child: Row(
children: [ children: [
_channelIcon(widget.channel), _channelIcon(widget.channel),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -299,20 +294,12 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
Consumer<MeshCoreConnector>( Consumer<MeshCoreConnector>(
builder: (context, connector, _) { builder: (context, connector, _) {
final unreadCount = connector final unreadCount = connector
.getUnreadCountForChannelIndex( .getUnreadCountForChannelIndex(widget.channel.index);
widget.channel.index,
);
final privacy = widget.channel.isPublicChannel final privacy = widget.channel.isPublicChannel
? context.l10n.channels_public ? context.l10n.channels_public
: context.l10n.channels_private; : context.l10n.channels_private;
final region = connector.getChannelRegion(
widget.channel.index,
);
final regionText = region.isNotEmpty
? '${context.l10n.channels_regionSetTo(region)}'
: '';
return Text( return Text(
'$privacy${context.l10n.chat_unread(unreadCount)}$regionText', '$privacy${context.l10n.chat_unread(unreadCount)}',
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
); );
@@ -323,15 +310,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
), ),
], ],
), ),
),
centerTitle: false, centerTitle: false,
bottom: const SyncProgressAppBarBottom(), bottom: const SyncProgressAppBarBottom(),
actions: [ actions: [
IconButton(
tooltip: context.l10n.channels_regionSelect_Title,
icon: const Icon(Icons.landscape),
onPressed: () => openRegionSelectDialog(widget.channel),
),
const RadioStatsIconButton(), const RadioStatsIconButton(),
PopupMenuButton<String>( PopupMenuButton<String>(
icon: const Icon(Icons.more_vert), icon: const Icon(Icons.more_vert),
@@ -665,9 +646,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
color: textColor.withValues(alpha: 0.72), color: textColor.withValues(alpha: 0.72),
), ),
onSecondaryTap: PlatformInfo.isDesktop
? () => _showMessageActions(message)
: null,
), ),
), ),
], ],
@@ -680,7 +658,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
: EdgeInsets.zero, : EdgeInsets.zero,
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
RouteChip( RouteChip(
isDirect: (message.pathLength ?? -1) >= 0, isDirect: (message.pathLength ?? -1) >= 0,
@@ -689,8 +666,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
: null, : null,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Flexible( Text(
child: Text(
context.l10n.channels_via( context.l10n.channels_via(
_formatPathPrefixes(displayPath), _formatPathPrefixes(displayPath),
), ),
@@ -699,7 +675,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
color: metaColor, color: metaColor,
), ),
), ),
),
], ],
), ),
), ),
@@ -1597,118 +1572,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
} }
return hues[h % hues.length]; return hues[h % hues.length];
} }
Future<void> openRegionSelectDialog(Channel channel) async {
// The AppBar subtitle reads the region from the connector inside a
// Consumer, so setChannelRegion's notifyListeners refreshes it directly —
// no post-dialog setState needed.
await showDialog(
context: context,
builder: (BuildContext context) => _RegionSelectDialog(channel: channel),
);
}
}
class _RegionSelectDialog extends StatefulWidget {
final Channel channel;
const _RegionSelectDialog({required this.channel});
@override
State<_RegionSelectDialog> createState() => _RegionSelectDialogState();
}
class _RegionSelectDialogState extends State<_RegionSelectDialog> {
final RegionStore regionStore = RegionStore();
List<Region> regions = [];
int selectedIndex = -1;
@override
void initState() {
super.initState();
loadRegions();
}
void loadRegions() {
setState(() {
regions = regionStore.loadRegions();
final channelRegion = context.read<MeshCoreConnector>().getChannelRegion(
widget.channel.index,
);
selectedIndex = regions.indexOf(channelRegion);
});
}
@override
Widget build(BuildContext context) {
return Dialog(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
AppBar(
backgroundColor: Colors.transparent,
title: Text(context.l10n.channels_regionSelect_Title),
centerTitle: true,
actions: [
IconButton(
tooltip: context.l10n.channels_clearRegion,
icon: const Icon(Icons.backspace_outlined),
onPressed: () {
context.read<MeshCoreConnector>().setChannelRegion(
widget.channel.index,
'',
);
Navigator.pop(context);
},
),
IconButton(
tooltip: context.l10n.settings_regionSettingsSubtitle,
icon: const Icon(Icons.settings),
onPressed: () async {
await pushRegionManagementScreen(context);
if (!mounted) return;
loadRegions();
},
),
],
),
const SizedBox(height: 15),
Expanded(
child: ListView.builder(
itemCount: regions.length,
itemBuilder: (context, index) {
final selected = selectedIndex == index;
return ListTile(
leading: Icon(
Icons.landscape,
color: selected ? MeshPalette.blue : null,
),
title: Text(regions[index]),
trailing: selected
? const Icon(Icons.check, color: MeshPalette.blue)
: null,
tileColor: selected ? MeshPalette.blueBg : null,
onTap: () {
// Tapping the already-selected region clears it.
context.read<MeshCoreConnector>().setChannelRegion(
widget.channel.index,
selected ? '' : regions[index],
);
Navigator.pop(context);
},
);
},
),
),
],
),
),
);
}
} }
class _SwipeReplyBubble extends StatefulWidget { class _SwipeReplyBubble extends StatefulWidget {
+4 -30
View File
@@ -374,30 +374,13 @@ class _ChannelsScreenState extends State<ChannelsScreen>
_communityIndex, _communityIndex,
); );
final bool isCommunityChannel = Channel.isCommunityChannel(channelType); final bool isCommunityChannel = Channel.isCommunityChannel(channelType);
final community = isCommunityChannel
? _communityIndex.getCommunityForChannel(channel)
: null;
// Only flood-routed channels carry a region; show it when one is set.
String subtitle = connector.hasChannelRegion(channel.index)
? context.l10n.channels_regionSetTo(
connector.getChannelRegion(channel.index),
)
: '';
switch (channelType) { switch (channelType) {
case ChannelType.communityPublic: case ChannelType.communityPublic:
icon = Icons.groups; icon = Icons.groups;
iconColor = MeshPalette.magenta; iconColor = MeshPalette.magenta;
if (community != null) {
subtitle =
'${context.l10n.community_publicChannel}${community.name}';
}
case ChannelType.communityHashtag: case ChannelType.communityHashtag:
icon = Icons.groups; icon = Icons.groups;
iconColor = MeshPalette.magenta; iconColor = MeshPalette.magenta;
if (community != null) {
subtitle =
'${context.l10n.community_hashtagChannel}${community.name}';
}
case ChannelType.public: case ChannelType.public:
icon = Icons.public; icon = Icons.public;
iconColor = MeshPalette.signal; iconColor = MeshPalette.signal;
@@ -446,8 +429,9 @@ class _ChannelsScreenState extends State<ChannelsScreen>
channelMessageStore, channelMessageStore,
channel, channel,
), ),
onSecondaryTap: PlatformInfo.isDesktop child: GestureDetector(
? () => _showChannelActions( onSecondaryTapUp: PlatformInfo.isDesktop
? (_) => _showChannelActions(
this.context, this.context,
connector, connector,
channelMessageStore, channelMessageStore,
@@ -520,17 +504,6 @@ class _ChannelsScreenState extends State<ChannelsScreen>
), ),
], ],
), ),
if (subtitle.isNotEmpty) ...[
const SizedBox(height: 2),
Text(
subtitle,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: scheme.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
if (lastPreview.isNotEmpty) ...[ if (lastPreview.isNotEmpty) ...[
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
@@ -593,6 +566,7 @@ class _ChannelsScreenState extends State<ChannelsScreen>
], ],
), ),
), ),
),
); );
} }
-3
View File
@@ -1435,9 +1435,6 @@ class _MessageBubble extends StatelessWidget {
color: textColor.withValues(alpha: 0.72), color: textColor.withValues(alpha: 0.72),
fontSize: bodyFontSize * textScale, fontSize: bodyFontSize * textScale,
), ),
onSecondaryTap: PlatformInfo.isDesktop
? onLongPress
: null,
), ),
), ),
], ],
+7 -3
View File
@@ -147,6 +147,13 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
connector, connector,
index, index,
); );
if (PlatformInfo.isDesktop) {
return GestureDetector(
onSecondaryTapUp: (_) =>
_showContactContextMenu(contact, connector),
child: tile,
);
}
return tile; return tile;
}, },
), ),
@@ -197,9 +204,6 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
} }
}, },
onLongPress: () => _showContactContextMenu(contact, connector), onLongPress: () => _showContactContextMenu(contact, connector),
onSecondaryTap: PlatformInfo.isDesktop
? () => _showContactContextMenu(contact, connector)
: null,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
child: Row( child: Row(
children: [ children: [
@@ -430,7 +430,6 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
minZoom: _mapMinZoom, minZoom: _mapMinZoom,
maxZoom: _mapMaxZoom, maxZoom: _mapMaxZoom,
onLongPress: (_, point) => _addCustomPoint(point), onLongPress: (_, point) => _addCustomPoint(point),
onSecondaryTap: (_, point) => _addCustomPoint(point),
onPositionChanged: (camera, hasGesture) { onPositionChanged: (camera, hasGesture) {
final shouldShow = camera.zoom >= _labelZoomThreshold; final shouldShow = camera.zoom >= _labelZoomThreshold;
if (!_didReceivePositionUpdate || if (!_didReceivePositionUpdate ||
+23 -41
View File
@@ -285,31 +285,6 @@ class _MapScreenState extends State<MapScreen> {
); );
} }
void _handleMapContextPress(
BuildContext context,
MeshCoreConnector connector,
LatLng latLng,
) {
if (_isSelectingPoi) {
setState(() {
_isSelectingPoi = false;
});
_shareMarker(
context: context,
connector: connector,
position: latLng,
defaultLabel: context.l10n.map_pointOfInterest,
flags: 'poi',
);
return;
}
_showShareMarkerAtPositionSheet(
context: context,
connector: connector,
position: latLng,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Builder( return Builder(
@@ -733,10 +708,24 @@ class _MapScreenState extends State<MapScreen> {
} }
}, },
onLongPress: (_, latLng) { onLongPress: (_, latLng) {
_handleMapContextPress(context, connector, latLng); if (_isSelectingPoi) {
}, setState(() {
onSecondaryTap: (_, latLng) { _isSelectingPoi = false;
_handleMapContextPress(context, connector, latLng); });
_shareMarker(
context: context,
connector: connector,
position: latLng,
defaultLabel: context.l10n.map_pointOfInterest,
flags: 'poi',
);
return;
}
_showShareMarkerAtPositionSheet(
context: context,
connector: connector,
position: latLng,
);
}, },
onPositionChanged: (camera, hasGesture) { onPositionChanged: (camera, hasGesture) {
// Track zoom in half-step buckets so cluster/marker // Track zoom in half-step buckets so cluster/marker
@@ -1192,12 +1181,9 @@ class _MapScreenState extends State<MapScreen> {
width: 48, width: 48,
height: 48, height: 48,
child: GestureDetector( child: GestureDetector(
onLongPress: () { onLongPress: () => _isBuildingPathTrace
if (_isBuildingPathTrace) _showNodeInfo(context, guess.contact); ? _showNodeInfo(context, guess.contact)
}, : null,
onSecondaryTap: () {
if (_isBuildingPathTrace) _showNodeInfo(context, guess.contact);
},
onTap: () => _isBuildingPathTrace onTap: () => _isBuildingPathTrace
? _addToPath(context, guess.contact, position: guess.position) ? _addToPath(context, guess.contact, position: guess.position)
: _selectNode(guess.contact, guessedPosition: guess.position), : _selectNode(guess.contact, guessedPosition: guess.position),
@@ -1397,12 +1383,8 @@ class _MapScreenState extends State<MapScreen> {
height: size, height: size,
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onLongPress: () { onLongPress: () =>
if (_isBuildingPathTrace) _showNodeInfo(context, contact); _isBuildingPathTrace ? _showNodeInfo(context, contact) : null,
},
onSecondaryTap: () {
if (_isBuildingPathTrace) _showNodeInfo(context, contact);
},
onTap: () => _isBuildingPathTrace onTap: () => _isBuildingPathTrace
? _addToPath(context, contact) ? _addToPath(context, contact)
: _selectNode(contact), : _selectNode(contact),
-527
View File
@@ -1,527 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:meshcore_open/connector/meshcore_connector.dart';
import 'package:meshcore_open/connector/meshcore_protocol.dart';
import 'package:meshcore_open/l10n/l10n.dart';
import 'package:meshcore_open/models/contact.dart';
import 'package:meshcore_open/storage/region_store.dart';
import 'package:meshcore_open/theme/mesh_theme.dart';
import 'package:meshcore_open/widgets/mesh_ui.dart';
import 'package:provider/provider.dart';
Future<void> pushRegionManagementScreen(BuildContext context) {
return Navigator.push(
context,
MaterialPageRoute<void>(
builder: (context) => const RegionManagementScreen(),
),
);
}
class RegionManagementScreen extends StatefulWidget {
const RegionManagementScreen({super.key});
@override
State<RegionManagementScreen> createState() => _RegionManagementScreenState();
}
class _RegionManagementScreenState extends State<RegionManagementScreen> {
static final RegExp _validFetchedRegion = RegExp(r'^[a-z0-9-]{1,30}$');
final RegionStore _regionStore = RegionStore();
List<Region> _regions = [];
bool _isFetchingRegions = false;
@override
void initState() {
super.initState();
final connector = context.read<MeshCoreConnector>();
_regionStore.setPublicKeyHex = connector.selfPublicKeyHex;
_loadRegions();
}
void _loadRegions() {
final regions = _regionStore.loadRegions();
if (mounted) {
setState(() {
_regions = regions;
});
}
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Scaffold(
appBar: AppBar(
title: Text(l10n.settings_regionManagement_screenTitle),
centerTitle: true,
actions: [
IconButton(
tooltip: l10n.settings_regionAddRegion,
icon: const Icon(Icons.add),
onPressed: () => _showAddRegionDialog(context),
),
IconButton(
tooltip: l10n.settings_regionFetchRegions,
icon: _isFetchingRegions
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.travel_explore),
onPressed: _isFetchingRegions ? null : _showFetchRegionsDialog,
),
],
),
body: ListView.builder(
padding: const EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 88),
itemCount: _regions.length,
itemBuilder: (context, index) {
final region = _regions[index];
return _buildRegionTile(context, region);
},
),
);
}
void _showAddRegionDialog(BuildContext context) {
final l10n = context.l10n;
final controller = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(l10n.settings_regionName),
content: TextField(
controller: controller,
autofocus: true,
textInputAction: TextInputAction.send,
onSubmitted: (_) => _handleAddRegion(controller.text, context),
decoration: InputDecoration(
hintText: l10n.settings_regionNameHint,
border: const OutlineInputBorder(),
),
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp("[a-z0-9-]")),
],
maxLength: 30,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(l10n.common_cancel),
),
TextButton(
onPressed: () => _handleAddRegion(controller.text, context),
child: Text(l10n.common_add),
),
],
),
);
}
Future<void> _showFetchRegionsDialog() async {
if (_isFetchingRegions) return;
setState(() {
_isFetchingRegions = true;
});
Set<Region> fetchedRegions = {};
try {
fetchedRegions = await _fetchRegionsFromRepeaters();
} finally {
if (mounted) {
setState(() {
_isFetchingRegions = false;
});
}
}
if (!mounted) return;
final l10n = context.l10n;
final sortedRegions = fetchedRegions.toList()..sort();
await showDialog<void>(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(l10n.settings_regionFetchRegions),
content: sortedRegions.isEmpty
? Text(l10n.settings_regionFetchRegionsFail)
: StatefulBuilder(
builder: (context, setDialogState) {
return SizedBox(
width: double.maxFinite,
child: ListView.builder(
shrinkWrap: true,
itemCount: sortedRegions.length,
itemBuilder: (context, index) {
final fetchedRegion = sortedRegions[index];
final alreadyExists = _regions.contains(fetchedRegion);
return MeshCard(
margin: const EdgeInsets.symmetric(vertical: 4),
padding: const EdgeInsets.only(left: 14, right: 4),
child: Row(
children: [
const Icon(
Icons.landscape,
color: MeshPalette.blue,
),
const SizedBox(width: 12),
Expanded(
child: Text(
fetchedRegion,
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
TextButton(
style: alreadyExists
? TextButton.styleFrom(
foregroundColor: Theme.of(
context,
).disabledColor,
)
: null,
onPressed: () {
if (alreadyExists) {
_showDialogSnackBar(
context,
l10n.settings_regionFetchRegionsAlreadyExists,
);
return;
}
_regionStore.addRegion(fetchedRegion);
_loadRegions();
setDialogState(() {});
},
child: Text(l10n.common_add),
),
],
),
);
},
),
);
},
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: Text(l10n.common_close),
),
],
),
);
}
void _showDialogSnackBar(BuildContext context, String message) {
final overlay = Overlay.maybeOf(context);
if (overlay == null) return;
final theme = Theme.of(context);
final entry = OverlayEntry(
builder: (context) => Positioned(
left: 16,
right: 16,
bottom: 32,
child: SafeArea(
child: Material(
color: theme.colorScheme.inverseSurface,
elevation: 6,
borderRadius: BorderRadius.circular(4),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Text(
message,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onInverseSurface,
),
),
),
),
),
),
);
overlay.insert(entry);
Timer(const Duration(seconds: 3), entry.remove);
}
Future<Set<Region>> _fetchRegionsFromRepeaters() async {
final connector = context.read<MeshCoreConnector>();
final repeaters = await _discoverNearbyRepeaters(connector);
final regions = <Region>{};
for (final repeater in repeaters) {
if (!mounted || !connector.isConnected) break;
regions.addAll(await _requestRegionsFromRepeater(connector, repeater));
}
return regions;
}
Future<List<Contact>> _discoverNearbyRepeaters(
MeshCoreConnector connector,
) async {
final repeaters = connector.contacts
.where((contact) => contact.type == advTypeRepeater)
.toList();
if (repeaters.isEmpty || !connector.isConnected) return <Contact>[];
StreamSubscription<Uint8List>? subscription;
Timer? timeout;
final completer = Completer<Set<String>>();
final respondingPrefixes = <String>{};
final tag = DateTime.now().microsecondsSinceEpoch & 0xFFFFFFFF;
void complete() {
if (completer.isCompleted) return;
timeout?.cancel();
subscription?.cancel();
completer.complete(respondingPrefixes);
}
subscription = connector.receivedFrames.listen((frame) {
if (frame.isEmpty || completer.isCompleted) return;
final reader = BufferReader(frame);
try {
if (reader.readByte() != pushCodeControlData) return;
if (reader.remaining < 9) return;
reader.skipBytes(3); // SNR, RSSI, path_len from companion firmware.
final payloadType = reader.readByte();
if (((payloadType >> 4) & 0x0F) != controlSubtypeDiscoverResp ||
(payloadType & 0x0F) != advTypeRepeater) {
return;
}
reader.skipBytes(1); // Inbound SNR reported by the responding repeater.
if (reader.readUInt32LE() != tag) return;
final publicKeyPrefix = reader.readRemainingBytes();
if (publicKeyPrefix.isEmpty) return;
respondingPrefixes.add(pubKeyToHex(publicKeyPrefix));
} catch (_) {
// Ignore malformed discovery frames; another response may still arrive.
}
});
try {
final payload = buildDiscoveryRequestPayload(tag, prefixOnly: true);
await connector.sendFrame(buildSendControlDataFrame(payload));
timeout = Timer(const Duration(seconds: 10), complete);
final prefixes = await completer.future;
return repeaters.where((contact) {
final contactKey = contact.publicKeyHex.toLowerCase();
return prefixes.any((prefix) => contactKey.startsWith(prefix));
}).toList();
} catch (_) {
timeout?.cancel();
await subscription.cancel();
return <Contact>[];
}
}
Future<Set<Region>> _requestRegionsFromRepeater(
MeshCoreConnector connector,
Contact repeater,
) async {
StreamSubscription<Uint8List>? subscription;
Timer? timeout;
final completer = Completer<Set<Region>>();
int? expectedTag;
final originalPath = Uint8List.fromList(repeater.path);
final originalPathLength = repeater.pathLength;
var pathChangedForRequest = false;
void complete(Set<Region> regions) {
if (completer.isCompleted) return;
timeout?.cancel();
subscription?.cancel();
completer.complete(regions);
}
void restartTimeout(Duration duration) {
timeout?.cancel();
timeout = Timer(duration, () => complete(<Region>{}));
}
try {
final replyPath = Uint8List(0);
const replyHopCount = 0;
await connector.setContactPath(repeater, replyPath, replyHopCount);
pathChangedForRequest = true;
subscription = connector.receivedFrames.listen((frame) {
if (frame.isEmpty || completer.isCompleted) return;
final reader = BufferReader(frame);
try {
final cmd = reader.readByte();
if (cmd == respCodeSent) {
reader.skipBytes(1);
expectedTag = reader.readUInt32LE();
final estimatedTimeoutMs = reader.readUInt32LE();
restartTimeout(
Duration(
milliseconds: estimatedTimeoutMs > 0
? estimatedTimeoutMs + 2000
: 10000,
),
);
return;
}
if (cmd == respCodeErr) {
complete(<Region>{});
return;
}
if (cmd != pushCodeBinaryResponse || expectedTag == null) return;
reader.skipBytes(1);
final tag = reader.readUInt32LE();
if (tag != expectedTag) return;
complete(_parseRegionsResponse(reader.readRemainingBytes()));
} catch (_) {
complete(<Region>{});
}
});
restartTimeout(const Duration(seconds: 10));
final frame = buildSendAnonReqFrame(
repeater.publicKey,
requestType: anonReqTypeRegions,
replyPath: replyPath,
replyHopCount: replyHopCount,
pathHashWidth: connector.pathHashByteWidth,
);
await connector.sendFrame(frame);
final regions = await completer.future;
if (pathChangedForRequest && connector.isConnected) {
await _restoreRepeaterPath(
connector,
repeater,
originalPathLength,
originalPath,
);
}
return regions;
} catch (_) {
timeout?.cancel();
subscription?.cancel();
if (pathChangedForRequest && connector.isConnected) {
await _restoreRepeaterPath(
connector,
repeater,
originalPathLength,
originalPath,
);
}
return <Region>{};
}
}
Future<void> _restoreRepeaterPath(
MeshCoreConnector connector,
Contact repeater,
int originalPathLength,
Uint8List originalPath,
) async {
if (originalPathLength < 0) {
await connector.clearContactPath(repeater);
return;
}
await connector.setContactPath(repeater, originalPath, originalPathLength);
}
Set<Region> _parseRegionsResponse(Uint8List frame) {
if (frame.length <= 4) return <Region>{};
final names = utf8
.decode(frame.sublist(4), allowMalformed: true)
.replaceAll('\x00', '')
.split(',');
return names
.map((name) => name.trim())
.where((name) => _validFetchedRegion.hasMatch(name))
.toSet();
}
void _handleAddRegion(Region region, BuildContext context) {
Navigator.pop(context);
_regionStore.addRegion(region);
_loadRegions();
}
Widget _buildRegionTile(BuildContext context, Region region) {
final scheme = Theme.of(context).colorScheme;
return MeshCard(
key: ValueKey(region),
padding: const EdgeInsets.only(left: 14, right: 4),
child: Row(
children: [
const Icon(Icons.landscape, color: MeshPalette.blue),
const SizedBox(width: 12),
Expanded(
child: Text(
region,
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
IconButton(
tooltip: context.l10n.settings_deleteRegion,
icon: Icon(Icons.delete_outline, color: scheme.error),
onPressed: () => _confirmDelete(context, region),
),
],
),
);
}
void _confirmDelete(BuildContext context, Region region) {
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(context.l10n.settings_deleteRegion),
content: Text(context.l10n.settings_deleteRegionConfirm(region)),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: Text(context.l10n.common_cancel),
),
TextButton(
onPressed: () async {
final connector = context.read<MeshCoreConnector>();
Navigator.pop(dialogContext);
await _regionStore.removeRegion(region);
// Deleting a region clears it from any channels that used it;
// refresh the connector's in-memory channel regions to match.
await connector.loadChannelSettings();
_loadRegions();
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.settings_regionDeleted)),
);
},
child: Text(
context.l10n.common_delete,
style: TextStyle(
color: Theme.of(dialogContext).colorScheme.error,
),
),
),
],
),
);
}
}
-9
View File
@@ -19,7 +19,6 @@ import 'app_debug_log_screen.dart';
import 'ble_debug_log_screen.dart'; import 'ble_debug_log_screen.dart';
import '../widgets/radio_stats_entry.dart'; import '../widgets/radio_stats_entry.dart';
import '../widgets/sync_progress_overlay.dart'; import '../widgets/sync_progress_overlay.dart';
import 'region_management_screen.dart';
/// Convert device coding-rate value (1-4 on some firmware, 5-8 on others) /// Convert device coding-rate value (1-4 on some firmware, 5-8 on others)
/// to the UI enum range (always 5-8). /// to the UI enum range (always 5-8).
@@ -450,14 +449,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
onTap: () => _showRadioSettings(context, connector), onTap: () => _showRadioSettings(context, connector),
), ),
const Divider(height: 1, indent: 16), const Divider(height: 1, indent: 16),
_tappableTile(
context,
icon: Icons.landscape,
title: l10n.settings_regionSettings,
subtitle: l10n.settings_regionSettingsSubtitle,
onTap: () => pushRegionManagementScreen(context),
),
const Divider(height: 1, indent: 16),
_tappableTile( _tappableTile(
context, context,
icon: Icons.sensors_outlined, icon: Icons.sensors_outlined,
-2
View File
@@ -176,8 +176,6 @@ class BleDebugLogService extends ChangeNotifier {
return 'CMD_SET_CUSTOM_VAR'; return 'CMD_SET_CUSTOM_VAR';
case cmdSendTracePath: case cmdSendTracePath:
return 'CMD_SEND_TRACE_PATH'; return 'CMD_SEND_TRACE_PATH';
case cmdSetFloodScope:
return 'CMD_SET_FLOOD_SCOPE';
default: default:
return null; return null;
} }
-39
View File
@@ -1,39 +0,0 @@
import '../utils/app_logger.dart';
import 'prefs_manager.dart';
class ChannelRegionStore {
static const String _keyPrefix = 'channel_region_';
String publicKeyHex = '';
set setPublicKeyHex(String value) =>
publicKeyHex = value.length >= 10 ? value.substring(0, 10) : '';
String get keyFor => '$_keyPrefix$publicKeyHex';
Future<String> loadRegion(int channelIndex) async {
if (publicKeyHex.isEmpty) {
appLogger.warn(
'Public key hex is not set. Cannot load channel settings.',
);
return '';
}
final prefs = PrefsManager.instance;
final key = '$keyFor$channelIndex';
String? region = prefs.getString(key);
return region ?? '';
}
Future<String> saveRegion(int channelIndex, String region) async {
if (publicKeyHex.isEmpty) {
appLogger.warn(
'Public key hex is not set. Cannot save channel settings.',
);
return '';
}
final prefs = PrefsManager.instance;
final key = '$keyFor$channelIndex';
await prefs.setString(key, region);
return region;
}
}
-53
View File
@@ -1,53 +0,0 @@
import 'package:meshcore_open/storage/channel_region_store.dart';
import 'package:meshcore_open/storage/channel_store.dart';
import 'prefs_manager.dart';
typedef Region = String;
class RegionStore {
static const String key = 'regions';
String publicKeyHex = '';
set setPublicKeyHex(String value) =>
publicKeyHex = value.length >= 10 ? value.substring(0, 10) : '';
List<Region> loadRegions() {
final prefs = PrefsManager.instance;
List<Region>? region = prefs.getStringList(key);
return region ?? [];
}
void saveRegions(List<Region> regions) {
final prefs = PrefsManager.instance;
var distinctRegions = [
...{...regions},
];
distinctRegions.sort();
prefs.setStringList(key, distinctRegions);
}
void addRegion(Region region) {
final regions = loadRegions();
regions.add(region);
saveRegions(regions);
}
Future<void> removeRegion(Region region) async {
final regions = loadRegions();
final channelStore = ChannelStore();
final channelRegionStore = ChannelRegionStore();
channelStore.setPublicKeyHex = publicKeyHex;
channelRegionStore.setPublicKeyHex = publicKeyHex;
for (var channel in await channelStore.loadChannels()) {
var channelRegion = await channelRegionStore.loadRegion(channel.index);
if (channelRegion == region) {
await channelRegionStore.saveRegion(channel.index, '');
}
}
regions.remove(region);
saveRegions(regions);
}
}
-3
View File
@@ -50,7 +50,6 @@ class MeshCard extends StatelessWidget {
final Widget child; final Widget child;
final VoidCallback? onTap; final VoidCallback? onTap;
final VoidCallback? onLongPress; final VoidCallback? onLongPress;
final VoidCallback? onSecondaryTap;
final EdgeInsetsGeometry padding; final EdgeInsetsGeometry padding;
final EdgeInsetsGeometry margin; final EdgeInsetsGeometry margin;
final Color? color; final Color? color;
@@ -62,7 +61,6 @@ class MeshCard extends StatelessWidget {
required this.child, required this.child,
this.onTap, this.onTap,
this.onLongPress, this.onLongPress,
this.onSecondaryTap,
this.padding = const EdgeInsets.all(14), this.padding = const EdgeInsets.all(14),
this.margin = const EdgeInsets.symmetric(horizontal: 16, vertical: 4), this.margin = const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
this.color, this.color,
@@ -91,7 +89,6 @@ class MeshCard extends StatelessWidget {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
onLongPress!(); onLongPress!();
}, },
onSecondaryTap: onSecondaryTap,
child: Padding(padding: padding, child: child), child: Padding(padding: padding, child: child),
), ),
), ),
+3 -15
View File
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart'; import '../connector/meshcore_connector.dart';
import '../utils/platform_info.dart';
import '../helpers/path_helper.dart'; import '../helpers/path_helper.dart';
import '../l10n/l10n.dart'; import '../l10n/l10n.dart';
import '../models/contact.dart'; import '../models/contact.dart';
@@ -535,13 +534,7 @@ class _RoutingSheetBodyState extends State<_RoutingSheetBody> {
l10n.routing_deliveryCounts(record.successCount, record.failureCount), l10n.routing_deliveryCounts(record.successCount, record.failureCount),
]; ];
return GestureDetector( return Card(
behavior: HitTestBehavior.opaque,
onSecondaryTapUp: PlatformInfo.isDesktop && hasBytes
? (_) =>
_showPathDetail(context, connector, contact, record.pathBytes)
: null,
child: Card(
margin: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile( child: ListTile(
enabled: hasBytes, enabled: hasBytes,
@@ -588,15 +581,10 @@ class _RoutingSheetBodyState extends State<_RoutingSheetBody> {
? () => _applyHistoryPath(connector, contact, record) ? () => _applyHistoryPath(connector, contact, record)
: null, : null,
onLongPress: hasBytes onLongPress: hasBytes
? () => _showPathDetail( ? () =>
context, _showPathDetail(context, connector, contact, record.pathBytes)
connector,
contact,
record.pathBytes,
)
: null, : null,
), ),
),
); );
} }
@@ -8,7 +8,6 @@ class TranslatedMessageContent extends StatelessWidget {
final TextStyle style; final TextStyle style;
final TextStyle? originalStyle; final TextStyle? originalStyle;
final bool showOriginalFirst; final bool showOriginalFirst;
final VoidCallback? onSecondaryTap;
const TranslatedMessageContent({ const TranslatedMessageContent({
super.key, super.key,
@@ -17,7 +16,6 @@ class TranslatedMessageContent extends StatelessWidget {
this.originalText, this.originalText,
this.originalStyle, this.originalStyle,
this.showOriginalFirst = true, this.showOriginalFirst = true,
this.onSecondaryTap,
}); });
@override @override
@@ -38,14 +36,12 @@ class TranslatedMessageContent extends StatelessWidget {
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
fontSize: style.fontSize, fontSize: style.fontSize,
), ),
onSecondaryTap: onSecondaryTap,
) )
: null; : null;
final translatedWidget = LinkHandler.buildLinkifyText( final translatedWidget = LinkHandler.buildLinkifyText(
context: context, context: context,
text: trimmedDisplay, text: trimmedDisplay,
style: style, style: style,
onSecondaryTap: onSecondaryTap,
); );
if (!shouldShowOriginal) { if (!shouldShowOriginal) {
+1 -311
View File
@@ -1,311 +1 @@
{ {}
"bg": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"de": [
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists"
],
"es": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"fr": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"hu": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"it": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"ja": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"ko": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"nl": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"pl": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"pt": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"ru": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"sk": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"sl": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"sv": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"uk": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
],
"zh": [
"settings_regionSettings",
"settings_regionSettingsSubtitle",
"settings_regionManagement_screenTitle",
"settings_regionNameHint",
"settings_regionAddRegion",
"settings_regionFetchRegions",
"settings_regionFetchRegionsFail",
"settings_regionFetchRegionsAlreadyExists",
"settings_regionName",
"settings_regionDeleted",
"settings_deleteRegion",
"settings_deleteRegionConfirm",
"channels_regionSetTo",
"channels_regionNotSet",
"channels_regionSelect_Title",
"channels_clearRegion"
]
}