feat: update outbound message status handling with method-aware icons and titles, update localization for delivery statuses

This commit is contained in:
Ivan 2026-07-21 16:20:02 -05:00
parent 50e3d6004a
commit 4f6bccea18
No known key found for this signature in database
13 changed files with 104 additions and 12 deletions

Binary file not shown.

View file

@ -309,17 +309,17 @@
</button>
<MaterialDesignIcon
v-if="entry.items[0].lxmf_message.state === 'delivered'"
icon-name="check-all"
:icon-name="cv.outboundBubbleStatusIconName(entry.items[0].lxmf_message)"
class="size-3"
:class="cv.outboundBubbleDeliveredIconClass(entry.items[0])"
title="Delivered"
:title="cv.outboundBubbleStatusTitle(entry.items[0].lxmf_message)"
/>
<MaterialDesignIcon
v-else-if="['sent', 'propagated', 'unknown'].includes(entry.items[0].lxmf_message.state)"
icon-name="check"
:icon-name="cv.outboundBubbleStatusIconName(entry.items[0].lxmf_message)"
class="size-3"
:class="cv.outboundBubbleSentCheckIconClass(entry.items[0])"
:title="cv.outboundSentStatusTitle(entry.items[0].lxmf_message)"
:title="cv.outboundBubbleStatusTitle(entry.items[0].lxmf_message)"
/>
<svg
v-if="cv.showRichOutboundPendingUi(entry.items[0]) && cv.isOutboundPendingForUi(entry.items[0])"
@ -545,13 +545,15 @@
<template v-if="chatItem.is_outbound">
<MaterialDesignIcon
v-if="chatItem.lxmf_message.state === 'delivered'"
icon-name="check-all"
:icon-name="cv.outboundBubbleStatusIconName(chatItem.lxmf_message)"
class="size-3 opacity-50"
:title="cv.outboundBubbleStatusTitle(chatItem.lxmf_message)"
/>
<MaterialDesignIcon
v-else-if="['sent', 'propagated', 'unknown'].includes(chatItem.lxmf_message.state)"
icon-name="check"
:icon-name="cv.outboundBubbleStatusIconName(chatItem.lxmf_message)"
class="size-3 opacity-50"
:title="cv.outboundBubbleStatusTitle(chatItem.lxmf_message)"
/>
<span
v-else-if="['failed', 'cancelled', 'rejected'].includes(chatItem.lxmf_message.state)"
@ -1240,21 +1242,21 @@
<MaterialDesignIcon icon-name="refresh" class="size-3 text-white" />
</button>
<!-- delivered: double check -->
<!-- delivered / sent: method-aware status icon -->
<MaterialDesignIcon
v-if="chatItem.lxmf_message.state === 'delivered'"
icon-name="check-all"
:icon-name="cv.outboundBubbleStatusIconName(chatItem.lxmf_message)"
class="size-3"
:class="cv.outboundBubbleDeliveredIconClass(chatItem)"
title="Delivered"
:title="cv.outboundBubbleStatusTitle(chatItem.lxmf_message)"
/>
<!-- sent: single check (include unknown for initial outbound when server confirmed creation) -->
<!-- sent: include unknown for initial outbound when server confirmed creation -->
<MaterialDesignIcon
v-else-if="['sent', 'propagated', 'unknown'].includes(chatItem.lxmf_message.state)"
icon-name="check"
:icon-name="cv.outboundBubbleStatusIconName(chatItem.lxmf_message)"
class="size-3"
:class="cv.outboundBubbleSentCheckIconClass(chatItem)"
:title="cv.outboundSentStatusTitle(chatItem.lxmf_message)"
:title="cv.outboundBubbleStatusTitle(chatItem.lxmf_message)"
/>
<svg
v-if="cv.showRichOutboundPendingUi(chatItem) && cv.isOutboundPendingForUi(chatItem)"

View file

