diff --git a/docs/architecture/block-contract-as-built.md b/docs/architecture/block-contract-as-built.md index d58f113..5dd001a 100644 --- a/docs/architecture/block-contract-as-built.md +++ b/docs/architecture/block-contract-as-built.md @@ -66,6 +66,7 @@ API: `isBlocked(keyHex)`, `isNameBlocked(name)`, `block(keyHex)`, `unblock(keyHe | Surface | Rule | |---|---| | Incoming DM | drop + suppress notification when `senderKey ∈ blockedKeys` (on Offband, firmware already dropped it at receive — §7) | +| Outgoing DM | **prevent sending** to a blocked contact — the DM composer is replaced by a notice bar with an inline **Unblock** action (you cannot message someone you've blocked without first unblocking). Chat history stays visible. | | Advert | suppress the new-advert notification for `blockedKeys`. **Do NOT discard the advert** — the contact-name update must keep flowing (self-heal, §5) | | Contacts / Discovery lists | **keep blocked entries VISIBLE** with a clear blocked indicator — a red block icon (`Icons.block`, circle-with-slash) + muted/greyed styling. **Do NOT hide them.** Inline unblock available. *(A "hide blocked entirely" toggle is a deliberate post-implementation follow-on, not v1.)* | | Channel message | hide per the resolution rule in §5 | diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6297c90..678b86a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -2602,5 +2602,6 @@ "block_namesSection": "Blocked names", "block_namesHint": "Name-only blocks (channel senders) — each upgrades to a full block once we learn their key.", "block_offloadActive": "Firmware offload active — blocks sync to this radio", - "block_offloadStoreFull": "Radio block list full (32) — extra blocks stay app-only" + "block_offloadStoreFull": "Radio block list full (32) — extra blocks stay app-only", + "block_composerNotice": "You've blocked this contact" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 009b545..ab5a0eb 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -7887,6 +7887,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Radio block list full (32) — extra blocks stay app-only'** String get block_offloadStoreFull; + + /// No description provided for @block_composerNotice. + /// + /// In en, this message translates to: + /// **'You\'ve blocked this contact'** + String get block_composerNotice; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index ba2168b..1dc52be 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -4618,4 +4618,7 @@ class AppLocalizationsBg extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 7809b58..a71083b 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -4636,4 +4636,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 269ecc8..caa4ccd 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -4540,4 +4540,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 3d55e51..f9c59e8 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -4623,4 +4623,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 8e660fd..bb5c9c2 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -4652,4 +4652,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index e46ea06..2bbdc07 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -4639,4 +4639,7 @@ class AppLocalizationsHu extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index b9d938c..7d99e44 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -4628,4 +4628,7 @@ class AppLocalizationsIt extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index d141bc3..9c65198 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -4393,4 +4393,7 @@ class AppLocalizationsJa extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 9e7ab48..4a91d0f 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -4394,4 +4394,7 @@ class AppLocalizationsKo extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 22e965e..a691455 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -4603,4 +4603,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 6b6a168..7c65e59 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -4640,4 +4640,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 84e8866..773f522 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -4616,4 +4616,7 @@ class AppLocalizationsPt extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 81b7558..cf6c583 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -4634,4 +4634,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 5fb717d..0e2ddfa 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -4599,4 +4599,7 @@ class AppLocalizationsSk extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 1b2fae8..d5141a0 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -4597,4 +4597,7 @@ class AppLocalizationsSl extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 2ad010a..24babd5 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -4572,4 +4572,7 @@ class AppLocalizationsSv extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 4e43be4..98e2487 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -4635,4 +4635,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 6fe8981..0652e85 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -4267,4 +4267,7 @@ class AppLocalizationsZh extends AppLocalizations { @override String get block_offloadStoreFull => 'Radio block list full (32) — extra blocks stay app-only'; + + @override + String get block_composerNotice => 'You\'ve blocked this contact'; } diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 677f4b1..1449660 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -591,6 +591,9 @@ class _ChatScreenState extends State { } Widget _buildInputBar(MeshCoreConnector connector) { + if (context.watch().isBlocked(widget.contact.publicKeyHex)) { + return _buildBlockedBar(); + } final maxBytes = maxContactMessageBytes(); final colorScheme = Theme.of(context).colorScheme; final settings = context.watch().settings; @@ -736,6 +739,38 @@ class _ChatScreenState extends State { ); } + Widget _buildBlockedBar() { + final colorScheme = Theme.of(context).colorScheme; + return Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: colorScheme.surface, + border: Border(top: BorderSide(color: Theme.of(context).dividerColor)), + ), + child: SafeArea( + child: Row( + children: [ + const SizedBox(width: 4), + Icon(Icons.block, color: colorScheme.error), + const SizedBox(width: 12), + Expanded( + child: Text( + context.l10n.block_composerNotice, + style: TextStyle(color: colorScheme.onSurfaceVariant), + ), + ), + TextButton( + onPressed: () => context.read().unblock( + widget.contact.publicKeyHex, + ), + child: Text(context.l10n.block_unblock), + ), + ], + ), + ), + ); + } + Future _sendMessage(MeshCoreConnector connector) async { final text = _textController.text.trim(); if (text.isEmpty) return; diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 379e36f..93e4682 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST flserial + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/untranslated.json b/untranslated.json index 269eb2b..2ef710f 100644 --- a/untranslated.json +++ b/untranslated.json @@ -49,7 +49,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "de": [ @@ -102,7 +103,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "es": [ @@ -155,7 +157,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "fr": [ @@ -208,7 +211,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "hu": [ @@ -261,7 +265,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "it": [ @@ -314,7 +319,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "ja": [ @@ -367,7 +373,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "ko": [ @@ -420,7 +427,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "nl": [ @@ -473,7 +481,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "pl": [ @@ -526,7 +535,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "pt": [ @@ -579,7 +589,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "ru": [ @@ -632,7 +643,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "sk": [ @@ -685,7 +697,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "sl": [ @@ -738,7 +751,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "sv": [ @@ -791,7 +805,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "uk": [ @@ -844,7 +859,8 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ], "zh": [ @@ -897,6 +913,7 @@ "block_namesSection", "block_namesHint", "block_offloadActive", - "block_offloadStoreFull" + "block_offloadStoreFull", + "block_composerNotice" ] } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index f02857f..533a171 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -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)