diff --git a/src/ui/windows/explorer_tab.cpp b/src/ui/windows/explorer_tab.cpp index 021a757..7ce7940 100644 --- a/src/ui/windows/explorer_tab.cpp +++ b/src/ui/windows/explorer_tab.cpp @@ -269,7 +269,7 @@ static void fetchBlockDetail(App* app, int height) { auto hashResult = rpc->call("getblockhash", {height}); std::string hash = hashResult.get(); result = rpc->call("getblock", {hash}); - } catch (const std::exception& e) { error = e.what(); } + } catch (const std::exception& e) { error = util::friendlyRpcError(e.what()); } return [result, error]() { applyBlockDetailResult(result, error); }; @@ -383,19 +383,27 @@ static void navigateToHash(App* app, const std::string& hash) { worker->post([rpc, hash]() -> rpc::RPCWorker::MainCb { json blockResult, txResult; bool gotBlock = false, gotTx = false; + bool rpcFailed = false; std::string rpcErr; + // Distinguish a genuine "not found" (RPC -5/-8) from a transport/timeout failure, so a flaky + // connection isn't reported as "no block or transaction found". + auto noteErr = [&](const std::exception& e) { + const auto* re = dynamic_cast(&e); + if (re && (re->code == -5 || re->code == -8)) return; // not-found: a real negative answer + if (!rpcFailed) { rpcFailed = true; rpcErr = e.what(); } + }; try { rpc::RPCClient::TraceScope trace("Explorer tab / Search"); blockResult = rpc->call("getblock", {hash}); gotBlock = true; - } catch (...) {} + } catch (const std::exception& e) { noteErr(e); } if (!gotBlock) { try { rpc::RPCClient::TraceScope trace("Explorer tab / Search"); txResult = rpc->call("getrawtransaction", {hash, 1}); gotTx = true; - } catch (...) {} + } catch (const std::exception& e) { noteErr(e); } } - return [blockResult, txResult, gotBlock, gotTx]() { + return [blockResult, txResult, gotBlock, gotTx, rpcFailed, rpcErr]() { s_search_loading = false; if (gotBlock) { int h = blockResult.value("height", -1); @@ -407,6 +415,8 @@ static void navigateToHash(App* app, const std::string& hash) { s_tx_loading = false; s_expanded_tx_idx = 0; s_show_detail_modal = false; + } else if (rpcFailed) { + s_search_error = std::string(TR("explorer_lookup_failed")) + ": " + util::friendlyRpcError(rpcErr); } else { s_search_error = TR("explorer_hash_not_found"); } diff --git a/src/ui/windows/key_export_dialog.cpp b/src/ui/windows/key_export_dialog.cpp index e1c5adf..6809de7 100644 --- a/src/ui/windows/key_export_dialog.cpp +++ b/src/ui/windows/key_export_dialog.cpp @@ -121,7 +121,7 @@ void KeyExportDialog::render(App* app) ImGui::Text("%s", key_label); if (s_fetching) { - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "%s", TR("key_export_fetching")); + ImGui::TextColored(material::WarningVec4(), "%s%s", TR("key_export_fetching_base"), material::LoadingDots()); } else if (!s_error.empty()) { ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), TR("error_format"), s_error.c_str()); // Not a dead-end: clearing the error falls back through to the Reveal button next frame. @@ -133,6 +133,16 @@ void KeyExportDialog::render(App* app) // Guard against an empty/blatantly-malformed address before dispatching a doomed reveal. // (Also avoids reading s_address[0] on an empty string in the Reveal branch below.) ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%s", TR("invalid_address")); + } else if (s_key.empty() && app->getWalletState().isLocked()) { + // Reveal would just fail with a locked-wallet RPC error — say so and offer to unlock. + ImGui::PushStyleColor(ImGuiCol_Text, material::Warning()); + ImGui::TextWrapped("%s", TR("key_export_unlock_first")); + ImGui::PopStyleColor(); + ImGui::Spacing(); + if (material::TactileButton(TR("lite_unlock"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) { + if (app->liteWallet()) app->requestLiteUnlock(); + else s_open = false; // full node: closing reveals the lock screen already shown while locked + } } else if (s_key.empty()) { // Show button to fetch key if (material::TactileButton(TR("key_export_reveal"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) { @@ -173,7 +183,7 @@ void KeyExportDialog::render(App* app) s_key = found; s_show_key = wantViewing; // viewing keys are less sensitive } else { - s_error = r.ok ? std::string(TR("grpc_key_not_available")) : r.error; + s_error = r.ok ? std::string(TR("grpc_key_not_available")) : util::friendlyRpcError(r.error); } wallet::secureWipeLiteSecret(found); s_fetching = false; diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index e905388..99fff58 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -268,6 +268,10 @@ void I18n::loadBuiltinEnglish() strings_["chat_retry"] = "Retry"; strings_["chat_jump_latest"] = "Latest"; strings_["chat_empty_title"] = "No conversations yet"; + strings_["chat_intro_title"] = "How chat works"; + strings_["chat_intro_body"] = "Messages are end-to-end encrypted and sent as shielded transactions, so only you and your contact can read them. Your chat identity is tied to your wallet seed, so restoring your seed restores your chat address."; + strings_["chat_intro_fee"] = "Each message costs a small network fee. Share your chat address to let people start a conversation with you."; + strings_["chat_intro_dismiss"] = "Got it"; strings_["chat_empty_start"] = "Start one with \"New conversation\"."; strings_["chat_copy_my_address"] = "Copy my chat address"; strings_["chat_my_address_tip"] = "Share this address so others can message you"; @@ -2126,6 +2130,7 @@ void I18n::loadBuiltinEnglish() strings_["export_tx_file_fail"] = "Failed to create CSV file"; strings_["export_tx_none"] = "No transactions to export"; strings_["export_tx_success"] = "Transactions exported successfully"; + strings_["export_tx_open_folder"] = "Open folder"; strings_["export_tx_title"] = "Export Transactions to CSV"; // --- Import Key Dialog --- @@ -2177,6 +2182,8 @@ void I18n::loadBuiltinEnglish() // --- Key Export Dialog --- strings_["key_export_fetching"] = "Fetching key from wallet..."; + strings_["key_export_fetching_base"] = "Fetching key from wallet"; + strings_["key_export_unlock_first"] = "Unlock your wallet first, then reveal the key."; strings_["key_export_failed"] = "Couldn't export the key \xE2\x80\x94 unlock the wallet (if encrypted) and try again."; strings_["key_export_private_key"] = "Private Key:"; strings_["key_export_private_warning"] = "Keep this key SECRET! Anyone with this key can spend your funds. Never share it online or with untrusted parties."; @@ -2846,6 +2853,7 @@ void I18n::loadBuiltinEnglish() strings_["explorer_invalid_query"] = "Enter a block height or 64-character hash"; strings_["explorer_invalid_response"] = "Invalid response from daemon"; strings_["explorer_hash_not_found"] = "No block or transaction found for this hash"; + strings_["explorer_lookup_failed"] = "Lookup failed"; strings_["explorer_not_connected"] = "Not connected to daemon — cannot look up a block or transaction hash"; strings_["explorer_no_results"] = "No matching cached blocks"; strings_["explorer_beyond_tip"] = "Block %d is beyond the chain tip (%d)";