feat(#420): remove Smaz send-side compression (Phase 1)

Smaz is a client-side text convention with no MeshCore protocol or firmware
support (findings: GH-315). When enabled it compressed ordinary prose into
`s:`+base64, which any non-lineage client renders as garbage — the same
cross-client interop failure as the old `g:<code>` GIF token.

Phase 1 of the Smaz-removal epic (GH-314): stop sending Smaz and remove the
user-facing surface, while KEEPING decode so messages from lineage peers still
on Smaz, and any legacy compressed rows, keep rendering. Decode removal is
deferred to Phase 2 (GH-421).

Removed: the Smaz branch in prepareContact/ChannelOutboundText (Cyr2Lat branch
and the structured-payload guard preserved); connector state/API (the enabled
maps, is*/set*SmazEnabled, ensureContactSmazSettingLoaded, the warm-up and
channel loaders); the per-channel and per-contact toggles plus their
mutual-exclusion; loadSmazEnabled/saveSmazEnabled and the `*_smaz_` key prefixes
(stores kept, they also hold Cyr2Lat); l10n `channels_smazCompression` and the
orphaned `chat_compressOutgoingMessages` across 18 locales (+ regenerated
app_localizations).

Retained for Phase 2: the 5 Smaz.tryDecodePrefixed decode sites and
helpers/smaz.dart.

Safety (verified): no storage migration, the app already persists plaintext
(receive decodes before store; send stores the pre-compression text), so the
`s:` form was wire-only. ACK matching is unaffected, the expected hash derives
from prepare*'s output, so dropping compression keeps both sides hashing
plaintext.

Behavior change: the composer byte-counter now reflects raw size, so anyone who
had Smaz on can type slightly fewer chars per message.

Tests: gif_url_outbound_guard_test rewritten to pin plaintext passthrough and
decode retention. Full suite green, analyze clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull/455/head
Strycher 2 months ago
parent c8874cc00b
commit f0d0ca0b27

@ -407,12 +407,10 @@ class MeshCoreConnector extends ChangeNotifier {
final ChannelStore _channelStore = ChannelStore();
final UnreadStore _unreadStore = UnreadStore();
List<Channel> _cachedChannels = [];
final Map<int, bool> _channelSmazEnabled = {};
final Map<int, bool> _channelCyr2LatEnabled = {};
final Map<int, String?> _channelCyr2LatProfileId = {};
bool _lastSentWasCliCommand =
false; // Track if last sent message was a CLI command
final Map<String, bool> _contactSmazEnabled = {};
final Map<String, bool> _contactCyr2LatEnabled = {};
final Map<String, String?> _contactCyr2LatProfileId = {};
final Set<String> _knownContactKeys = {};
@ -939,18 +937,6 @@ class MeshCoreConnector extends ChangeNotifier {
);
}
bool isChannelSmazEnabled(int channelIndex) {
return _channelSmazEnabled[channelIndex] ?? false;
}
bool isContactSmazEnabled(String contactKeyHex) {
return _contactSmazEnabled[contactKeyHex] ?? false;
}
void ensureContactSmazSettingLoaded(String contactKeyHex) {
_ensureContactSmazSettingLoaded(contactKeyHex);
}
bool isChannelCyr2LatEnabled(int channelIndex) {
_ensureChannelCyr2LatSettingLoaded(channelIndex);
return _channelCyr2LatEnabled[channelIndex] ?? false;
@ -1081,28 +1067,8 @@ class MeshCoreConnector extends ChangeNotifier {
}
}
Future<void> setChannelSmazEnabled(int channelIndex, bool enabled) async {
_channelSmazEnabled[channelIndex] = enabled;
if (enabled) {
_channelCyr2LatEnabled[channelIndex] = false;
await _channelSettingsStore.saveCyr2LatEnabled(channelIndex, false);
}
await _channelSettingsStore.saveSmazEnabled(channelIndex, enabled);
notifyListeners();
}
Future<void> setContactSmazEnabled(String contactKeyHex, bool enabled) async {
_contactSmazEnabled[contactKeyHex] = enabled;
await _contactSettingsStore.saveSmazEnabled(contactKeyHex, enabled);
notifyListeners();
}
Future<void> setChannelCyr2LatEnabled(int channelIndex, bool enabled) async {
_channelCyr2LatEnabled[channelIndex] = enabled;
if (enabled) {
_channelSmazEnabled[channelIndex] = false;
await _channelSettingsStore.saveSmazEnabled(channelIndex, false);
}
await _channelSettingsStore.saveCyr2LatEnabled(channelIndex, enabled);
notifyListeners();
}
@ -1261,7 +1227,6 @@ class MeshCoreConnector extends ChangeNotifier {
// contact list, which is what stalled the UI for ~44s on connect.
await Future.wait([
for (final contact in cached) ...[
_ensureContactSmazSettingLoaded(contact.publicKeyHex, notify: false),
_ensureContactCyr2LatSettingLoaded(contact.publicKeyHex, notify: false),
],
]);
@ -1276,11 +1241,9 @@ class MeshCoreConnector extends ChangeNotifier {
}
Future<void> loadChannelSettings({int? maxChannels}) async {
_channelSmazEnabled.clear();
_channelCyr2LatEnabled.clear();
final channelCount = maxChannels ?? _maxChannels;
for (int i = 0; i < channelCount; i++) {
_channelSmazEnabled[i] = await _channelSettingsStore.loadSmazEnabled(i);
_channelCyr2LatEnabled[i] = await _channelSettingsStore
.loadCyr2LatEnabled(i);
}
@ -6041,17 +6004,6 @@ class MeshCoreConnector extends ChangeNotifier {
/// Notifying per contact rebuilds the whole tree once per contact, and each
/// rebuild itself walks the contact list, so a large address book turns this
/// into O(contacts^2) work on the UI isolate.
Future<void> _ensureContactSmazSettingLoaded(
String contactKeyHex, {
bool notify = true,
}) async {
if (_contactSmazEnabled.containsKey(contactKeyHex)) return;
final enabled = await _contactSettingsStore.loadSmazEnabled(contactKeyHex);
if (_contactSmazEnabled[contactKeyHex] == enabled) return;
_contactSmazEnabled[contactKeyHex] = enabled;
if (notify) notifyListeners();
}
Future<void> _ensureContactCyr2LatSettingLoaded(
String contactKeyHex, {
bool notify = true,
@ -6131,9 +6083,7 @@ class MeshCoreConnector extends ChangeNotifier {
trimmed.startsWith('m:') ||
trimmed.startsWith('V1|');
if (!isStructuredPayload) {
if (isContactSmazEnabled(contact.publicKeyHex)) {
return Smaz.encodeIfSmaller(text);
} else if (isContactCyr2LatEnabled(contact.publicKeyHex)) {
if (isContactCyr2LatEnabled(contact.publicKeyHex)) {
final profileId = getContactCyr2LatProfileId(contact.publicKeyHex);
final profile = profileId != null && _appSettingsService != null
? _appSettingsService!.getCyr2LatProfileById(profileId)
@ -6159,9 +6109,7 @@ class MeshCoreConnector extends ChangeNotifier {
final isStructuredPayload =
trimmed.startsWith('g:') || trimmed.startsWith('m:');
if (!isStructuredPayload) {
if (isChannelSmazEnabled(channelIndex)) {
return Smaz.encodeIfSmaller(text);
} else if (isChannelCyr2LatEnabled(channelIndex)) {
if (isChannelCyr2LatEnabled(channelIndex)) {
final profileId = getChannelCyr2LatProfileId(channelIndex);
final profile = profileId != null && _appSettingsService != null
? _appSettingsService!.getCyr2LatProfileById(profileId)

@ -390,7 +390,6 @@
}
}
},
"channels_smazCompression": "Компресия SMAZ",
"channels_cyr2latCompression": "Компресия Cyr2Lat",
"channels_cyr2latCompressionDscr": "Заменя някои кирилични символи с латиница при изпращане.",
"channels_cyr2latSettingsHeading": "Настройки на Cyr2Lat",
@ -607,7 +606,6 @@
"chat_type": "Въведете",
"chat_path": "Пътекино",
"chat_publicKey": "Публичен ключ",
"chat_compressOutgoingMessages": "Компресиране на изходящи съобщения",
"chat_floodForced": "Потоп (принуден)",
"chat_directForced": "Директно (принудително)",
"chat_hopsForced": "{count} скока (принудително)",
@ -1318,7 +1316,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Брой заявки",
"telemetry_error": "Неуспешно получаване на данни",
"telemetry_noData": "Няма налични данни за телеметрията.",

@ -390,7 +390,6 @@
}
}
},
"channels_smazCompression": "SMAZ-Komprimierung",
"channels_cyr2latCompression": "Cyr2Lat-Komprimierung",
"channels_cyr2latCompressionDscr": "Ersetzt einige kyrillische Zeichen durch lateinische Zeichen, wenn sie gesendet werden.",
"channels_cyr2latSettingsHeading": "Cyr2Lat-Einstellungen",
@ -607,7 +606,6 @@
"chat_type": "Gebe ein",
"chat_path": "Pfad",
"chat_publicKey": "Öffentlicher Schlüssel",
"chat_compressOutgoingMessages": "Komprimieren ausgehender Nachrichten",
"chat_floodForced": "Geflutet (erzwungen)",
"chat_directForced": "Direkt (erzwungen)",
"chat_hopsForced": "{count} Sprünge (erzwungen)",
@ -1318,7 +1316,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Anzahl der Anfragen",
"telemetry_error": "Daten konnten nicht abgerufen werden",
"telemetry_noData": "Keine Telemetriedaten verfügbar.",

@ -629,7 +629,6 @@
}
}
},
"channels_smazCompression": "SMAZ compression",
"channels_cyr2latCompression": "Cyr2Lat compression",
"channels_cyr2latCompressionDscr": "Replaces some Cyrillic characters with Latin characters when sending.",
"channels_cyr2latSettingsHeading": "Cyr2Lat Setup",
@ -888,7 +887,6 @@
"chat_type": "Type",
"chat_path": "Path",
"chat_publicKey": "Public Key",
"chat_compressOutgoingMessages": "Compress outgoing messages",
"chat_floodForced": "Flood (forced)",
"chat_directForced": "Direct (forced)",
"chat_hopsForced": "{count} hops (forced)",

@ -390,7 +390,6 @@
}
}
},
"channels_smazCompression": "Compresión SMAZ",
"channels_cyr2latCompression": "Compresión Cyr2Lat",
"channels_cyr2latCompressionDscr": "Reemplaza algunos caracteres cirílicos con caracteres latinos al enviar.",
"channels_cyr2latSettingsHeading": "Configuración de Cyr2Lat",
@ -607,7 +606,6 @@
"chat_type": "Escribe",
"chat_path": "Ruta",
"chat_publicKey": "Clave Pública",
"chat_compressOutgoingMessages": "Comprimir mensajes salientes",
"chat_floodForced": "Inundación (forzada)",
"chat_directForced": "Directo (forzado)",
"chat_hopsForced": "{count} saltos (forzados)",
@ -1318,7 +1316,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Número de solicitudes",
"telemetry_error": "No se pudieron obtener los datos",
"telemetry_noData": "No hay datos de telemetría disponibles.",

@ -390,7 +390,6 @@
}
}
},
"channels_smazCompression": "Compression SMAZ",
"channels_cyr2latCompression": "Compression Cyr2Lat",
"channels_cyr2latCompressionDscr": "Remplace certains caractères cyrilliques par des caractères latins lors de l'envoi.",
"channels_cyr2latSettingsHeading": "Paramètres Cyr2Lat",
@ -607,7 +606,6 @@
"chat_type": "Saisir",
"chat_path": "Chemin",
"chat_publicKey": "Clé Publique",
"chat_compressOutgoingMessages": "Compresser les messages sortants",
"chat_floodForced": "Tout le réseau (forcée)",
"chat_directForced": "Direct (forcé)",
"chat_hopsForced": "{count} sauts (forcés)",
@ -1318,7 +1316,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Nombre de requêtes",
"telemetry_error": "Impossible de récupérer les données",
"telemetry_noData": "Aucune donnée de télémétrie disponible.",