@ -4968,9 +4968,44 @@ export default {
return this.$t("messages.outbound_pending");
},
outboundSentStatusTitle(lxmfMessage) {
return this.outboundBubbleStatusTitle(lxmfMessage);
},
outboundBubbleStatusIconName(lxmfMessage) {
if (!lxmfMessage) {
return "check";
}
const state = lxmfMessage.state;
const method = lxmfMessage.method;
if (state === "delivered") {
if (method === "propagated") {
return "email-check-outline";
}
if (method === "paper") {
return "note-check-outline";
}
return "check-all";
}
if (["sent", "propagated", "unknown"].includes(state)) {
if (method === "propagated") {
return "email-outline";
}
if (method === "paper") {
return "note-outline";
}
return "check";
}
return "check";
},
outboundBubbleStatusTitle(lxmfMessage) {
if (!lxmfMessage) {
return "";
}
if (lxmfMessage.state === "delivered") {
if (lxmfMessage.method === "propagated") {
return this.$t("messages.outbound_delivered_propagated");
}
return this.$t("messages.outbound_delivered");
}
if (lxmfMessage.method === "propagated") {
return this.$t("messages.outbound_on_propagation_node");
}

View file

@ -1669,6 +1669,8 @@
"send_pathfinding_tooltip": "Pfad zum Peer wird gesucht (Reticulum ermittelt eine Route). Ihre Nachricht wird gesendet, sobald die Route bereit ist. Fahren Sie für Details mit der Maus über die Statussymbole in der Sprechblase.",
"outbound_on_propagation_node": "Auf dem Propagationsknoten",
"outbound_sent_network": "Gesendet",
"outbound_delivered": "Zugestellt",
"outbound_delivered_propagated": "Über Propagation zugestellt",
"more_actions": "Weitere Aktionen",
"retry_failed": "Fehlgeschlagene erneut senden",
"telemetry_history": "Telemetrie-Verlauf",

View file

@ -1637,6 +1637,8 @@
"send_pathfinding_tooltip": "Finding path to peer (Reticulum is resolving a route). Your message sends as soon as the route is ready. Hover status icons in the bubble for details.",
"outbound_on_propagation_node": "On propagation node",
"outbound_sent_network": "Sent",
"outbound_delivered": "Delivered",
"outbound_delivered_propagated": "Delivered via propagation",
"no_messages_in_conversation": "No messages in this conversation yet.",
"say_hello": "Say hello!",
"no_active_chat": "No Active Chat",

View file

@ -1637,6 +1637,8 @@
"send_pathfinding_tooltip": "Buscando ruta al par (Reticulum está resolviendo una ruta). Su mensaje se enviará en cuanto la ruta esté lista. Pase el cursor sobre los iconos de estado en la burbuja para más detalles.",
"outbound_on_propagation_node": "En el nodo de propagación",
"outbound_sent_network": "Enviado",
"outbound_delivered": "Entregado",
"outbound_delivered_propagated": "Entregado vía propagación",
"no_messages_in_conversation": "Aún no hay mensajes en esta conversación.",
"say_hello": "¡Saluda!",
"no_active_chat": "Sin chat activo",

View file

@ -1637,6 +1637,8 @@
"send_pathfinding_tooltip": "Etsitään polkua vertaisen luo (Reticulum etsii reittiä). Viestisi lähetetään heti kun polku on valmis. Kohdista kuplan tilakuvakkeisiin lisätietoja varten.",
"outbound_on_propagation_node": "Levityssolmussa",
"outbound_sent_network": "Lähetetty",
"outbound_delivered": "Toimitettu",
"outbound_delivered_propagated": "Toimitettu levityksen kautta",
"no_messages_in_conversation": "Tässä keskustelussa ei ole viestejä.",
"say_hello": "Sano hei!",
"no_active_chat": "Ei valittua keskustelua",

View file

@ -1637,6 +1637,8 @@
"send_pathfinding_tooltip": "Recherche d'itinéraire vers le pair (Reticulum résout une route). Votre message sera envoyé dès que la route sera prête. Survolez les icônes d'état dans la bulle pour plus de détails.",
"outbound_on_propagation_node": "Sur le nœud de propagation",
"outbound_sent_network": "Envoyé",
"outbound_delivered": "Distribué",
"outbound_delivered_propagated": "Distribué via propagation",
"no_messages_in_conversation": "Pas encore de messages dans cette conversation.",
"say_hello": "Dis bonjour !",
"no_active_chat": "Pas de chat actif",

View file

@ -1669,6 +1669,8 @@
"send_pathfinding_tooltip": "Ricerca percorso verso il peer (Reticulum sta risolvendo un percorso). Il messaggio verrà inviato non appena il percorso sarà pronto. Passa il mouse sulle icone di stato nella bolla per i dettagli.",
"outbound_on_propagation_node": "Sul nodo di propagazione",
"outbound_sent_network": "Inviato",
"outbound_delivered": "Consegnato",
"outbound_delivered_propagated": "Consegnato via propagazione",
"more_actions": "Altre azioni",
"retry_failed": "Riprova messaggi non inviati",
"telemetry_history": "Cronologia telemetria",

View file

@ -1637,6 +1637,8 @@
"send_pathfinding_tooltip": "Pad naar peer zoeken (Reticulum zoekt een route). Uw bericht wordt verzonden zodra de route klaar is. Beweeg de muis over de statuspictogrammen in de bubbel voor details.",
"outbound_on_propagation_node": "Op propagatieknooppunt",
"outbound_sent_network": "Verzonden",
"outbound_delivered": "Afgeleverd",
"outbound_delivered_propagated": "Afgeleverd via propagatie",
"no_messages_in_conversation": "Nog geen berichten in dit gesprek.",
"say_hello": "Zeg hallo!",
"no_active_chat": "Geen actief gesprek",

View file

@ -1669,6 +1669,8 @@
"send_pathfinding_tooltip": "Поиск пути к узлу (Reticulum определяет маршрут). Сообщение будет отправлено, как только маршрут будет готов. Наведите курсор на значки статуса в пузыре для подробностей.",
"outbound_on_propagation_node": "На узле распространения",
"outbound_sent_network": "Отправлено",
"outbound_delivered": "Доставлено",
"outbound_delivered_propagated": "Доставлено через распространение",
"more_actions": "Другие действия",
"retry_failed": "Повторить неудачные",
"telemetry_history": "История телеметрии",

View file

@ -1637,6 +1637,8 @@
"send_pathfinding_tooltip": "正在查找到节点的路径Reticulum 正在解析路由)。路由就绪后消息将立即发送。将鼠标悬停在气泡中的状态图标上可查看详情。",
"outbound_on_propagation_node": "已在传播节点",
"outbound_sent_network": "已发送",
"outbound_delivered": "已送达",
"outbound_delivered_propagated": "已通过传播送达",
"no_messages_in_conversation": "对话中尚无消息",
"say_hello": "打个招呼!",
"no_active_chat": "无活动聊天",

View file

@ -79,6 +79,43 @@ describe("ConversationViewer outbound propagation status", () => {
expect(wrapper.vm.outboundSentStatusTitle(null)).toBe("");
});
it("outboundBubbleStatusTitle uses delivered and method-aware copy", () => {
const wrapper = mountViewer();
expect(wrapper.vm.outboundBubbleStatusTitle({ method: "direct", state: "delivered" })).toBe(
"messages.outbound_delivered"
);
expect(wrapper.vm.outboundBubbleStatusTitle({ method: "propagated", state: "delivered" })).toBe(
"messages.outbound_delivered_propagated"
);
expect(wrapper.vm.outboundBubbleStatusTitle({ method: "propagated", state: "sent" })).toBe(
"messages.outbound_on_propagation_node"
);
expect(wrapper.vm.outboundBubbleStatusTitle({ method: "paper", state: "sent" })).toBe(
"messages.outbound_sent_network"
);
expect(wrapper.vm.outboundBubbleStatusTitle(null)).toBe("");
});
it("outboundBubbleStatusIconName maps method and state to icons", () => {
const wrapper = mountViewer();
expect(wrapper.vm.outboundBubbleStatusIconName({ method: "direct", state: "sent" })).toBe("check");
expect(wrapper.vm.outboundBubbleStatusIconName({ method: "direct", state: "delivered" })).toBe("check-all");
expect(wrapper.vm.outboundBubbleStatusIconName({ method: "opportunistic", state: "sent" })).toBe("check");
expect(wrapper.vm.outboundBubbleStatusIconName({ method: "opportunistic", state: "delivered" })).toBe(
"check-all"
);
expect(wrapper.vm.outboundBubbleStatusIconName({ method: "propagated", state: "sent" })).toBe("email-outline");
expect(wrapper.vm.outboundBubbleStatusIconName({ method: "propagated", state: "delivered" })).toBe(
"email-check-outline"
);
expect(wrapper.vm.outboundBubbleStatusIconName({ method: "paper", state: "sent" })).toBe("note-outline");
expect(wrapper.vm.outboundBubbleStatusIconName({ method: "paper", state: "delivered" })).toBe(
"note-check-outline"
);
expect(wrapper.vm.outboundBubbleStatusIconName({ method: "direct", state: "unknown" })).toBe("check");
expect(wrapper.vm.outboundBubbleStatusIconName(null)).toBe("check");
});
it("outboundTransferProgressPercent and label track resource transfer", () => {
const wrapper = mountViewer();
expect(wrapper.vm.outboundTransferProgressPercent({ state: "sending", progress: 42.5 })).toBe(43);