diff --git a/src/ui/windows/chat_tab.cpp b/src/ui/windows/chat_tab.cpp index 6235e41..87f8f74 100644 --- a/src/ui/windows/chat_tab.cpp +++ b/src/ui/windows/chat_tab.cpp @@ -2342,6 +2342,57 @@ void RenderChatTab(App* app) s_delete_cid.clear(); s_delete_name.clear(); }; + // "Delete everywhere": block locally (instant, persistent — no sync flicker) AND broadcast a + // self-addressed Control(del) so other devices + a fresh seed import hide it too. Carries the + // peer z-address as the control label so Restore can match it by address. + auto doDeleteEverywhere = [&]() { + const std::string cid = s_delete_cid, name = s_delete_name; + std::string peerAddr; + for (const auto& m : app->chatService().store().conversation(cid)) { + if (!m.peer_zaddr.empty()) { peerAddr = m.peer_zaddr; break; } + } + if (!app->chatService().deleteConversation(cid, /*block=*/true)) { + Notifications::instance().error(TR("chat_delete_failed")); + s_show_delete_confirm = false; s_delete_cid.clear(); s_delete_name.clear(); + return; + } + if (app->settings()) { + app->settings()->setChatBlocked(cid, name, true); + app->settings()->setChatHidden(cid, false); + app->settings()->save(); + } + app->sendChatControl(cid, "del", peerAddr.empty() ? name : peerAddr); + if (s_selected_cid == cid) s_selected_cid.clear(); + Notifications::instance().info(TR("chat_delete_everywhere_toast")); + s_show_delete_confirm = false; s_delete_cid.clear(); s_delete_name.clear(); + }; + + // Full-width on-chain delete above the local footer, gated on a fully-synced wallet (so a + // conversation can't flicker in/out mid-sync while the delete signal is still catching up). + // Full node requires tip_known (isSynced); lite never sets it, so use its own "sync complete + // + at tip" signal there — otherwise the button would be permanently disabled in lite. + const auto& syncSt = app->state().sync; + const bool synced = app->isLiteBuild() + ? (!syncSt.syncing && syncSt.blocks > 0 && syncSt.blocks >= syncSt.headers - 2) + : syncSt.isSynced(); + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(material::WithAlpha(material::Error(), 205))); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(material::Error())); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::ColorConvertU32ToFloat4(material::WithAlpha(material::Error(), 235))); + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(material::OnError())); + ImGui::BeginDisabled(!synced); + const bool doEvery = material::TactileButton(TR("chat_delete_everywhere"), + ImVec2(ImGui::GetContentRegionAvail().x, 0)); + ImGui::EndDisabled(); + ImGui::PopStyleColor(4); + if (doEvery) doDeleteEverywhere(); + ImGui::Dummy(ImVec2(0, Layout::spacingXs())); + ImGui::PushStyleColor(ImGuiCol_Text, material::OnSurfaceMedium()); + ImGui::PushTextWrapPos(0.0f); + ImGui::TextUnformatted(synced ? TR("chat_delete_everywhere_note") : TR("chat_delete_needs_sync")); + ImGui::PopTextWrapPos(); + ImGui::PopStyleColor(); + ImGui::Dummy(ImVec2(0, Layout::spacingMd())); + auto textW = [&](const char* t) { return ImGui::CalcTextSize(t).x + ImGui::GetStyle().FramePadding.x * 2.0f + 20.0f * dp; }; diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 884ad28..e0f46b9 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -312,6 +312,10 @@ void I18n::loadBuiltinEnglish() strings_["chat_deleted_toast"] = "Conversation deleted"; strings_["chat_delete_failed"] = "Couldn't delete the conversation — nothing was changed."; strings_["chat_blocked_toast"] = "Conversation deleted & blocked"; + strings_["chat_delete_everywhere"] = "Delete everywhere"; + strings_["chat_delete_everywhere_note"] = "Also tells your other devices - and a future restore from your seed phrase - to hide this conversation. It sends a tiny on-chain signal (costs the usual chat fee). You can bring it back later from Restore."; + strings_["chat_delete_everywhere_toast"] = "Deleting everywhere - signal sent"; + strings_["chat_delete_needs_sync"] = "Wait until the wallet is fully synced to delete everywhere."; strings_["chat_blocked_manage"] = "Blocked"; strings_["chat_blocked_title"] = "Blocked conversations"; strings_["chat_blocked_desc"] = "Blocked conversations are removed and their messages are dropped — old and new — until you unblock. Unblocking re-imports the conversation from the chain.";