@ -550,7 +550,6 @@
}
}
},
"channels_smazCompression": "SMAZ kompresszió",
"channels_cyr2latCompression": "Cyr2Lat kompresszió",
"channels_cyr2latCompressionDscr": "Néhány Cirill betűt Latin betűkkel helyettesít küldéskor.",
"channels_cyr2latSettingsHeading": "Cyr2Lat beállítások",
@ -780,7 +779,6 @@
"chat_type": "Típus",
"chat_path": "Út",
"chat_publicKey": "Nyelvkönyv",
"chat_compressOutgoingMessages": "A küldött üzenetek tömörítése",
"chat_floodForced": "Áradás (kényszerített)",
"chat_directForced": "Közvetlen (erélyes)",
"chat_hopsForced": "{count} ánusz (erővel)",
@ -1504,7 +1502,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Kérések száma",
"telemetry_error": "Nem sikerült lekérni az adatokat",
"telemetry_noData": "Nincsenek elérhető telemetriadatok.",

@ -390,7 +390,6 @@
}
}
},
"channels_smazCompression": "Compressione SMAZ",
"channels_cyr2latCompression": "Compressione Cyr2Lat",
"channels_cyr2latCompressionDscr": "Sostituisce alcuni caratteri cirillici con caratteri latini durante l'invio.",
"channels_cyr2latSettingsHeading": "Impostazioni Cyr2Lat",
@ -607,7 +606,6 @@
"chat_type": "Digita",
"chat_path": "Percorso",
"chat_publicKey": "Chiave Pubblica",
"chat_compressOutgoingMessages": "Comprimi messaggi in uscita",
"chat_floodForced": "Inondazione (forzata)",
"chat_directForced": "Riavvia (forzato)",
"chat_hopsForced": "{count} salti (forzati)",
@ -1318,7 +1316,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Numero di richieste",
"telemetry_error": "Impossibile recuperare i dati",
"telemetry_noData": "Nessun dato di telemetria disponibile.",

