feat(#131): channel reply-UX — composer focus + route reply (#106)

- Reply long-press drops the cursor into the composer (post-frame focus). (#131)
- "Reply with route" sends `@[sender] ↩ N hops · via <path>`, byte-truncated with `…`, Cyr2Lat-aware. (#106)

Gemini (fix-then-ship): the unawaited `sendChannelMessage` in `_sendRouteReply` mirrors the established `_sendMessage` convention — failure surfaces via the visible message's delivery status, not the returned Future; holistic channel-send error handling is tracked in #133 per maintainer decision. O(n^2) path truncation is negligible on the <=160-byte payload; the `w<1` guard is a defensive div-by-zero (pathHashByteWidth is 1-3).

Closes #131
Closes #106
Part of #132

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull/143/head
Strycher 4 weeks ago
parent 8a8def7d3f
commit cd7794b69f

@ -702,6 +702,7 @@
}, },
"chat_sendGif": "Send GIF", "chat_sendGif": "Send GIF",
"chat_reply": "Reply", "chat_reply": "Reply",
"chat_replyWithRoute": "Reply with route",
"chat_addReaction": "Add Reaction", "chat_addReaction": "Add Reaction",
"chat_me": "Me", "chat_me": "Me",
"emojiCategorySmileys": "Smileys", "emojiCategorySmileys": "Smileys",

@ -2536,6 +2536,12 @@ abstract class AppLocalizations {
/// **'Reply'** /// **'Reply'**
String get chat_reply; String get chat_reply;
/// No description provided for @chat_replyWithRoute.
///
/// In en, this message translates to:
/// **'Reply with route'**
String get chat_replyWithRoute;
/// No description provided for @chat_addReaction. /// No description provided for @chat_addReaction.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

@ -1380,6 +1380,9 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get chat_reply => 'Отговори'; String get chat_reply => 'Отговори';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Добави Реакция'; String get chat_addReaction => 'Добави Реакция';

@ -1379,6 +1379,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get chat_reply => 'Beantworten'; String get chat_reply => 'Beantworten';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Reaktion hinzufügen'; String get chat_addReaction => 'Reaktion hinzufügen';

@ -1351,6 +1351,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get chat_reply => 'Reply'; String get chat_reply => 'Reply';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Add Reaction'; String get chat_addReaction => 'Add Reaction';

@ -1378,6 +1378,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get chat_reply => 'Responder'; String get chat_reply => 'Responder';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Añadir Reacción'; String get chat_addReaction => 'Añadir Reacción';

@ -1383,6 +1383,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get chat_reply => 'Répondre'; String get chat_reply => 'Répondre';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Ajouter une Réaction'; String get chat_addReaction => 'Ajouter une Réaction';

@ -1385,6 +1385,9 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get chat_reply => 'Válasz'; String get chat_reply => 'Válasz';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Hozzon létre reakciót'; String get chat_addReaction => 'Hozzon létre reakciót';

@ -1380,6 +1380,9 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get chat_reply => 'Rispondi'; String get chat_reply => 'Rispondi';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Aggiungi Reazione'; String get chat_addReaction => 'Aggiungi Reazione';

@ -1316,6 +1316,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get chat_reply => '返信'; String get chat_reply => '返信';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => '反応を追加'; String get chat_addReaction => '反応を追加';

@ -1311,6 +1311,9 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get chat_reply => '답변'; String get chat_reply => '답변';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => '댓글 추가'; String get chat_addReaction => '댓글 추가';

@ -1367,6 +1367,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get chat_reply => 'Reageren'; String get chat_reply => 'Reageren';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Reactie toevoegen'; String get chat_addReaction => 'Reactie toevoegen';

@ -1390,6 +1390,9 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get chat_reply => 'Odpowiedz'; String get chat_reply => 'Odpowiedz';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Dodaj Reakcję'; String get chat_addReaction => 'Dodaj Reakcję';

@ -1377,6 +1377,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get chat_reply => 'Responder'; String get chat_reply => 'Responder';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Adicionar Reação'; String get chat_addReaction => 'Adicionar Reação';

@ -1378,6 +1378,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get chat_reply => 'Ответить'; String get chat_reply => 'Ответить';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Добавить реакцию'; String get chat_addReaction => 'Добавить реакцию';

@ -1367,6 +1367,9 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get chat_reply => 'Odpovedať'; String get chat_reply => 'Odpovedať';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Pridať Reakciu'; String get chat_addReaction => 'Pridať Reakciu';

@ -1365,6 +1365,9 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get chat_reply => 'Odgovori'; String get chat_reply => 'Odgovori';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Dodaj reakcijo'; String get chat_addReaction => 'Dodaj reakcijo';

@ -1359,6 +1359,9 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get chat_reply => 'Svara'; String get chat_reply => 'Svara';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Lägg till reaktion'; String get chat_addReaction => 'Lägg till reaktion';

@ -1372,6 +1372,9 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get chat_reply => 'Відповісти'; String get chat_reply => 'Відповісти';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => 'Додати реакцію'; String get chat_addReaction => 'Додати реакцію';

@ -1297,6 +1297,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get chat_reply => '回复'; String get chat_reply => '回复';
@override
String get chat_replyWithRoute => 'Reply with route';
@override @override
String get chat_addReaction => '添加表情'; String get chat_addReaction => '添加表情';

@ -185,6 +185,11 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
setState(() { setState(() {
_replyingToMessage = message; _replyingToMessage = message;
}); });
// Drop the cursor straight into the composer (the reply banner has just
// laid out, so focus after the frame). Saves a click/tab on reply. (#131)
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _textFieldFocusNode.requestFocus();
});
} }
void _cancelReply() { void _cancelReply() {
@ -1219,6 +1224,34 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
); );
} }
/// One-click reply quoting the route a message took hop count + path,
/// truncated with to fit the channel byte budget, fired directly. (#106)
void _sendRouteReply(ChannelMessage message) {
final connector = context.read<MeshCoreConnector>();
final w = connector.pathHashByteWidth;
final hops = message.pathBytes.length ~/ (w < 1 ? 1 : w);
final prefix =
'@[${message.senderName}] ↩ ${context.l10n.chat_hopsCount(hops)} · via ';
final maxBytes = maxChannelMessageBytes(connector.selfName);
var path = _formatPathPrefixes(message.pathBytes, w);
var truncated = false;
String outbound() => connector.prepareChannelOutboundText(
widget.channel.index,
'$prefix$path${truncated ? '' : ''}',
);
while (path.isNotEmpty && utf8.encode(outbound()).length > maxBytes) {
path = path.substring(0, path.length - 1);
truncated = true;
}
var text = '$prefix$path${truncated ? '' : ''}';
if (connector.isChannelCyr2LatEnabled(widget.channel.index)) {
text = Cyr2Lat.encode(text);
}
connector.sendChannelMessage(widget.channel, text);
}
void _ensureDateFormats(BuildContext context) { void _ensureDateFormats(BuildContext context) {
final locale = Localizations.localeOf(context).toString(); final locale = Localizations.localeOf(context).toString();
final clock = context.read<AppSettingsService>().settings.clockFormat; final clock = context.read<AppSettingsService>().settings.clockFormat;
@ -1394,6 +1427,15 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
_setReplyingTo(message); _setReplyingTo(message);
}, },
), ),
if (message.pathBytes.isNotEmpty)
ListTile(
leading: const Icon(Icons.alt_route),
title: Text(context.l10n.chat_replyWithRoute),
onTap: () {
Navigator.pop(sheetContext);
_sendRouteReply(message);
},
),
if (PlatformInfo.isDesktop) if (PlatformInfo.isDesktop)
ListTile( ListTile(
leading: const Icon(Icons.route), leading: const Icon(Icons.route),

@ -6,6 +6,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -17,6 +18,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -28,6 +30,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -39,6 +42,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -50,6 +54,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -61,6 +66,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -72,6 +78,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -83,6 +90,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -94,6 +102,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -105,6 +114,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -116,6 +126,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -127,6 +138,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -138,6 +150,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -149,6 +162,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -160,6 +174,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -171,6 +186,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
], ],
@ -182,6 +198,7 @@
"appSettings_clockSystem", "appSettings_clockSystem",
"appSettings_clock12h", "appSettings_clock12h",
"appSettings_clock24h", "appSettings_clock24h",
"chat_replyWithRoute",
"channel_heardTooltip", "channel_heardTooltip",
"snrIndicator_noNeighbors" "snrIndicator_noNeighbors"
] ]

Loading…
Cancel
Save

Powered by TurnKey Linux.