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:
+40
-11
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user