@ -550,7 +550,6 @@
}
}
},
"channels_smazCompression": "SMAZ 圧縮",
"channels_cyr2latCompression": "Cyr2Lat 圧縮",
"channels_cyr2latCompressionDscr": "送信時に一部のキリル文字をラテン文字に置き換えます。",
"channels_cyr2latSettingsHeading": "cyr2latの設定",
@ -780,7 +779,6 @@
"chat_type": "種類",
"chat_path": "道",
"chat_publicKey": "公開鍵",
"chat_compressOutgoingMessages": "送信されるメッセージを圧縮する",
"chat_floodForced": "洪水(強制的な)",
"chat_directForced": "直接的な(強制的な)",
"chat_hopsForced": "{count} 本のホップ(強制的に採取)",
@ -1504,7 +1502,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "リクエスト数",
"telemetry_error": "データを取得できません",
"telemetry_noData": "テレメトリデータは利用できません。",

@ -550,7 +550,6 @@
}
}
},
"channels_smazCompression": "SMAZ 압축",
"channels_cyr2latCompression": "Cyr2Lat 압축",
"channels_cyr2latCompressionDscr": "보낼 때 일부 키릴 문자를 라틴 문자로 바꿉니다.",
"channels_cyr2latSettingsHeading": "Cyr2Lat 설정",
@ -780,7 +779,6 @@
"chat_type": "종류",
"chat_path": "경로",
"chat_publicKey": "공개 키",
"chat_compressOutgoingMessages": "전송되는 메시지 압축",
"chat_floodForced": "홍수 (강제)",
"chat_directForced": "직접적인 (강제적인)",
"chat_hopsForced": "{count}번 띄우기 (강제)",
@ -1504,7 +1502,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "요청 수",
"telemetry_error": "데이터를 가져올 수 없습니다",
"telemetry_noData": "텔레메트리 데이터는 제공되지 않습니다.",

