feat(chat,notifications): first-run chat intro card; clickable toast history + "+N" badge

- Chat: a dismissible first-run intro card (how chat works — encrypted, sent as
  shielded txs with a small fee, tied to the seed) with a "Copy my chat address"
  button, shown in the empty thread pane; dismissal persisted. The copy logic is
  shared with the existing sidebar button (moved to a helper, unchanged).
- Toasts: clicking a toast now opens the existing notification-history panel, and
  a "+N more" badge appears when several live toasts are queued (render only ever
  showed the newest). Badge opacity is a ui.toml token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 23:51:07 -05:00
co-authored by Claude Opus 4.8
parent 2e74a3ca69
commit 794150b11b
7 changed files with 86 additions and 14 deletions
+40 -11
View File
@@ -591,6 +591,17 @@ void centeredHint(const char* text) {
}
// Centered empty state: big muted icon + title + optional wrapped hint (V4).
// Copy the user's own chat address to the clipboard (shared by the sidebar button and the intro card).
void copyMyChatAddress(App* app) {
std::string myAddr = app->chatMyAddress();
if (myAddr.empty()) {
Notifications::instance().warning(TR("chat_no_my_address"));
} else {
ImGui::SetClipboardText(myAddr.c_str());
Notifications::instance().info(TR("address_copied"));
}
}
void centeredEmptyState(const char* icon, const char* title, const char* hint) {
const ImVec2 avail = ImGui::GetContentRegionAvail();
const ImVec2 origin = ImGui::GetCursorPos();
@@ -956,15 +967,7 @@ void RenderChatTab(App* app)
{
ImGui::PushStyleColor(ImGuiCol_Text, material::OnSurfaceMedium());
// Text-only: SmallButton uses the body font, which has no Material icon glyph (would show "?").
if (ImGui::SmallButton(TR("chat_copy_my_address"))) {
std::string myAddr = app->chatMyAddress();
if (myAddr.empty()) {
Notifications::instance().warning(TR("chat_no_my_address"));
} else {
ImGui::SetClipboardText(myAddr.c_str());
Notifications::instance().info(TR("address_copied"));
}
}
if (ImGui::SmallButton(TR("chat_copy_my_address"))) copyMyChatAddress(app);
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("chat_my_address_tip"));
}
@@ -1875,9 +1878,35 @@ void RenderChatTab(App* app)
ImGui::EndChild();
ImGui::PopStyleVar(2); // ##ChatMessages WindowPadding + zeroed vertical ItemSpacing
// Composer is rendered OUTSIDE this box, directly below it (see after EndChild).
} else if (convs.empty() && app->settings() && !app->settings()->getChatIntroDismissed()) {
// First-run intro: explain how chat works + let the user copy their address, instead of a bare
// "no conversations yet" line. Dismissal is persisted.
const float idp = Layout::dpiScale();
const float availX = ImGui::GetContentRegionAvail().x;
const float cardW = std::min(availX - 40.0f * idp, 460.0f * idp);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + std::max(0.0f, (availX - cardW) * 0.5f));
ImGui::BeginGroup();
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + cardW);
material::Type().textColored(material::TypeStyle::Subtitle1, material::OnSurface(), TR("chat_intro_title"));
ImGui::Spacing();
ImGui::PushStyleColor(ImGuiCol_Text, material::OnSurfaceMedium());
ImGui::TextWrapped("%s", TR("chat_intro_body"));
ImGui::Spacing();
ImGui::TextWrapped("%s", TR("chat_intro_fee"));
ImGui::PopStyleColor();
ImGui::PopTextWrapPos();
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
if (material::TactileButton(TR("chat_copy_my_address"), ImVec2(cardW, 0))) copyMyChatAddress(app);
ImGui::Spacing();
if (material::TactileButton(TR("chat_intro_dismiss"), ImVec2(cardW, 0))) {
app->settings()->setChatIntroDismissed(true);
app->settings()->save();
}
ImGui::EndGroup();
} else if (convs.empty()) {
centeredEmptyState(ICON_MD_FORUM, TR("chat_empty_title"), TR("chat_empty_start"));
} else {
if (convs.empty()) centeredEmptyState(ICON_MD_FORUM, TR("chat_empty_title"), TR("chat_empty_start"));
else centeredEmptyState(ICON_MD_CHAT_BUBBLE_OUTLINE, TR("chat_select_hint"), nullptr);
centeredEmptyState(ICON_MD_CHAT_BUBBLE_OUTLINE, TR("chat_select_hint"), nullptr);
}
}
ImGui::EndChild();