feat(request,balance): Request-Payment create-address empty-state + tx empty states
- Request-Payment no longer dead-ends with no shielded address: it shows a hint and a "Create a shielded address" button (auto-selecting the new address), and labels address entries with their contact/wallet names. - The balance "Recent transactions" empty area uses a proper empty-state and now distinguishes "still loading / syncing" from "no transactions yet" instead of a dim one-liner that also showed mid-sync. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1057,7 +1057,27 @@ void RenderSharedRecentTx(App* app, float recentH, float availW, float hs, float
|
|||||||
int count = std::min({4, maxRows, (int)txs.size()}); // show only the most recent (state.transactions is newest-first)
|
int count = std::min({4, maxRows, (int)txs.size()}); // show only the most recent (state.transactions is newest-first)
|
||||||
|
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("no_transactions_yet"));
|
const bool roomForIcon = ImGui::GetContentRegionAvail().y >= 100.0f * dp;
|
||||||
|
if (!app->isConnected()) {
|
||||||
|
// Disconnected: not "empty" and not "loading" — keep a plain caption.
|
||||||
|
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||||
|
TR(app->isLiteBuild() ? "lite_no_wallet" : "not_connected"));
|
||||||
|
} else {
|
||||||
|
const std::string txLoadingText = app->transactionRefreshProgressText();
|
||||||
|
// In lite, transactionRefreshProgressText() is empty and last_tx_update is stamped differently,
|
||||||
|
// so treat only an active sync as "loading" there.
|
||||||
|
const bool loadingOrSyncing = !txLoadingText.empty() ||
|
||||||
|
(state.sync.syncing && !state.sync.isSynced()) ||
|
||||||
|
(!app->isLiteBuild() && state.last_tx_update == 0);
|
||||||
|
if (loadingOrSyncing) {
|
||||||
|
char lbuf[192];
|
||||||
|
const char* base = !txLoadingText.empty() ? txLoadingText.c_str() : TR("loading_transactions");
|
||||||
|
snprintf(lbuf, sizeof(lbuf), "%s%s", base, material::LoadingDots());
|
||||||
|
material::DrawEmptyState(roomForIcon ? ICON_MD_HOURGLASS_EMPTY : nullptr, lbuf);
|
||||||
|
} else {
|
||||||
|
material::DrawEmptyState(roomForIcon ? ICON_MD_RECEIPT_LONG : nullptr, TR("no_transactions_yet"));
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||||
ImFont* capFont = Type().caption();
|
ImFont* capFont = Type().caption();
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ static uintptr_t s_qr_texture = 0;
|
|||||||
static int s_qr_width = 0;
|
static int s_qr_width = 0;
|
||||||
static int s_qr_height = 0;
|
static int s_qr_height = 0;
|
||||||
static bool s_uri_dirty = true;
|
static bool s_uri_dirty = true;
|
||||||
|
static bool s_creating_address = false; // true while a Create-shielded-address RPC is in flight
|
||||||
|
|
||||||
// Helper to build payment URI
|
// Helper to build payment URI
|
||||||
static std::string buildPaymentUri()
|
static std::string buildPaymentUri()
|
||||||
@@ -48,6 +49,7 @@ void RequestPaymentDialog::show(const std::string& address)
|
|||||||
s_label[0] = '\0';
|
s_label[0] = '\0';
|
||||||
s_payment_uri.clear();
|
s_payment_uri.clear();
|
||||||
s_uri_dirty = true;
|
s_uri_dirty = true;
|
||||||
|
s_creating_address = false;
|
||||||
|
|
||||||
// Only a shielded address can be paid (DragonX is a private chain): never prefill a mining address.
|
// Only a shielded address can be paid (DragonX is a private chain): never prefill a mining address.
|
||||||
if (!address.empty() && !util::isTransparentAddress(address)) {
|
if (!address.empty() && !util::isTransparentAddress(address)) {
|
||||||
@@ -67,6 +69,7 @@ void RequestPaymentDialog::show(const std::string& address)
|
|||||||
void RequestPaymentDialog::hide()
|
void RequestPaymentDialog::hide()
|
||||||
{
|
{
|
||||||
s_open = false;
|
s_open = false;
|
||||||
|
s_creating_address = false;
|
||||||
s_payment_uri.clear();
|
s_payment_uri.clear();
|
||||||
if (s_qr_texture != 0) {
|
if (s_qr_texture != 0) {
|
||||||
FreeQRTexture(s_qr_texture);
|
FreeQRTexture(s_qr_texture);
|
||||||
@@ -100,17 +103,54 @@ void RequestPaymentDialog::render(App* app)
|
|||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
||||||
// Address selection
|
// Address selection
|
||||||
ImGui::Text("%s", TR("request_receive_address"));
|
const bool noShielded = state.z_addresses.empty();
|
||||||
|
if (noShielded) {
|
||||||
|
// Nothing to request to yet — offer to create a shielded address instead of dead-ending.
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(material::OnSurfaceMedium()));
|
||||||
|
ImGui::TextWrapped("%s", TR("request_no_shielded"));
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::Spacing();
|
||||||
|
if (s_creating_address) {
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
material::TactileButton(TR("generating"), ImVec2(0, 0), S.resolveFont(actionBtn.font));
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
} else {
|
||||||
|
ImGui::BeginDisabled(!app->isConnected());
|
||||||
|
if (material::TactileButton(TR("request_create_shielded"), ImVec2(0, 0), S.resolveFont(actionBtn.font))) {
|
||||||
|
if (app->isConnected()) {
|
||||||
|
s_creating_address = true;
|
||||||
|
app->createNewZAddress([](const std::string& addr) {
|
||||||
|
s_creating_address = false;
|
||||||
|
if (addr.empty()) {
|
||||||
|
Notifications::instance().error(TR("failed_create_shielded"));
|
||||||
|
} else {
|
||||||
|
Notifications::instance().success(TR("new_shielded_created"));
|
||||||
|
// Pre-select the new address if the dialog is still open and none chosen.
|
||||||
|
if (s_open && s_address[0] == '\0') {
|
||||||
|
strncpy(s_address, addr.c_str(), sizeof(s_address) - 1);
|
||||||
|
s_address[sizeof(s_address) - 1] = '\0';
|
||||||
|
s_uri_dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::Text("%s", TR("request_receive_address"));
|
||||||
|
|
||||||
std::string addr_display = s_address[0] ? s_address : TR("request_select_address");
|
std::string addr_display = s_address[0] ? s_address : std::string(TR("request_select_address"));
|
||||||
if (addr_display.length() > static_cast<size_t>(zAddrLbl.truncate)) {
|
if (addr_display.length() > static_cast<size_t>(zAddrLbl.truncate)) {
|
||||||
addr_display = addr_display.substr(0, zAddrFrontLbl.truncate) + "..." + addr_display.substr(addr_display.length() - zAddrBackLbl.truncate);
|
addr_display = addr_display.substr(0, zAddrFrontLbl.truncate) + "..." + addr_display.substr(addr_display.length() - zAddrBackLbl.truncate);
|
||||||
}
|
}
|
||||||
|
if (s_address[0]) {
|
||||||
|
std::string l = app->getAddressLabel(s_address);
|
||||||
|
if (!l.empty()) addr_display = l + " " + addr_display;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::SetNextItemWidth(-1);
|
ImGui::SetNextItemWidth(-1);
|
||||||
if (ImGui::BeginCombo("##Address", addr_display.c_str())) {
|
if (ImGui::BeginCombo("##Address", addr_display.c_str())) {
|
||||||
// Z-addresses
|
|
||||||
if (!state.z_addresses.empty()) {
|
|
||||||
ImGui::TextDisabled("%s", TR("request_shielded_addrs"));
|
ImGui::TextDisabled("%s", TR("request_shielded_addrs"));
|
||||||
for (size_t i = 0; i < state.z_addresses.size(); i++) {
|
for (size_t i = 0; i < state.z_addresses.size(); i++) {
|
||||||
const auto& addr = state.z_addresses[i];
|
const auto& addr = state.z_addresses[i];
|
||||||
@@ -118,17 +158,18 @@ void RequestPaymentDialog::render(App* app)
|
|||||||
if (label.length() > static_cast<size_t>(zAddrLbl.truncate)) {
|
if (label.length() > static_cast<size_t>(zAddrLbl.truncate)) {
|
||||||
label = label.substr(0, zAddrFrontLbl.truncate) + "..." + label.substr(label.length() - zAddrBackLbl.truncate);
|
label = label.substr(0, zAddrFrontLbl.truncate) + "..." + label.substr(label.length() - zAddrBackLbl.truncate);
|
||||||
}
|
}
|
||||||
|
std::string clbl = app->getAddressLabel(addr.address);
|
||||||
|
std::string shown = (clbl.empty() ? label : (clbl + " " + label)) + "##" + addr.address;
|
||||||
|
|
||||||
if (ImGui::Selectable(label.c_str(), addr.address == s_address)) {
|
if (ImGui::Selectable(shown.c_str(), addr.address == s_address)) {
|
||||||
strncpy(s_address, addr.address.c_str(), sizeof(s_address) - 1);
|
strncpy(s_address, addr.address.c_str(), sizeof(s_address) - 1);
|
||||||
s_uri_dirty = true;
|
s_uri_dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// No mining (transparent) addresses: they only receive mining rewards, so a request to one
|
||||||
|
// could never be paid.
|
||||||
|
ImGui::EndCombo();
|
||||||
}
|
}
|
||||||
|
|
||||||
// No mining (transparent) addresses: they only receive mining rewards, so a request to one
|
|
||||||
// could never be paid.
|
|
||||||
ImGui::EndCombo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|||||||
Reference in New Issue
Block a user