@ -2380,12 +2380,6 @@ abstract class AppLocalizations {
/// **'Edit Channel {index}'**
String channels_editChannelTitle(int index);
/// No description provided for @channels_smazCompression.
///
/// In en, this message translates to:
/// **'SMAZ compression'**
String get channels_smazCompression;
/// No description provided for @channels_cyr2latCompression.
///
/// In en, this message translates to:
@ -3136,12 +3130,6 @@ abstract class AppLocalizations {
/// **'Public Key'**
String get chat_publicKey;
/// No description provided for @chat_compressOutgoingMessages.
///
/// In en, this message translates to:
/// **'Compress outgoing messages'**
String get chat_compressOutgoingMessages;
/// No description provided for @chat_floodForced.
///
/// In en, this message translates to:

@ -1286,9 +1286,6 @@ class AppLocalizationsBg extends AppLocalizations {
return 'Редактирай Канал $index';
}
@override
String get channels_smazCompression => 'Компресия SMAZ';
@override
String get channels_cyr2latCompression => 'Компресия Cyr2Lat';
@ -1737,10 +1734,6 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get chat_publicKey => 'Публичен ключ';
@override
String get chat_compressOutgoingMessages =>
'Компресиране на изходящи съобщения';
@override
String get chat_floodForced => 'Потоп (принуден)';

@ -1281,9 +1281,6 @@ class AppLocalizationsDe extends AppLocalizations {
return 'Bearbeiteter Kanal $index';
}
@override
String get channels_smazCompression => 'SMAZ-Komprimierung';
@override
String get channels_cyr2latCompression => 'Cyr2Lat-Komprimierung';
@ -1733,10 +1730,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get chat_publicKey => 'Öffentlicher Schlüssel';
@override
String get chat_compressOutgoingMessages =>
'Komprimieren ausgehender Nachrichten';
@override
String get chat_floodForced => 'Geflutet (erzwungen)';

@ -1260,9 +1260,6 @@ class AppLocalizationsEn extends AppLocalizations {
return 'Edit Channel $index';
}
@override
String get channels_smazCompression => 'SMAZ compression';
@override
String get channels_cyr2latCompression => 'Cyr2Lat compression';
@ -1701,9 +1698,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get chat_publicKey => 'Public Key';
@override
String get chat_compressOutgoingMessages => 'Compress outgoing messages';
@override
String get chat_floodForced => 'Flood (forced)';

@ -1282,9 +1282,6 @@ class AppLocalizationsEs extends AppLocalizations {
return 'Editar Canal $index';
}
@override
String get channels_smazCompression => 'Compresión SMAZ';
@override
String get channels_cyr2latCompression => 'Compresión Cyr2Lat';
@ -1732,9 +1729,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get chat_publicKey => 'Clave Pública';
@override
String get chat_compressOutgoingMessages => 'Comprimir mensajes salientes';
@override
String get chat_floodForced => 'Inundación (forzada)';

@ -1287,9 +1287,6 @@ class AppLocalizationsFr extends AppLocalizations {
return 'Modifier le Canal $index';
}
@override
String get channels_smazCompression => 'Compression SMAZ';
@override
String get channels_cyr2latCompression => 'Compression Cyr2Lat';
@ -1740,10 +1737,6 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get chat_publicKey => 'Clé Publique';
@override
String get chat_compressOutgoingMessages =>
'Compresser les messages sortants';
@override
String get chat_floodForced => 'Tout le réseau (forcée)';

@ -1288,9 +1288,6 @@ class AppLocalizationsHu extends AppLocalizations {
return 'Módosítsd a csatornát $index';
}
@override
String get channels_smazCompression => 'SMAZ kompresszió';
@override
String get channels_cyr2latCompression => 'Cyr2Lat kompresszió';
@ -1743,9 +1740,6 @@ class AppLocalizationsHu extends AppLocalizations {
@override
String get chat_publicKey => 'Nyelvkönyv';
@override
String get chat_compressOutgoingMessages => 'A küldött üzenetek tömörítése';
@override
String get chat_floodForced => 'Áradás (kényszerített)';

@ -1283,9 +1283,6 @@ class AppLocalizationsIt extends AppLocalizations {
return 'Modifica Canale $index';
}
@override
String get channels_smazCompression => 'Compressione SMAZ';
@override
String get channels_cyr2latCompression => 'Compressione Cyr2Lat';
@ -1735,9 +1732,6 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get chat_publicKey => 'Chiave Pubblica';
@override
String get chat_compressOutgoingMessages => 'Comprimi messaggi in uscita';
@override
String get chat_floodForced => 'Inondazione (forzata)';

@ -1229,9 +1229,6 @@ class AppLocalizationsJa extends AppLocalizations {
return 'チャンネル $index の編集';
}
@override
String get channels_smazCompression => 'SMAZ 圧縮';
@override
String get channels_cyr2latCompression => 'Cyr2Lat 圧縮';
@ -1663,9 +1660,6 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get chat_publicKey => '公開鍵';
@override
String get chat_compressOutgoingMessages => '送信されるメッセージを圧縮する';
@override
String get chat_floodForced => '洪水(強制的な)';

@ -1224,9 +1224,6 @@ class AppLocalizationsKo extends AppLocalizations {
return '채널 $index 편집';
}
@override
String get channels_smazCompression => 'SMAZ 압축';
@override
String get channels_cyr2latCompression => 'Cyr2Lat 압축';
@ -1659,9 +1656,6 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get chat_publicKey => '공개 키';
@override
String get chat_compressOutgoingMessages => '전송되는 메시지 압축';
@override
String get chat_floodForced => '홍수 (강제)';

@ -1273,9 +1273,6 @@ class AppLocalizationsNl extends AppLocalizations {
return 'Bewerk Kanaal $index';
}
@override
String get channels_smazCompression => 'SMAZ compressie';
@override
String get channels_cyr2latCompression => 'Cyr2Lat compressie';
@ -1721,10 +1718,6 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get chat_publicKey => 'Openbare Sleutel';
@override
String get chat_compressOutgoingMessages =>
'Verzenden van uitgaande berichten comprimeren';
@override
String get chat_floodForced => 'Flood (afgedwongen)';

@ -1292,9 +1292,6 @@ class AppLocalizationsPl extends AppLocalizations {
return 'Edytuj Kanał $index';
}
@override
String get channels_smazCompression => 'Kompresja SMAZ';
@override
String get channels_cyr2latCompression => 'Kompresja Cyr2Lat';
@ -1747,9 +1744,6 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get chat_publicKey => 'Klucz Publiczny';
@override
String get chat_compressOutgoingMessages => 'Kompresuj wychodzące wiadomości';
@override
String get chat_floodForced => 'Zalew (wymuszony)';

@ -1282,9 +1282,6 @@ class AppLocalizationsPt extends AppLocalizations {
return 'Editar Canal $index';
}
@override
String get channels_smazCompression => 'Compressão SMAZ';
@override
String get channels_cyr2latCompression => 'Compressão Cyr2Lat';
@ -1731,9 +1728,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get chat_publicKey => 'Chave Pública';
@override
String get chat_compressOutgoingMessages => 'Comprimir mensagens enviadas';
@override
String get chat_floodForced => 'Inundação (forçada)';

@ -1283,9 +1283,6 @@ class AppLocalizationsRu extends AppLocalizations {
return 'Изменить канал $index';
}
@override
String get channels_smazCompression => 'Сжатие SMAZ';
@override
String get channels_cyr2latCompression => 'Сжатие Cyr2Lat';
@ -1736,9 +1733,6 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get chat_publicKey => 'Публичный ключ';
@override
String get chat_compressOutgoingMessages => 'Сжимать исходящие сообщения';
@override
String get chat_floodForced => 'Рассылка (принудительно)';

@ -1273,9 +1273,6 @@ class AppLocalizationsSk extends AppLocalizations {
return 'Upraviť kanál $index';
}
@override
String get channels_smazCompression => 'Odstránenie kompresie SMAZ';
@override
String get channels_cyr2latCompression => 'Odstránenie kompresie Cyr2Lat';
@ -1724,9 +1721,6 @@ class AppLocalizationsSk extends AppLocalizations {
@override
String get chat_publicKey => 'Verejný kľúč';
@override
String get chat_compressOutgoingMessages => 'Komprimovať odoslané správy';
@override
String get chat_floodForced => 'Povodňová (nutená)';

@ -1270,9 +1270,6 @@ class AppLocalizationsSl extends AppLocalizations {
return 'Uredi Kanal $index';
}
@override
String get channels_smazCompression => 'Kompresija SMAZ';
@override
String get channels_cyr2latCompression => 'Kompresija Cyr2Lat';
@ -1718,9 +1715,6 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get chat_publicKey => 'Ključ javnega tipa';
@override
String get chat_compressOutgoingMessages => 'Stisnite izhodne sporočila';
@override
String get chat_floodForced => 'Porolni (nasilje).';

@ -1263,9 +1263,6 @@ class AppLocalizationsSv extends AppLocalizations {
return 'Redigera Kanal $index';
}
@override
String get channels_smazCompression => 'SMAZ-komprimering';
@override
String get channels_cyr2latCompression => 'Cyr2Lat-komprimering';
@ -1712,9 +1709,6 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get chat_publicKey => 'Allmänt nyckel';
@override
String get chat_compressOutgoingMessages => 'Kryptera utgående meddelanden';
@override
String get chat_floodForced => 'Översvämning (tvingad)';

@ -1278,9 +1278,6 @@ class AppLocalizationsUk extends AppLocalizations {
return 'Редагувати канал $index';
}
@override
String get channels_smazCompression => 'Стиснення SMAZ';
@override
String get channels_cyr2latCompression => 'Стиснення Cyr2Lat';
@ -1731,9 +1728,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get chat_publicKey => 'Відкритий ключ';
@override
String get chat_compressOutgoingMessages => 'Стискати вихідні повідомлення';
@override
String get chat_floodForced => 'Через всю мережу (примусово)';

@ -1210,9 +1210,6 @@ class AppLocalizationsZh extends AppLocalizations {
return '编辑频道 $index';
}
@override
String get channels_smazCompression => 'SMAZ 压缩';
@override
String get channels_cyr2latCompression => 'Cyr2Lat 压缩';
@ -1630,9 +1627,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get chat_publicKey => '公钥';
@override
String get chat_compressOutgoingMessages => '压缩发送的消息';
@override
String get chat_floodForced => '泛洪(强制)';

@ -390,7 +390,6 @@
}
}
},
"channels_smazCompression": "SMAZ compressie",
"channels_cyr2latCompression": "Cyr2Lat compressie",
"channels_cyr2latCompressionDscr": "Vervangt sommige Cyrillische tekens door Latijnse tekens bij het verzenden.",
"channels_cyr2latSettingsHeading": "Instellingen Cyr2Lat",
@ -607,7 +606,6 @@
"chat_type": "Typen",
"chat_path": "Pad",
"chat_publicKey": "Openbare Sleutel",
"chat_compressOutgoingMessages": "Verzenden van uitgaande berichten comprimeren",
"chat_floodForced": "Flood (afgedwongen)",
"chat_directForced": "Direct (afgedwongen)",
"chat_hopsForced": "{count} hops (afgedwongen)",
@ -1318,7 +1316,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Aantal aanvragen",
"telemetry_error": "Kan gegevens niet ophalen",
"telemetry_noData": "Geen telemetriedata beschikbaar.",

@ -400,7 +400,6 @@
}
}
},
"channels_smazCompression": "Kompresja SMAZ",
"channels_cyr2latCompression": "Kompresja Cyr2Lat",
"channels_cyr2latCompressionDscr": "Zastępuje niektóre znaki cyrylicy alfabetem łacińskim podczas wysyłania.",
"channels_cyr2latSettingsHeading": "Ustawienia Cyr2Lat",
@ -617,7 +616,6 @@
"chat_type": "Typ",
"chat_path": "Ścieżka",
"chat_publicKey": "Klucz Publiczny",
"chat_compressOutgoingMessages": "Kompresuj wychodzące wiadomości",
"chat_floodForced": "Zalew (wymuszony)",
"chat_directForced": "Bezpośrednio (wymuszono)",
"chat_hopsForced": "{count} skoków (wymuszonych)",
@ -1328,7 +1326,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Liczba żądań",
"telemetry_error": "Nie udało się pobrać danych",
"telemetry_noData": "Brak dostępnych danych telemetrycznych.",

