feat(lite): first-run welcome prompt (create / restore)

Replace the bare "land on main UI with a No-wallet overlay" first-run with a
lite welcome modal, shown when no wallet file exists yet (lite_wallet_ present,
not open, walletExists() false):

- "Create new wallet" — one-click createWallet({}); on success, notifies the user
  to back up their recovery phrase and navigates to Settings (Backup & keys),
  where the seed can be revealed/copied via the existing backup UI.
- "Restore from seed" — navigates to Settings (Lite wallet request → Restore).
- "Later" — dismiss for the session.

Routes to the already-built + verified create/restore/backup flows rather than
re-implementing seed display in the modal (no new secret-handling surface).
Dismissed once an action is chosen; never shown again once a wallet exists.
Full-node is unaffected (renderLiteFirstRunPrompt() returns early when
lite_wallet_ is null). English i18n built-ins added.

Verified: fresh-HOME lite launch shows the prompt, clean run + shutdown, no
crash/RPC noise; tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 17:38:32 -05:00
parent f511c0d509
commit 4f7a4fb38e
3 changed files with 71 additions and 1 deletions

View File

@@ -1394,7 +1394,10 @@ void App::render()
if (show_about_) {
ui::RenderAboutDialog(this, &show_about_);
}
// Lite first-run welcome: prompt to create/restore when no wallet file exists yet.
renderLiteFirstRunPrompt();
if (show_import_key_) {
renderImportKeyDialog();
}
@@ -1785,6 +1788,61 @@ void App::reloadThemeImages(const std::string& bgPath, const std::string& logoPa
coin_logo_h_ = 0;
}
void App::renderLiteFirstRunPrompt()
{
// Lite-only: a brief welcome shown when no wallet file exists yet (full-node uses the wizard,
// which is skipped in lite). Routes to the existing create/restore + Backup flows in Settings.
if (!lite_wallet_ || lite_firstrun_dismissed_) return;
if (lite_wallet_->walletOpen() || lite_wallet_->walletExists()) return;
if (!ImGui::IsPopupOpen("##LiteFirstRun")) ImGui::OpenPopup("##LiteFirstRun");
ImGuiViewport* vp = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(ImVec2(vp->Pos.x + vp->Size.x * 0.5f, vp->Pos.y + vp->Size.y * 0.5f),
ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("##LiteFirstRun", nullptr,
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar)) {
ImGui::PushFont(ui::material::Type().subtitle1());
ImGui::TextUnformatted(TR("lite_welcome_title"));
ImGui::PopFont();
ImGui::Spacing();
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 360.0f);
ImGui::TextUnformatted(TR("lite_welcome_msg"));
ImGui::PopTextWrapPos();
ImGui::Spacing();
ImGui::Spacing();
const float btnW = 170.0f;
if (ImGui::Button(TR("lite_welcome_create"), ImVec2(btnW, 0))) {
// One-click create (unencrypted by default); the user backs up the seed next via the
// Settings → Backup & keys flow, where the seed can be revealed and copied.
const auto result = lite_wallet_->createWallet(wallet::LiteWalletCreateRequest{});
if (result.walletReady) {
ui::Notifications::instance().success(TR("lite_welcome_created"), 8.0f);
setCurrentPage(ui::NavPage::Settings);
} else {
ui::Notifications::instance().warning(TR("lite_welcome_create_failed"));
}
lite_firstrun_dismissed_ = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button(TR("lite_welcome_restore"), ImVec2(btnW, 0))) {
ui::Notifications::instance().info(TR("lite_welcome_restore_hint"), 8.0f);
setCurrentPage(ui::NavPage::Settings);
lite_firstrun_dismissed_ = true;
ImGui::CloseCurrentPopup();
}
ImGui::Spacing();
if (ImGui::Button(TR("lite_welcome_later"),
ImVec2(btnW * 2 + ImGui::GetStyle().ItemSpacing.x, 0))) {
lite_firstrun_dismissed_ = true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void App::renderAboutDialog()
{
auto dlg = ui::schema::UI().drawElement("inline-dialogs", "about");