mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-15 07:04:26 +10:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e53c493e78 | |||
| 54e0dae172 | |||
| 066aba7c5d | |||
| 6b6a881c7a | |||
| 8ef8a38495 | |||
| ddcda4ba5a | |||
| b572314ae9 | |||
| e97fb9bd24 |
@@ -2994,13 +2994,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
_pendingChannelSentQueue.add(message.messageId);
|
||||
notifyListeners();
|
||||
|
||||
final trimmed = text.trim();
|
||||
final isStructuredPayload =
|
||||
trimmed.startsWith('g:') || trimmed.startsWith('m:');
|
||||
final outboundText =
|
||||
(isChannelSmazEnabled(channel.index) && !isStructuredPayload)
|
||||
? Smaz.encodeIfSmaller(text)
|
||||
: text;
|
||||
final outboundText = prepareChannelOutboundText(channel.index, text);
|
||||
await _waitForRadioQuiet(lastInboundRxTime: _lastChannelMsgRxTime);
|
||||
await sendFrame(
|
||||
buildSendChannelTextMsgFrame(channel.index, outboundText),
|
||||
@@ -4452,6 +4446,16 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
return text;
|
||||
}
|
||||
|
||||
String prepareChannelOutboundText(int channelIndex, String text) {
|
||||
final trimmed = text.trim();
|
||||
final isStructuredPayload =
|
||||
trimmed.startsWith('g:') || trimmed.startsWith('m:');
|
||||
if (!isStructuredPayload && isChannelSmazEnabled(channelIndex)) {
|
||||
return Smaz.encodeIfSmaller(text);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
String _channelDisplayName(int channelIndex) {
|
||||
for (final channel in _channels) {
|
||||
if (channel.index != channelIndex) continue;
|
||||
|
||||
@@ -4,8 +4,14 @@ import 'package:flutter/services.dart';
|
||||
|
||||
class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter {
|
||||
final int maxBytes;
|
||||
final String Function(String)? encoder;
|
||||
|
||||
const Utf8LengthLimitingTextInputFormatter(this.maxBytes);
|
||||
const Utf8LengthLimitingTextInputFormatter(this.maxBytes, {this.encoder});
|
||||
|
||||
int _effectiveByteLength(String text) {
|
||||
final effective = encoder != null ? encoder!(text) : text;
|
||||
return utf8.encode(effective).length;
|
||||
}
|
||||
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
@@ -13,8 +19,7 @@ class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter {
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
if (maxBytes <= 0) return oldValue;
|
||||
final bytes = utf8.encode(newValue.text);
|
||||
if (bytes.length <= maxBytes) return newValue;
|
||||
if (_effectiveByteLength(newValue.text) <= maxBytes) return newValue;
|
||||
|
||||
final truncated = _truncateToMaxBytes(newValue.text, maxBytes);
|
||||
return TextEditingValue(
|
||||
@@ -25,6 +30,14 @@ class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter {
|
||||
}
|
||||
|
||||
String _truncateToMaxBytes(String text, int limit) {
|
||||
if (encoder != null) {
|
||||
final runes = text.runes.toList();
|
||||
while (runes.isNotEmpty &&
|
||||
_effectiveByteLength(String.fromCharCodes(runes)) > maxBytes) {
|
||||
runes.removeLast();
|
||||
}
|
||||
return String.fromCharCodes(runes);
|
||||
}
|
||||
final buffer = StringBuffer();
|
||||
var used = 0;
|
||||
for (final rune in text.runes) {
|
||||
|
||||
+2
-9
@@ -1922,13 +1922,6 @@
|
||||
"contact_teleLocSubtitle": "Позволи споделяне на данни за местоположение",
|
||||
"contact_teleLoc": "Местоположение на телеметрията",
|
||||
"contact_teleEnvSubtitle": "Позволи споделяне на данни от средносферните датчици",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_initialRouteWeight": "Първоначална тежест на маршрута",
|
||||
"appSettings_maxRouteWeight": "Максимално допустимо тегло на маршрута",
|
||||
"appSettings_initialRouteWeightSubtitle": "Начално тегло за новооткрити маршрути",
|
||||
@@ -1940,7 +1933,6 @@
|
||||
"appSettings_maxMessageRetries": "Максимален брой опити за изпращане на съобщение",
|
||||
"appSettings_maxMessageRetriesSubtitle": "Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_multiAck": "Мулти-потвърди: {value}",
|
||||
"settings_telemetryModeUpdated": "Режим на телеметрията е обновен",
|
||||
"map_showOverlaps": "Покриване на ключа на повтаряча",
|
||||
"map_runTraceWithReturnPath": "Върни се по същия път.",
|
||||
@@ -2073,5 +2065,6 @@
|
||||
"chat_sendMessage": "Изпратете съобщение",
|
||||
"room_guest": "Информация за сървъра на стаята",
|
||||
"repeater_guest": "Информация за ретранслаторите",
|
||||
"repeater_guestTools": "Инструменти за гости"
|
||||
"repeater_guestTools": "Инструменти за гости",
|
||||
"settings_multiAck": "Множество потвърждения"
|
||||
}
|
||||
|
||||
+2
-9
@@ -1950,13 +1950,6 @@
|
||||
"contact_lastSeen": "Zuletzt gesehen",
|
||||
"contact_clearChat": "Chat löschen",
|
||||
"contact_teleEnvSubtitle": "Teilen von Umgebungsensordaten zulassen",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_initialRouteWeightSubtitle": "Ausgangsgewicht für neu entdeckte Pfade",
|
||||
"appSettings_maxRouteWeightSubtitle": "Maximales Gewicht, das ein Weg durch erfolgreiche Lieferungen erreichen kann.",
|
||||
"appSettings_maxRouteWeight": "Maximale Gesamtstreckenlänge",
|
||||
@@ -1969,7 +1962,6 @@
|
||||
"appSettings_maxMessageRetriesSubtitle": "Anzahl der Versuche, eine Nachricht erneut zu senden, bevor sie als fehlgeschlagen markiert wird.",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_telemetryModeUpdated": "Telemetriemodus aktualisiert",
|
||||
"settings_multiAck": "Mehrfach-Bestätigungen: {value}",
|
||||
"map_showOverlaps": "Überlappungen der Repeater-Taste",
|
||||
"map_runTraceWithReturnPath": "Auf dem gleichen Pfad zurückkehren.",
|
||||
"@radioStats_noiseFloor": {
|
||||
@@ -2101,5 +2093,6 @@
|
||||
"repeater_guest": "Informationen zu Repeatern",
|
||||
"repeater_guestTools": "Gastwerkzeuge",
|
||||
"chat_sendMessage": "Nachricht senden",
|
||||
"room_guest": "Informationen zum Room Server"
|
||||
"room_guest": "Informationen zum Room Server",
|
||||
"settings_multiAck": "Mehrere Bestätigungen"
|
||||
}
|
||||
|
||||
+1
-8
@@ -178,14 +178,7 @@
|
||||
"settings_telemetryEnvironmentMode": "Telemetry Environment Mode",
|
||||
"settings_advertLocation": "Advert Location",
|
||||
"settings_advertLocationSubtitle": "Include location in advert.",
|
||||
"settings_multiAck": "Multi-ACKs: {value}",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings_multiAck": "Multi-ACKs",
|
||||
"settings_telemetryModeUpdated": "Telemetry mode updated",
|
||||
"settings_actions": "Actions",
|
||||
"settings_sendAdvertisement": "Send Advertisement",
|
||||
|
||||
+2
-9
@@ -1950,13 +1950,6 @@
|
||||
"contact_teleBaseSubtitle": "Permitir el intercambio de nivel de batería y telemetría básica",
|
||||
"contact_teleEnv": "Entorno de Telemetría",
|
||||
"contact_teleEnvSubtitle": "Permitir el intercambio de datos de sensores de entorno",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_initialRouteWeight": "Peso inicial de la ruta",
|
||||
"appSettings_maxRouteWeight": "Peso máximo permitido para la ruta",
|
||||
"appSettings_initialRouteWeightSubtitle": "Peso inicial para rutas recién descubiertas",
|
||||
@@ -1969,7 +1962,6 @@
|
||||
"appSettings_maxMessageRetriesSubtitle": "Número de intentos de reintento antes de marcar un mensaje como fallido.",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_telemetryModeUpdated": "Modo de telemetría actualizado",
|
||||
"settings_multiAck": "Multi-ACKs: {value}",
|
||||
"map_showOverlaps": "Superposiciones de tecla repetidora",
|
||||
"map_runTraceWithReturnPath": "Volver atrás por el mismo camino.",
|
||||
"@radioStats_noiseFloor": {
|
||||
@@ -2101,5 +2093,6 @@
|
||||
"repeater_guest": "Información sobre repetidores",
|
||||
"chat_sendMessage": "Enviar mensaje",
|
||||
"repeater_guestTools": "Herramientas para invitados",
|
||||
"room_guest": "Información del servidor"
|
||||
"room_guest": "Información del servidor",
|
||||
"settings_multiAck": "Múltiples respuestas de confirmación"
|
||||
}
|
||||
|
||||
+2
-9
@@ -1922,13 +1922,6 @@
|
||||
"contact_lastSeen": "Dernière fois vu",
|
||||
"contact_clearChat": "Effacer la conversation",
|
||||
"contact_teleBaseSubtitle": "Autoriser le partage du niveau de batterie et de la télémétrie de base",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_maxRouteWeightSubtitle": "Poids maximal qu'un itinéraire peut accumuler grâce à des livraisons réussies.",
|
||||
"appSettings_initialRouteWeight": "Poids initial de l'itinéraire",
|
||||
"appSettings_maxRouteWeight": "Poids maximal autorisé pour le trajet",
|
||||
@@ -1940,7 +1933,6 @@
|
||||
"appSettings_maxMessageRetries": "Nombre maximal de tentatives de récupération de messages",
|
||||
"appSettings_maxMessageRetriesSubtitle": "Nombre de tentatives de relance avant de marquer un message comme ayant échoué.",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_multiAck": "Multi-ACKs : {value}",
|
||||
"settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour",
|
||||
"map_showOverlaps": "Chevauchement de la touche répétitive",
|
||||
"map_runTraceWithReturnPath": "Revenir sur le même chemin.",
|
||||
@@ -2073,5 +2065,6 @@
|
||||
"repeater_guestTools": "Outils pour les invités",
|
||||
"chat_sendMessage": "Envoyer un message",
|
||||
"room_guest": "Informations sur le serveur",
|
||||
"repeater_guest": "Informations sur les répéteurs"
|
||||
"repeater_guest": "Informations sur les répéteurs",
|
||||
"settings_multiAck": "Plusieurs accusés de réception"
|
||||
}
|
||||
|
||||
+2
-9
@@ -2012,13 +2012,6 @@
|
||||
"radioStats_stripWaiting": "Rádió adatok begyűjtése…",
|
||||
"radioStats_settingsTile": "Rádió statisztikák",
|
||||
"radioStats_settingsSubtitle": "Háttérzaj, RSSI, zaj-sűrűség, és a használat időtartama",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings_denyAll": "Elutasítom",
|
||||
"settings_privacySettingsDescription": "Válassza ki, hogy az eszközének melyik információkat oszt meg másokkal.",
|
||||
"settings_privacySubtitle": "Ellenőrizd, hogy milyen információkat osztanak meg.",
|
||||
@@ -2030,7 +2023,6 @@
|
||||
"settings_telemetryEnvironmentMode": "Adatkapcsolati környezeti mód",
|
||||
"settings_advertLocation": "Reklám megjelenési hely",
|
||||
"settings_advertLocationSubtitle": "A hirdetés tartalmazza a helyszínt.",
|
||||
"settings_multiAck": "Többszöri visszaigazolások: {value}",
|
||||
"settings_telemetryModeUpdated": "A telemetriamód frissítve",
|
||||
"contact_info": "Kapcsolattartási információk",
|
||||
"contact_settings": "Kapcsolat beállítások",
|
||||
@@ -2111,5 +2103,6 @@
|
||||
"repeater_guestTools": "Vendégek számára elérhető eszközök",
|
||||
"room_guest": "Szoba szerver információk",
|
||||
"chat_sendMessage": "Üzenet küldése",
|
||||
"repeater_guest": "Adatok a repeaterről"
|
||||
"repeater_guest": "Adatok a repeaterről",
|
||||
"settings_multiAck": "Többszörös visszaigazolások"
|
||||
}
|
||||
|
||||
+2
-9
@@ -1922,13 +1922,6 @@
|
||||
"contact_teleBaseSubtitle": "Consenti la condivisione del livello della batteria e della telemetria di base",
|
||||
"contact_teleEnvSubtitle": "Consenti la condivisione dei dati del sensore ambientale",
|
||||
"contact_teleEnv": "Ambiente di telemetria",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_initialRouteWeight": "Peso iniziale del percorso",
|
||||
"appSettings_initialRouteWeightSubtitle": "Peso di partenza per nuovi percorsi",
|
||||
"appSettings_maxRouteWeightSubtitle": "Il peso massimo che un percorso può accumulare grazie a consegne di successo.",
|
||||
@@ -1941,7 +1934,6 @@
|
||||
"appSettings_maxMessageRetriesSubtitle": "Numero di tentativi di riprova prima di considerare un messaggio come fallito.",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_telemetryModeUpdated": "Modalità telemetria aggiornata",
|
||||
"settings_multiAck": "Multi-ACKs: {value}",
|
||||
"map_showOverlaps": "Sovrapposizioni della chiave ripetitore",
|
||||
"map_runTraceWithReturnPath": "Tornare indietro sullo stesso percorso",
|
||||
"@radioStats_noiseFloor": {
|
||||
@@ -2073,5 +2065,6 @@
|
||||
"repeater_guest": "Informazioni sul ripetitore",
|
||||
"repeater_guestTools": "Strumenti per gli ospiti",
|
||||
"chat_sendMessage": "Invia messaggio",
|
||||
"room_guest": "Informazioni sul server"
|
||||
"room_guest": "Informazioni sul server",
|
||||
"settings_multiAck": "ACK multipli"
|
||||
}
|
||||
|
||||
+2
-9
@@ -2012,13 +2012,6 @@
|
||||
"radioStats_stripWaiting": "ラジオの統計情報を取得中…",
|
||||
"radioStats_settingsTile": "ラジオの統計",
|
||||
"radioStats_settingsSubtitle": "ノイズレベル、RSSI、SNR、および通信時間",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings_privacy": "プライバシー設定",
|
||||
"settings_privacySubtitle": "共有する情報の内容を管理する。",
|
||||
"settings_denyAll": "すべてを否定",
|
||||
@@ -2030,7 +2023,6 @@
|
||||
"settings_telemetryEnvironmentMode": "テレメトリ環境モード",
|
||||
"settings_advertLocation": "広告掲載場所",
|
||||
"settings_advertLocationSubtitle": "広告に場所を記載してください。",
|
||||
"settings_multiAck": "複数のACK:{value}",
|
||||
"settings_telemetryModeUpdated": "テレメトリモードが更新されました",
|
||||
"contact_info": "連絡先",
|
||||
"contact_settings": "連絡設定",
|
||||
@@ -2111,5 +2103,6 @@
|
||||
"room_guest": "ルームサーバーに関する情報",
|
||||
"chat_sendMessage": "メッセージを送信する",
|
||||
"repeater_guest": "繰り返し送信に関する情報",
|
||||
"repeater_guestTools": "ゲスト向けツール"
|
||||
"repeater_guestTools": "ゲスト向けツール",
|
||||
"settings_multiAck": "複数のACK(応答)"
|
||||
}
|
||||
|
||||
+2
-9
@@ -2012,13 +2012,6 @@
|
||||
"radioStats_stripWaiting": "라디오 통계 가져오기…",
|
||||
"radioStats_settingsTile": "라디오 통계",
|
||||
"radioStats_settingsSubtitle": "잡음 수준, RSSI, 신호 대 잡음비, 통신 시간",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings_privacy": "개인 정보 설정",
|
||||
"settings_privacySubtitle": "어떤 정보를 공유할지 통제하세요.",
|
||||
"settings_privacySettingsDescription": "어떤 정보를 기기가 다른 사람들과 공유할지 선택하세요.",
|
||||
@@ -2030,7 +2023,6 @@
|
||||
"settings_telemetryEnvironmentMode": "텔레메트리 환경 모드",
|
||||
"settings_advertLocation": "광고 위치",
|
||||
"settings_advertLocationSubtitle": "광고에 위치 정보를 포함하세요.",
|
||||
"settings_multiAck": "다중 ACK: {value}",
|
||||
"settings_telemetryModeUpdated": "텔레메트리 모드 업데이트 완료",
|
||||
"contact_info": "연락처",
|
||||
"contact_settings": "연락처 설정",
|
||||
@@ -2111,5 +2103,6 @@
|
||||
"repeater_guestTools": "손님용 도구",
|
||||
"chat_sendMessage": "메시지를 보내기",
|
||||
"repeater_guest": "반복 장비 정보",
|
||||
"room_guest": "서버 정보"
|
||||
"room_guest": "서버 정보",
|
||||
"settings_multiAck": "다중 ACK"
|
||||
}
|
||||
|
||||
@@ -901,8 +901,8 @@ abstract class AppLocalizations {
|
||||
/// No description provided for @settings_multiAck.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Multi-ACKs: {value}'**
|
||||
String settings_multiAck(String value);
|
||||
/// **'Multi-ACKs'**
|
||||
String get settings_multiAck;
|
||||
|
||||
/// No description provided for @settings_telemetryModeUpdated.
|
||||
///
|
||||
|
||||
@@ -437,9 +437,7 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
'Включи местоположение в обявата';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Мулти-потвърди: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Множество потвърждения';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'Режим на телеметрията е обновен';
|
||||
|
||||
@@ -435,9 +435,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Ort in der Anzeige einbeziehen';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Mehrfach-Bestätigungen: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Mehrere Bestätigungen';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'Telemetriemodus aktualisiert';
|
||||
|
||||
@@ -427,9 +427,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get settings_advertLocationSubtitle => 'Include location in advert.';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Multi-ACKs: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Multi-ACKs';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'Telemetry mode updated';
|
||||
|
||||
@@ -434,9 +434,7 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
String get settings_advertLocationSubtitle => 'Incluir ubicación en anuncio';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Multi-ACKs: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Múltiples respuestas de confirmación';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'Modo de telemetría actualizado';
|
||||
|
||||
@@ -438,9 +438,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
'Inclure l\'emplacement dans l\'annonce';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Multi-ACKs : $value';
|
||||
}
|
||||
String get settings_multiAck => 'Plusieurs accusés de réception';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated =>
|
||||
|
||||
@@ -437,9 +437,7 @@ class AppLocalizationsHu extends AppLocalizations {
|
||||
'A hirdetés tartalmazza a helyszínt.';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Többszöri visszaigazolások: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Többszörös visszaigazolások';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'A telemetriamód frissítve';
|
||||
|
||||
@@ -437,9 +437,7 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
'Includi la posizione nell\'annuncio';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Multi-ACKs: $value';
|
||||
}
|
||||
String get settings_multiAck => 'ACK multipli';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'Modalità telemetria aggiornata';
|
||||
|
||||
@@ -414,9 +414,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
String get settings_advertLocationSubtitle => '広告に場所を記載してください。';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return '複数のACK:$value';
|
||||
}
|
||||
String get settings_multiAck => '複数のACK(応答)';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'テレメトリモードが更新されました';
|
||||
|
||||
@@ -414,9 +414,7 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
String get settings_advertLocationSubtitle => '광고에 위치 정보를 포함하세요.';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return '다중 ACK: $value';
|
||||
}
|
||||
String get settings_multiAck => '다중 ACK';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => '텔레메트리 모드 업데이트 완료';
|
||||
|
||||
@@ -432,9 +432,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
'Locatie opnemen in advertentie';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Multi-ACKs: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Meerdere bevestigingen';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'Telemetrie-modus bijgewerkt';
|
||||
|
||||
@@ -439,9 +439,7 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
'Uwzględnij lokalizację w ogłoszeniu';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Wielokrotne ACK: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Wielokrotne potwierdzenia odbioru';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated =>
|
||||
|
||||
@@ -436,9 +436,7 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
'Incluir localização no anúncio';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Multi-ACKs: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Multi-ACKs';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'Modo de telemetria atualizado';
|
||||
|
||||
@@ -436,9 +436,7 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
'Включить местоположение в объявление';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Мульти-ACK: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Несколько подтверждений';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'Режим телеметрии обновлен';
|
||||
|
||||
@@ -430,9 +430,7 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
String get settings_advertLocationSubtitle => 'Zahrnúť polohu do inzerátu';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Viaceré ACK: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Viaceré ACK';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated =>
|
||||
|
||||
@@ -430,9 +430,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get settings_advertLocationSubtitle => 'Vključi lokacijo v oglas.';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Večkratni potrditvi: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Več potrdil';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'Način telemetrije posodobljen';
|
||||
|
||||
@@ -428,9 +428,7 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
String get settings_advertLocationSubtitle => 'Inkludera plats i annonsen';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Multi-ACKs: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Flera bekräftelser';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'Telemetri-läge uppdaterat';
|
||||
|
||||
@@ -432,9 +432,7 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
'Включити місце розташування в оголошення';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return 'Багатократне підтвердження: $value';
|
||||
}
|
||||
String get settings_multiAck => 'Багато підтверджень';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => 'Режим телеметрії оновлено';
|
||||
|
||||
@@ -408,9 +408,7 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
String get settings_advertLocationSubtitle => '在广告中包含位置';
|
||||
|
||||
@override
|
||||
String settings_multiAck(String value) {
|
||||
return '多重ACK:$value';
|
||||
}
|
||||
String get settings_multiAck => '多重ACK';
|
||||
|
||||
@override
|
||||
String get settings_telemetryModeUpdated => '遥测模式已更新';
|
||||
|
||||
+2
-9
@@ -1922,13 +1922,6 @@
|
||||
"contact_lastSeen": "Laatst gezien",
|
||||
"contact_clearChat": "Chat leegmaken",
|
||||
"contact_teleBaseSubtitle": "Sta delen van batterij niveau en basis telemetrie toe",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_maxRouteWeightSubtitle": "Het maximale gewicht dat een route kan bereiken door succesvolle leveringen.",
|
||||
"appSettings_initialRouteWeight": "เริ่มต้น gewicht van de route",
|
||||
"appSettings_maxRouteWeight": "Maximale gewicht voor de route",
|
||||
@@ -1941,7 +1934,6 @@
|
||||
"appSettings_maxMessageRetriesSubtitle": "Aantal pogingen om een bericht opnieuw te versturen voordat het als mislukt wordt gemarkeerd",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_telemetryModeUpdated": "Telemetrie-modus bijgewerkt",
|
||||
"settings_multiAck": "Multi-ACKs: {value}",
|
||||
"map_showOverlaps": "Herhalingssleutel overlapt",
|
||||
"map_runTraceWithReturnPath": "Terugkeren op hetzelfde pad.",
|
||||
"@radioStats_noiseFloor": {
|
||||
@@ -2073,5 +2065,6 @@
|
||||
"repeater_guestTools": "Gastenfuncties",
|
||||
"room_guest": "Informatie over de server",
|
||||
"chat_sendMessage": "Verzend bericht",
|
||||
"repeater_guest": "Informatie over herhalingsapparatuur"
|
||||
"repeater_guest": "Informatie over herhalingsapparatuur",
|
||||
"settings_multiAck": "Meerdere bevestigingen"
|
||||
}
|
||||
|
||||
+2
-9
@@ -1960,13 +1960,6 @@
|
||||
"contact_settings": "Ustawienia kontaktowe",
|
||||
"contact_lastSeen": "Ostatnio widziany",
|
||||
"contact_teleBaseSubtitle": "Pozwól na udostępnianie poziomu naładowania baterii i podstawowych danych telemetrycznych",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_initialRouteWeight": "Początkowa waga trasy",
|
||||
"appSettings_maxRouteWeight": "Maksymalny dopuszczalny ciężar pojazdu",
|
||||
"appSettings_initialRouteWeightSubtitle": "Początkowa waga dla nowych, odkrytych ścieżek",
|
||||
@@ -1979,7 +1972,6 @@
|
||||
"appSettings_maxMessageRetriesSubtitle": "Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_telemetryModeUpdated": "Tryb telemetryczny zaktualizowany",
|
||||
"settings_multiAck": "Wielokrotne ACK: {value}",
|
||||
"map_showOverlaps": "Nakładające się klucze przekaźników",
|
||||
"map_runTraceWithReturnPath": "Wróć tą samą ścieżką",
|
||||
"@radioStats_noiseFloor": {
|
||||
@@ -2111,5 +2103,6 @@
|
||||
"chat_sendMessage": "Wyślij wiadomość",
|
||||
"repeater_guestTools": "Narzędzia dla gości",
|
||||
"repeater_guest": "Informacje dotyczące urządzenia powtarzającego",
|
||||
"room_guest": "Informacje o serwerze"
|
||||
"room_guest": "Informacje o serwerze",
|
||||
"settings_multiAck": "Wielokrotne potwierdzenia odbioru"
|
||||
}
|
||||
|
||||
+1
-8
@@ -1922,13 +1922,6 @@
|
||||
"contact_telemetry": "Telemetria",
|
||||
"contact_settings": "Configurações de Contato",
|
||||
"contact_teleBaseSubtitle": "Permitir compartilhamento do nível da bateria e telemetria básica",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_initialRouteWeight": "Peso Inicial da Rota",
|
||||
"appSettings_maxRouteWeight": "Peso Máximo da Rota",
|
||||
"appSettings_maxRouteWeightSubtitle": "Peso máximo que um determinado percurso pode acumular com entregas bem-sucedidas.",
|
||||
@@ -1941,7 +1934,7 @@
|
||||
"appSettings_maxMessageRetriesSubtitle": "Número de tentativas de reenvio antes de classificar uma mensagem como falha.",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_telemetryModeUpdated": "Modo de telemetria atualizado",
|
||||
"settings_multiAck": "Multi-ACKs: {value}",
|
||||
"settings_multiAck": "Multi-ACKs",
|
||||
"map_showOverlaps": "Sobreposições da Chave Repeater",
|
||||
"map_runTraceWithReturnPath": "Retornar ao mesmo caminho.",
|
||||
"@radioStats_noiseFloor": {
|
||||
|
||||
+2
-9
@@ -1162,13 +1162,6 @@
|
||||
"contact_clearChat": "Очистить чат",
|
||||
"contact_lastSeen": "Последний раз видели",
|
||||
"contact_teleBaseSubtitle": "Разрешить обмен уровнем заряда батареи и базовой телеметрией",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_maxRouteWeight": "Максимальный допустимый вес маршрута",
|
||||
"appSettings_maxRouteWeightSubtitle": "Максимальный вес, который может быть перевезён по определённому маршруту при успешных доставках.",
|
||||
"appSettings_initialRouteWeightSubtitle": "Начальный вес для новых, только что открытых маршрутов",
|
||||
@@ -1181,7 +1174,6 @@
|
||||
"appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_telemetryModeUpdated": "Режим телеметрии обновлен",
|
||||
"settings_multiAck": "Мульти-ACK: {value}",
|
||||
"map_showOverlaps": "Перекрытия ключа повтора",
|
||||
"map_runTraceWithReturnPath": "Вернуться обратно по тому же пути",
|
||||
"@radioStats_noiseFloor": {
|
||||
@@ -1313,5 +1305,6 @@
|
||||
"chat_sendMessage": "Отправить сообщение",
|
||||
"repeater_guest": "Информация о ретрансляторе",
|
||||
"room_guest": "Информация о сервере",
|
||||
"repeater_guestTools": "Инструменты для гостей"
|
||||
"repeater_guestTools": "Инструменты для гостей",
|
||||
"settings_multiAck": "Несколько подтверждений"
|
||||
}
|
||||
|
||||
+1
-8
@@ -1922,13 +1922,6 @@
|
||||
"contact_lastSeen": "Naposledy videný",
|
||||
"contact_teleBase": "Báza telemetrie",
|
||||
"contact_teleEnvSubtitle": "Povoliť zdieľanie údajov senzorov prostredia",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_maxRouteWeightSubtitle": "Maximálna hmotnosť, ktorú môže trás prenášať vďaka úspešným zásielkam.",
|
||||
"appSettings_initialRouteWeightSubtitle": "Počiatočná váha pre nové, objavené cesty",
|
||||
"appSettings_initialRouteWeight": "Počiatočná váha trasy",
|
||||
@@ -1941,7 +1934,7 @@
|
||||
"appSettings_maxMessageRetriesSubtitle": "Počet pokusov o odošleť pred označením správy ako neúspešnej",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_telemetryModeUpdated": "Režim telemetrie bol aktualizovaný",
|
||||
"settings_multiAck": "Viaceré ACK: {value}",
|
||||
"settings_multiAck": "Viaceré ACK",
|
||||
"map_showOverlaps": "Prekrývanie opakovača kľúča",
|
||||
"map_runTraceWithReturnPath": "Vráťte sa späť po tej istej ceste.",
|
||||
"@radioStats_noiseFloor": {
|
||||
|
||||
+2
-9
@@ -1922,13 +1922,6 @@
|
||||
"contact_teleEnv": "Okolje telemetrije",
|
||||
"contact_teleEnvSubtitle": "Dovoli deljenje podatkov okoljskih senzorjev",
|
||||
"contact_teleLocSubtitle": "Dovoli deljenje podatkov o lokaciji",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_maxRouteWeightSubtitle": "Največja teža, ki jo lahko pot doseže s uspešnimi dostavnami.",
|
||||
"appSettings_initialRouteWeight": "Izvirna teža poti",
|
||||
"appSettings_initialRouteWeightSubtitle": "Izguba teže za nove, odkriti poti",
|
||||
@@ -1940,7 +1933,6 @@
|
||||
"appSettings_maxMessageRetries": "Najve število poskusov pošiljanja sporočil",
|
||||
"appSettings_maxMessageRetriesSubtitle": "Število poskusov ponovnega poslanja, preden se sporočilo označuje kot neuspešno",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_multiAck": "Večkratni potrditvi: {value}",
|
||||
"settings_telemetryModeUpdated": "Način telemetrije posodobljen",
|
||||
"map_showOverlaps": "Prekrivanje ključa ponovnega predvajanja",
|
||||
"map_runTraceWithReturnPath": "Vrni se nazaj po isti poti.",
|
||||
@@ -2073,5 +2065,6 @@
|
||||
"repeater_guest": "Informacije o ponovljalniku",
|
||||
"chat_sendMessage": "Pošlji sporočilo",
|
||||
"room_guest": "Informacije o strežniku",
|
||||
"repeater_guestTools": "Naložila za goste"
|
||||
"repeater_guestTools": "Naložila za goste",
|
||||
"settings_multiAck": "Več potrdil"
|
||||
}
|
||||
|
||||
+2
-9
@@ -1922,13 +1922,6 @@
|
||||
"contact_teleBaseSubtitle": "Tillåt delning av batterinivå och grundläggande telemetri",
|
||||
"contact_teleLoc": "Telemetridata plats",
|
||||
"contact_teleLocSubtitle": "Tillåt delning av platsdata",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_initialRouteWeightSubtitle": "Initial vikt för nyligen upptäckta vägar",
|
||||
"appSettings_maxRouteWeight": "Maximalt tillåtet vikt för rutten",
|
||||
"appSettings_maxRouteWeightSubtitle": "Maximal vikt som en leveransväg kan ackumulera från framgångsrika leveranser.",
|
||||
@@ -1941,7 +1934,6 @@
|
||||
"appSettings_maxMessageRetriesSubtitle": "Antal försök att skicka om ett meddelande innan det markeras som misslyckat.",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_telemetryModeUpdated": "Telemetri-läge uppdaterat",
|
||||
"settings_multiAck": "Multi-ACKs: {value}",
|
||||
"map_showOverlaps": "Repeater-nyckelöverlappningar",
|
||||
"map_runTraceWithReturnPath": "Gå tillbaka på samma väg",
|
||||
"@radioStats_noiseFloor": {
|
||||
@@ -2073,5 +2065,6 @@
|
||||
"repeater_guest": "Information om repetorer",
|
||||
"chat_sendMessage": "Skicka meddelande",
|
||||
"repeater_guestTools": "Gästverktyg",
|
||||
"room_guest": "Information om servern"
|
||||
"room_guest": "Information om servern",
|
||||
"settings_multiAck": "Flera bekräftelser"
|
||||
}
|
||||
|
||||
+2
-9
@@ -1922,13 +1922,6 @@
|
||||
"contact_lastSeen": "Останній раз бачили",
|
||||
"contact_teleEnv": "Середовище телеметрії",
|
||||
"contact_teleEnvSubtitle": "Дозволити спільний доступ до даних датчиків середовища",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_initialRouteWeight": "Початкова вартість маршруту",
|
||||
"appSettings_initialRouteWeightSubtitle": "Початкова вага для нових відкритих шляхів",
|
||||
"appSettings_maxRouteWeight": "Максимальна вага маршруту",
|
||||
@@ -1941,7 +1934,6 @@
|
||||
"appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_telemetryModeUpdated": "Режим телеметрії оновлено",
|
||||
"settings_multiAck": "Багатократне підтвердження: {value}",
|
||||
"map_showOverlaps": "Перекриття ключа повторювача",
|
||||
"map_runTraceWithReturnPath": "Повернутися назад тим же шляхом",
|
||||
"@radioStats_noiseFloor": {
|
||||
@@ -2073,5 +2065,6 @@
|
||||
"repeater_guestTools": "Інструменти для гостей",
|
||||
"repeater_guest": "Інформація про ретранслятор",
|
||||
"room_guest": "Інформація про сервер кімнати",
|
||||
"chat_sendMessage": "Надіслати повідомлення"
|
||||
"chat_sendMessage": "Надіслати повідомлення",
|
||||
"settings_multiAck": "Багато підтверджень"
|
||||
}
|
||||
|
||||
+1
-8
@@ -1927,13 +1927,6 @@
|
||||
"contact_settings": "联系人设置",
|
||||
"contact_teleLocSubtitle": "允许共享位置数据",
|
||||
"contact_telemetry": "遥测数据",
|
||||
"@settings_multiAck": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_maxRouteWeight": "最大路径重量",
|
||||
"appSettings_initialRouteWeightSubtitle": "新发现路径的初始重量",
|
||||
"appSettings_initialRouteWeight": "初始路线权重",
|
||||
@@ -1945,7 +1938,7 @@
|
||||
"appSettings_maxMessageRetries": "最大消息重试次数",
|
||||
"appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数",
|
||||
"path_routeWeight": "{weight}/{max}",
|
||||
"settings_multiAck": "多重ACK:{value}",
|
||||
"settings_multiAck": "多重ACK",
|
||||
"settings_telemetryModeUpdated": "遥测模式已更新",
|
||||
"map_showOverlaps": "重复键重叠",
|
||||
"map_runTraceWithReturnPath": "沿着相同的路径返回",
|
||||
|
||||
@@ -13,7 +13,6 @@ import '../helpers/chat_scroll_controller.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../helpers/gif_helper.dart';
|
||||
import '../helpers/reaction_helper.dart';
|
||||
import '../helpers/utf8_length_limiter.dart';
|
||||
import '../helpers/snack_bar_builder.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/channel.dart';
|
||||
@@ -23,6 +22,7 @@ import '../services/app_settings_service.dart';
|
||||
import '../services/chat_text_scale_service.dart';
|
||||
import '../services/translation_service.dart';
|
||||
import '../utils/emoji_utils.dart';
|
||||
import '../widgets/byte_count_input.dart';
|
||||
import '../widgets/chat_zoom_wrapper.dart';
|
||||
import '../widgets/emoji_picker.dart';
|
||||
import '../widgets/gif_message.dart';
|
||||
@@ -1093,27 +1093,33 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return TextField(
|
||||
return ByteCountedTextField(
|
||||
maxBytes: maxBytes,
|
||||
controller: _textController,
|
||||
focusNode: _textFieldFocusNode,
|
||||
inputFormatters: [
|
||||
Utf8LengthLimitingTextInputFormatter(maxBytes),
|
||||
],
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
hintText: context.l10n.chat_typeMessage,
|
||||
onSubmitted: (_) => _sendMessage(),
|
||||
encoder:
|
||||
connector.isChannelSmazEnabled(widget.channel.index)
|
||||
? (text) => connector.prepareChannelOutboundText(
|
||||
widget.channel.index,
|
||||
text,
|
||||
)
|
||||
: null,
|
||||
decoration: InputDecoration(
|
||||
hintText: context.l10n.chat_typeMessage,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainerLow,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
horizontal: 20,
|
||||
vertical: 14,
|
||||
),
|
||||
),
|
||||
maxLines: null,
|
||||
textInputAction: TextInputAction.send,
|
||||
onSubmitted: (_) => _sendMessage(),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -1195,7 +1201,11 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
}
|
||||
|
||||
final maxBytes = maxChannelMessageBytes(connector.selfName);
|
||||
if (utf8.encode(messageText).length > maxBytes) {
|
||||
final outboundText = connector.prepareChannelOutboundText(
|
||||
widget.channel.index,
|
||||
messageText,
|
||||
);
|
||||
if (utf8.encode(outboundText).length > maxBytes) {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.chat_messageTooLong(maxBytes)),
|
||||
|
||||
@@ -18,7 +18,6 @@ import '../widgets/message_status_icon.dart';
|
||||
import '../helpers/chat_scroll_controller.dart';
|
||||
import '../helpers/gif_helper.dart';
|
||||
import '../helpers/path_helper.dart';
|
||||
import '../helpers/utf8_length_limiter.dart';
|
||||
import '../models/channel_message.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../models/message.dart';
|
||||
@@ -30,6 +29,7 @@ import '../services/path_history_service.dart';
|
||||
import '../services/translation_service.dart';
|
||||
import '../widgets/chat_zoom_wrapper.dart';
|
||||
import '../widgets/elements_ui.dart';
|
||||
import '../widgets/byte_count_input.dart';
|
||||
import 'channel_message_path_screen.dart';
|
||||
import 'map_screen.dart';
|
||||
import '../utils/emoji_utils.dart';
|
||||
@@ -567,24 +567,35 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return TextField(
|
||||
return ByteCountedTextField(
|
||||
maxBytes: maxBytes,
|
||||
controller: _textController,
|
||||
focusNode: _textFieldFocusNode,
|
||||
inputFormatters: [
|
||||
Utf8LengthLimitingTextInputFormatter(maxBytes),
|
||||
],
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
hintText: context.l10n.chat_typeMessage,
|
||||
onSubmitted: (_) => _sendMessage(connector),
|
||||
encoder:
|
||||
connector.isContactSmazEnabled(
|
||||
widget.contact.publicKeyHex,
|
||||
)
|
||||
? (text) => connector.prepareContactOutboundText(
|
||||
widget.contact,
|
||||
text,
|
||||
)
|
||||
: null,
|
||||
decoration: InputDecoration(
|
||||
hintText: context.l10n.chat_typeMessage,
|
||||
border: const OutlineInputBorder(),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainerLow,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
horizontal: 20,
|
||||
vertical: 14,
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.send,
|
||||
onSubmitted: (_) => _sendMessage(connector),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -672,7 +683,11 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
}
|
||||
}
|
||||
final maxBytes = maxContactMessageBytes();
|
||||
if (utf8.encode(outgoingText).length > maxBytes) {
|
||||
final outboundText = connector.prepareContactOutboundText(
|
||||
_resolveContact(connector),
|
||||
outgoingText,
|
||||
);
|
||||
if (utf8.encode(outboundText).length > maxBytes) {
|
||||
showDismissibleSnackBar(
|
||||
context,
|
||||
content: Text(context.l10n.chat_messageTooLong(maxBytes)),
|
||||
|
||||
@@ -1011,6 +1011,15 @@ void _privacySettings(BuildContext context, MeshCoreConnector connector) {
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SwitchListTile(
|
||||
title: Text(l10n.settings_multiAck),
|
||||
value: multiAcks == 1,
|
||||
onChanged: (value) {
|
||||
setDialogState(() => multiAcks = value ? 1 : 0);
|
||||
},
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<int>(
|
||||
initialValue: telemetryMode,
|
||||
decoration: InputDecoration(
|
||||
@@ -1052,21 +1061,6 @@ void _privacySettings(BuildContext context, MeshCoreConnector connector) {
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.settings_multiAck(multiAcks.toString()),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
Slider(
|
||||
value: multiAcks.toDouble(),
|
||||
min: 0,
|
||||
max: 2,
|
||||
divisions: 2,
|
||||
label: multiAcks.toString(),
|
||||
onChanged: (value) {
|
||||
setDialogState(() => multiAcks = value.round());
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../helpers/utf8_length_limiter.dart';
|
||||
|
||||
/// A [TextField] that displays a live UTF-8 byte counter.
|
||||
///
|
||||
/// The counter appears below the field once the user starts typing and changes
|
||||
/// colour as the limit is approached (orange at 70 %, error-red at 90 %).
|
||||
///
|
||||
/// All standard [TextField] behaviour (focus nodes, input actions, decoration
|
||||
/// overrides, etc.) is forwarded so the widget can be dropped into any screen.
|
||||
class ByteCountedTextField extends StatelessWidget {
|
||||
/// Maximum number of UTF-8 bytes allowed.
|
||||
final int maxBytes;
|
||||
|
||||
/// Controller for the text field.
|
||||
final TextEditingController controller;
|
||||
|
||||
/// Optional focus node forwarded to the inner [TextField].
|
||||
final FocusNode? focusNode;
|
||||
|
||||
/// Hint text shown when the field is empty.
|
||||
final String? hintText;
|
||||
|
||||
/// Keyboard action button (defaults to [TextInputAction.send]).
|
||||
final TextInputAction textInputAction;
|
||||
|
||||
/// Called when the user submits via the keyboard action button.
|
||||
final ValueChanged<String>? onSubmitted;
|
||||
|
||||
/// Additional [TextInputFormatter]s applied *before* the byte limiter.
|
||||
final List<TextInputFormatter> extraFormatters;
|
||||
|
||||
/// Text capitalisation forwarded to the inner [TextField].
|
||||
final TextCapitalization textCapitalization;
|
||||
|
||||
/// Optional full [InputDecoration] override. When provided, [hintText] is
|
||||
/// ignored – set it inside the decoration instead.
|
||||
final InputDecoration? decoration;
|
||||
|
||||
/// Ratio (0–1) at which the counter turns the warning colour (default 0.7).
|
||||
final double warningThreshold;
|
||||
|
||||
/// Ratio (0–1) at which the counter turns the error colour (default 0.9).
|
||||
final double errorThreshold;
|
||||
|
||||
/// Whether to hide the counter when the field is empty (default `true`).
|
||||
final bool hideCounterWhenEmpty;
|
||||
|
||||
/// Optional encoder function to transform text before byte counting/limiting.
|
||||
/// If provided, byte limits and counters will use the encoded text length.
|
||||
final String Function(String)? encoder;
|
||||
|
||||
const ByteCountedTextField({
|
||||
super.key,
|
||||
required this.maxBytes,
|
||||
required this.controller,
|
||||
this.focusNode,
|
||||
this.hintText,
|
||||
this.textInputAction = TextInputAction.send,
|
||||
this.onSubmitted,
|
||||
this.extraFormatters = const [],
|
||||
this.textCapitalization = TextCapitalization.sentences,
|
||||
this.decoration,
|
||||
this.warningThreshold = 0.7,
|
||||
this.errorThreshold = 0.9,
|
||||
this.hideCounterWhenEmpty = true,
|
||||
this.encoder,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: controller,
|
||||
builder: (context, value, _) {
|
||||
final effectiveText = encoder != null
|
||||
? encoder!(value.text)
|
||||
: value.text;
|
||||
final usedBytes = utf8.encode(effectiveText).length;
|
||||
final ratio = maxBytes > 0 ? usedBytes / maxBytes : 0.0;
|
||||
final showCounter = !(hideCounterWhenEmpty && value.text.isEmpty);
|
||||
|
||||
final counterColor = ratio > errorThreshold
|
||||
? Theme.of(context).colorScheme.error
|
||||
: ratio > warningThreshold
|
||||
? Colors.orange
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
maxLines: null,
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
inputFormatters: [
|
||||
...extraFormatters,
|
||||
Utf8LengthLimitingTextInputFormatter(
|
||||
maxBytes,
|
||||
encoder: encoder,
|
||||
),
|
||||
],
|
||||
textCapitalization: textCapitalization,
|
||||
decoration:
|
||||
decoration ??
|
||||
InputDecoration(
|
||||
hintText: hintText,
|
||||
border: const OutlineInputBorder(),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
textInputAction: textInputAction,
|
||||
onSubmitted: onSubmitted,
|
||||
),
|
||||
if (showCounter)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4, right: 4),
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
'$usedBytes / $maxBytes',
|
||||
style: TextStyle(fontSize: 11, color: counterColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
flserial
|
||||
jni
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
@@ -22,8 +22,6 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- flserial (from `Flutter/ephemeral/.symlinks/plugins/flserial/macos`)
|
||||
@@ -36,7 +34,6 @@ DEPENDENCIES:
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
flserial:
|
||||
@@ -59,8 +56,6 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
wakelock_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
flserial: 3c161e076dfc73458ec5803e7a9a9d2bb85fadf6
|
||||
@@ -73,7 +68,6 @@ SPEC CHECKSUMS:
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
||||
wakelock_plus: 917609be14d812ddd9e9528876538b2263aaa03b
|
||||
|
||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 8.0.0+10
|
||||
version: 7.0.0+9
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
|
||||
@@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
flserial
|
||||
flutter_local_notifications_windows
|
||||
jni
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
Reference in New Issue
Block a user