@ -390,7 +390,6 @@
}
}
},
"channels_smazCompression": "Compressão SMAZ",
"channels_cyr2latCompression": "Compressão Cyr2Lat",
"channels_cyr2latCompressionDscr": "Substitui alguns caracteres cirílicos por caracteres latinos ao enviar.",
"channels_cyr2latSettingsHeading": "Configuração do Cyr2Lat",
@ -607,7 +606,6 @@
"chat_type": "Digite",
"chat_path": "Caminho",
"chat_publicKey": "Chave Pública",
"chat_compressOutgoingMessages": "Comprimir mensagens enviadas",
"chat_floodForced": "Inundação (forçada)",
"chat_directForced": "Direto (forçado)",
"chat_hopsForced": "{count} saltos (forçado)",
@ -1318,7 +1316,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Número de solicitações",
"telemetry_error": "Não foi possível obter os dados",
"telemetry_noData": "Não estão disponíveis dados de telemetria.",

@ -254,7 +254,6 @@
"channels_pskMustBe32Hex": "PSK должен содержать 32 шестнадцатеричных символа",
"channels_channelAdded": "Канал \"{name}\" добавлен",
"channels_editChannelTitle": "Изменить канал {index}",
"channels_smazCompression": "Сжатие SMAZ",
"channels_cyr2latCompression": "Сжатие Cyr2Lat",
"channels_cyr2latCompressionDscr": "Заменяет некоторые кириллические символы на латиницу при отправке.",
"channels_cyr2latSettingsHeading": "Настройка Cyr2Lat",
@ -369,7 +368,6 @@
"chat_type": "Тип",
"chat_path": "Маршрут",
"chat_publicKey": "Публичный ключ",
"chat_compressOutgoingMessages": "Сжимать исходящие сообщения",
"chat_floodForced": "Рассылка (принудительно)",
"chat_directForced": "Прямой (принудительно)",
"chat_hopsForced": "{count} хоп(ов) (принудительно)",
@ -724,7 +722,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Количество запросов",
"telemetry_error": "Не удалось получить данные",
"neighbors_receivedData": "Полученные данные о соседях",

@ -390,7 +390,6 @@
}
}
},
"channels_smazCompression": "Odstránenie kompresie SMAZ",
"channels_cyr2latCompression": "Odstránenie kompresie Cyr2Lat",
"channels_cyr2latCompressionDscr": "Pri odosielaní nahradí niektoré znaky cyriliky latinskými znakmi.",
"channels_cyr2latSettingsHeading": "Nastavenia Cyr2Lat",
@ -607,7 +606,6 @@
"chat_type": "Napište",
"chat_path": "Cesta",
"chat_publicKey": "Verejný kľúč",
"chat_compressOutgoingMessages": "Komprimovať odoslané správy",
"chat_floodForced": "Povodňová (nutená)",
"chat_directForced": "Priame (donútené)",
"chat_hopsForced": "{count} skokov (nutené)",
@ -1318,7 +1316,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Počet požiadaviek",
"telemetry_error": "Nepodarilo sa získať údaje",
"telemetry_noData": "Nejsú dostupné žiadne údaje z telemetrie.",

@ -390,7 +390,6 @@
}
}
},
"channels_smazCompression": "Kompresija SMAZ",
"channels_cyr2latCompression": "Kompresija Cyr2Lat",
"channels_cyr2latCompressionDscr": "Pri pošiljanju nekatere cirilice nadomesti z latiničnimi.",
"channels_cyr2latSettingsHeading": "Nastavitve Cyr2Lat",
@ -607,7 +606,6 @@
"chat_type": "Vnesite",
"chat_path": "Pot",
"chat_publicKey": "Ključ javnega tipa",
"chat_compressOutgoingMessages": "Stisnite izhodne sporočila",
"chat_floodForced": "Porolni (nasilje).",
"chat_directForced": "Nezglašen (nasilje)",
"chat_hopsForced": "{count} skoki (nasilje)",
@ -1318,7 +1316,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Število zahtev",
"telemetry_error": "Podatkov ni bilo mogoče pridobiti",
"telemetry_noData": "Niso na voljo podatki o telemetriji.",

@ -390,7 +390,6 @@
}
}
},
"channels_smazCompression": "SMAZ-komprimering",
"channels_cyr2latCompression": "Cyr2Lat-komprimering",
"channels_cyr2latCompressionDscr": "Ersätter vissa kyrilliska tecken med latinska tecken när du skickar.",
"channels_cyr2latSettingsHeading": "Inställningar för Cyr2Lat",
@ -607,7 +606,6 @@
"chat_type": "Skriv",
"chat_path": "Sökväg",
"chat_publicKey": "Allmänt nyckel",
"chat_compressOutgoingMessages": "Kryptera utgående meddelanden",
"chat_floodForced": "Översvämning (tvingad)",
"chat_directForced": "Direkt (tvingad)",
"chat_hopsForced": "{count} hopp (tvingat)",
@ -1318,7 +1316,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Antal förfrågningar",
"telemetry_error": "Det gick inte att hämta data",
"telemetry_noData": "Inga telemetridata tillgängliga.",

@ -393,7 +393,6 @@
}
}
},
"channels_smazCompression": "Стиснення SMAZ",
"channels_cyr2latCompression": "Стиснення Cyr2Lat",
"channels_cyr2latCompressionDscr": "Замінює деякі кириличні символи на латиницю при відправці.",
"channels_cyr2latSettingsHeading": "Налаштування Cyr2Lat",
@ -611,7 +610,6 @@
"chat_type": "Ввід",
"chat_path": "Шлях",
"chat_publicKey": "Відкритий ключ",
"chat_compressOutgoingMessages": "Стискати вихідні повідомлення",
"chat_floodForced": "Через всю мережу (примусово)",
"chat_directForced": "Напряму (примусово)",
"chat_hopsForced": "{count} переходів (примусово)",
@ -1329,7 +1327,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Кількість запитів",
"telemetry_error": "Не вдалося отримати дані",
"telemetry_noData": "Дані телеметрії недоступні.",

@ -405,7 +405,6 @@
}
}
},
"channels_smazCompression": "SMAZ 压缩",
"channels_cyr2latCompression": "Cyr2Lat 压缩",
"channels_cyr2latCompressionDscr": "发送时将一些西里尔字符替换为拉丁字符。",
"channels_cyr2latSettingsHeading": "Cyr2Lat 設定",
@ -634,7 +633,6 @@
"chat_type": "类型",
"chat_path": "路径",
"chat_publicKey": "公钥",
"chat_compressOutgoingMessages": "压缩发送的消息",
"chat_floodForced": "泛洪(强制)",
"chat_directForced": "直连(强制)",
"chat_hopsForced": "{count} 跳(强制)",
@ -1348,7 +1346,7 @@
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "请求次数",
"telemetry_error": "无法获取数据",
"telemetry_noData": "暂无遥测数据",

@ -1254,12 +1254,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
hintText: context.l10n.chat_typeMessage,
onSubmitted: (_) => _sendMessage(),
encoder:
(connector.isChannelSmazEnabled(
_currentChannel.index,
) ||
connector.isChannelCyr2LatEnabled(
_currentChannel.index,
))
connector.isChannelCyr2LatEnabled(
_currentChannel.index,
)
? (text) => connector.prepareChannelOutboundText(
_currentChannel.index,
text,

@ -1385,7 +1385,6 @@ class _ChannelsScreenState extends State<ChannelsScreen>
);
final nameController = TextEditingController(text: channel.name);
final pskController = TextEditingController(text: channel.pskHex);
bool smazEnabled = connector.isChannelSmazEnabled(channel.index);
bool cyr2latEnabled = connector.isChannelCyr2LatEnabled(channel.index);
String? selectedCyr2LatProfileId = connector.getChannelCyr2LatProfileId(
channel.index,
@ -1431,17 +1430,6 @@ class _ChannelsScreenState extends State<ChannelsScreen>
),
),
const SizedBox(height: 16),
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(dialogContext.l10n.channels_smazCompression),
value: smazEnabled,
onChanged: (value) => setState(() {
smazEnabled = value;
if (smazEnabled) {
cyr2latEnabled = false;
}
}),
),
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(dialogContext.l10n.channels_cyr2latCompression),
@ -1451,9 +1439,6 @@ class _ChannelsScreenState extends State<ChannelsScreen>
value: cyr2latEnabled,
onChanged: (value) => setState(() {
cyr2latEnabled = value;
if (cyr2latEnabled) {
smazEnabled = false;
}
}),
),
if (cyr2latEnabled) ...[
@ -1508,10 +1493,6 @@ class _ChannelsScreenState extends State<ChannelsScreen>
Navigator.pop(dialogContext);
try {
await connector.setChannel(channel.index, name, psk);
await connector.setChannelSmazEnabled(
channel.index,
smazEnabled,
);
await connector.setChannelCyr2LatEnabled(
channel.index,
cyr2latEnabled,

@ -674,12 +674,9 @@ class _ChatScreenState extends State<ChatScreen> {
hintText: context.l10n.chat_typeMessage,
onSubmitted: (_) => _sendMessage(connector),
encoder:
(connector.isContactSmazEnabled(
widget.contact.publicKeyHex,
) ||
connector.isContactCyr2LatEnabled(
widget.contact.publicKeyHex,
))
connector.isContactCyr2LatEnabled(
widget.contact.publicKeyHex,
)
? (text) => connector.prepareContactOutboundText(
widget.contact,
text,

@ -2,58 +2,14 @@ import '../utils/app_logger.dart';
import 'prefs_manager.dart';
class ChannelSettingsStore {
static const String _keyPrefix = 'channel_smaz_';
static const String _cyr2latKeyPrefix = 'channel_cyr2lat_';
String publicKeyHex = '';
set setPublicKeyHex(String value) =>
publicKeyHex = value.length > 10 ? value.substring(0, 10) : '';
String get keyFor => '$_keyPrefix$publicKeyHex';
String get keyForCyr2Lat => '$_cyr2latKeyPrefix$publicKeyHex';
Future<bool> loadSmazEnabled(int channelIndex) async {
if (publicKeyHex.isEmpty) {
appLogger.warn(
'Public key hex is not set. Cannot load channel settings.',
);
return false;
}
final prefs = PrefsManager.instance;
final key = '$keyFor$channelIndex';
final oldKey = '$_keyPrefix$channelIndex';
bool? enabled = prefs.getBool(key);
if (enabled == null) {
// Attempt migration from legacy unscoped key on first load
// Only touch storage when a legacy key actually exists. This ran
// unconditionally, and on Windows shared_preferences re-serialises and
// rewrites the WHOLE prefs file on every mutation, so removing a key
// that was never there still cost a full multi-MB write. Repeated per
// channel/contact on connect, that blocked the UI isolate for ~38s.
enabled = prefs.getBool(oldKey);
if (enabled != null) {
appLogger.info(
'Migrating channel settings from legacy key $oldKey to scoped key $key',
);
await prefs.setBool(key, enabled);
await prefs.remove(oldKey);
}
}
return enabled ?? false;
}
Future<void> saveSmazEnabled(int channelIndex, bool enabled) 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.setBool(key, enabled);
}
Future<bool> loadCyr2LatEnabled(int channelIndex) async {
if (publicKeyHex.isEmpty) {
appLogger.warn(

@ -2,58 +2,14 @@ import '../utils/app_logger.dart';
import 'prefs_manager.dart';
class ContactSettingsStore {
static const String _keyPrefix = 'contact_smaz_';
static const String _cyr2latKeyPrefix = 'contact_cyr2lat_';
String publicKeyHex = '';
set setPublicKeyHex(String value) =>
publicKeyHex = value.length > 10 ? value.substring(0, 10) : '';
String get keyFor => '$_keyPrefix$publicKeyHex';
String get keyForCyr2Lat => '$_cyr2latKeyPrefix$publicKeyHex';
Future<bool> loadSmazEnabled(String contactKeyHex) async {
if (publicKeyHex.isEmpty) {
appLogger.warn(
'Public key hex is not set. Cannot load contact settings.',
);
return false;
}
final prefs = PrefsManager.instance;
final key = '$keyFor$contactKeyHex';
final oldKey = '$_keyPrefix$contactKeyHex';
bool? enabled = prefs.getBool(key);
if (enabled == null) {
// Attempt migration from legacy unscoped key on first load
// Only touch storage when a legacy key actually exists. This ran
// unconditionally, and on Windows shared_preferences re-serialises and
// rewrites the WHOLE prefs file on every mutation, so removing a key
// that was never there still cost a full multi-MB write. Repeated per
// channel/contact on connect, that blocked the UI isolate for ~38s.
enabled = prefs.getBool(oldKey);
if (enabled != null) {
appLogger.info(
'Migrating contact settings from legacy key $oldKey to scoped key $key',
);
await prefs.setBool(key, enabled);
await prefs.remove(oldKey);
}
}
return prefs.getBool(key) ?? false;
}
Future<void> saveSmazEnabled(String contactKeyHex, bool enabled) async {
if (publicKeyHex.isEmpty) {
appLogger.warn(
'Public key hex is not set. Cannot save contact settings.',
);
return;
}
final prefs = PrefsManager.instance;
final key = '$keyFor$contactKeyHex';
await prefs.setBool(key, enabled);
}
Future<bool> loadCyr2LatEnabled(String contactKeyHex) async {
if (publicKeyHex.isEmpty) {
appLogger.warn(

@ -6,7 +6,7 @@ import '../l10n/l10n.dart';
import '../models/contact.dart';
import '../services/app_settings_service.dart';
/// The per-contact settings dialog (Smaz/Cyr2Lat compression + telemetry
/// The per-contact settings dialog (Cyr2Lat compression + telemetry
/// grants). Shared so it can be opened from the chat ellipsis AND the contacts
/// long-press menu without duplicating the body (#351).
void showContactSettingsDialog(BuildContext context, Contact contact) {
@ -15,9 +15,7 @@ void showContactSettingsDialog(BuildContext context, Contact contact) {
context,
listen: false,
);
connector.ensureContactSmazSettingLoaded(contact.publicKeyHex);
connector.ensureContactCyr2LatSettingLoaded(contact.publicKeyHex);
bool smazEnabled = connector.isContactSmazEnabled(contact.publicKeyHex);
bool cyr2latEnabled = connector.isContactCyr2LatEnabled(contact.publicKeyHex);
String? selectedCyr2LatProfileId = connector.getContactCyr2LatProfileId(
contact.publicKeyHex,
@ -42,26 +40,6 @@ void showContactSettingsDialog(BuildContext context, Contact contact) {
),
const Divider(height: 8),
],
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(context.l10n.channels_smazCompression),
subtitle: Text(context.l10n.chat_compressOutgoingMessages),
value: smazEnabled,
onChanged: (value) {
connector.setContactSmazEnabled(contact.publicKeyHex, value);
connector.setContactCyr2LatEnabled(
contact.publicKeyHex,
false,
);
setDialogState(() {
smazEnabled = value;
if (smazEnabled) {
cyr2latEnabled = false;
}
});
},
),
const Divider(height: 8),
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: Text(context.l10n.channels_cyr2latCompression),
@ -72,12 +50,8 @@ void showContactSettingsDialog(BuildContext context, Contact contact) {
contact.publicKeyHex,
value,
);
connector.setContactSmazEnabled(contact.publicKeyHex, false);
setDialogState(() {
cyr2latEnabled = value;
if (cyr2latEnabled) {
smazEnabled = false;
}
});
},
),

@ -5,10 +5,11 @@ import 'package:meshcore_open/helpers/smaz.dart';
import 'package:meshcore_open/storage/prefs_manager.dart';
import 'package:shared_preferences/shared_preferences.dart';
// #282: the GIF payload is now a URL. Outbound text prep skips Smaz/Cyr2Lat for
// "structured payloads" (it keyed on `g:`/`m:`/`V1|`). A transformed GIF URL
// would arrive as opaque `s:`-prefixed base64 on a stock client, which is worse
// than the raw text this change exists to fix. Pin that it survives intact.
// #420: Smaz send-side compression is removed. Outbound text is no longer
// compressed on any channel/contact (the toggle is gone), so
// prepare*OutboundText passes text through verbatim. Decode is deliberately
// RETAINED (Phase 2 = #421) so messages from lineage peers still sending `s:`,
// and any legacy compressed rows, keep rendering.
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@ -20,25 +21,32 @@ void main() {
await PrefsManager.initialize();
});
test('Smaz leaves the GIF URL intact on a Smaz-enabled channel', () async {
final connector = MeshCoreConnector();
await connector.setChannelSmazEnabled(0, true);
group('outbound text is never compressed (#420)', () {
test('a GIF URL passes through unchanged', () {
final connector = MeshCoreConnector();
final payload = GifHelper.encodeGif(gifId);
final prepared = connector.prepareChannelOutboundText(0, payload);
expect(prepared, payload);
expect(prepared.startsWith('s:'), isFalse);
expect(GifHelper.parseGif(prepared), gifId);
});
final payload = GifHelper.encodeGif(gifId);
final prepared = connector.prepareChannelOutboundText(0, payload);
expect(prepared, payload);
expect(prepared.startsWith('s:'), isFalse);
expect(GifHelper.parseGif(prepared), gifId);
test('a compressible sentence is sent verbatim, not s:-encoded', () {
final connector = MeshCoreConnector();
const sentence = 'hey are you there right now over and to the other end';
final prepared = connector.prepareChannelOutboundText(0, sentence);
expect(prepared, sentence);
expect(prepared.startsWith('s:'), isFalse);
});
});
test('Smaz declines to compress a GIF URL at all', () {
// Why the outbound `g:`/`m:`/`V1|` structured-payload guard did not need
// widening for #282: encodeIfSmaller only swaps in the `s:`+base64 form
// when it is genuinely smaller, and base64 overhead exceeds any dictionary
// gain on a URL. Pinned so a future dictionary change cannot silently start
// compressing GIF URLs into something a stock client cannot read.
final payload = GifHelper.encodeGif(gifId);
expect(Smaz.encodeIfSmaller(payload), payload);
group('receive decode is retained for lineage peers (Phase 2 = #421)', () {
test('an s:-compressed payload still decodes back to plaintext', () {
const sentence = 'hey are you there right now over and to the other end';
final compressed = Smaz.encodeIfSmaller(sentence);
// Guard the guard: the sample must actually compress, else vacuous.
expect(compressed.startsWith('s:'), isTrue);
expect(Smaz.tryDecodePrefixed(compressed), sentence);
});
});
}

Loading…
Cancel
Save

Powered by TurnKey Linux.