fix(overview): mining addresses visible at 0 balance; drag-reorder always applies

- Address list: a mining-flagged address stays visible even at 0 balance when
  "hide zero balances" is on (payout addresses shouldn't vanish).
- Drag-reorder: persist dense sort orders (0..N-1) for the whole visible list on
  drop via App::reorderAddresses, instead of a pairwise swap that no-ops when both
  rows are still at the default un-ordered state. First drag now always takes
  effect, and explicit order keeps overriding the starred/type/balance sort.
- Tests: 0-balance mining row survives hide-zero; ordered non-favorite outranks a
  favorite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 15:02:15 -05:00
parent 1a00ec3359
commit 14f5fdc559
5 changed files with 49 additions and 7 deletions

View File

@@ -252,6 +252,10 @@ public:
void setAddressSortOrder(const std::string& addr, int order);
int getNextSortOrder() const;
void swapAddressOrder(const std::string& a, const std::string& b);
// Assign dense sort orders (0..N-1) to the given addresses in the given order and
// persist once. Used by drag-reorder so a drop always takes effect (even from the
// default un-ordered state, where a pairwise swap would be a no-op).
void reorderAddresses(const std::vector<std::string>& orderedAddrs);
bool isMiningAddress(const std::string& addr) const;
void setMiningAddress(const std::string& addr, bool mining);
void invalidateAddressValidationCache();

View File

@@ -2094,6 +2094,14 @@ void App::swapAddressOrder(const std::string& a, const std::string& b)
}
}
void App::reorderAddresses(const std::vector<std::string>& orderedAddrs)
{
if (!settings_) return;
for (size_t i = 0; i < orderedAddrs.size(); ++i)
settings_->setAddressSortOrder(orderedAddrs[i], static_cast<int>(i));
settings_->save();
}
bool App::isMiningAddress(const std::string& addr) const
{
return settings_ && settings_->isMiningAddress(addr);

View File

@@ -41,7 +41,8 @@ std::vector<AddressListRow> BuildAddressListRows(const std::vector<AddressListIn
if (!input.info) continue;
if (!AddressListMatchesFilter(input, filter)) continue;
if (input.hidden && !showHidden) continue;
if (hideZeroBalances && input.info->balance < 1e-9 && !input.hidden && !input.favorite) continue;
// Keep mining-flagged addresses visible even at 0 balance (they receive payouts).
if (hideZeroBalances && input.info->balance < 1e-9 && !input.hidden && !input.favorite && !input.mining) continue;
rows.push_back({input.info, input.isZ, input.hidden, input.favorite, input.mining,
input.label, input.icon, input.sortOrder});
}

View File

@@ -343,12 +343,20 @@ void RenderSharedAddressList(App* app, float listH, float availW,
for (int i = 0; i < (int)rows.size(); ++i) {
if (mousePos.y > rowY[i] + rowH * 0.5f) insertIdx = i + 1;
}
if (insertIdx != s_dragIdx && insertIdx != s_dragIdx + 1) {
int targetIdx = (insertIdx > s_dragIdx) ? insertIdx - 1 : insertIdx;
if (targetIdx >= 0 && targetIdx < (int)rows.size() && s_dragIdx < (int)rows.size()) {
app->swapAddressOrder(rows[s_dragIdx].info->address,
rows[targetIdx].info->address);
}
if (insertIdx != s_dragIdx && insertIdx != s_dragIdx + 1 &&
s_dragIdx < (int)rows.size()) {
// Build the new visible order (move the dragged row to insertIdx) and
// persist dense sort orders 0..N-1 in one shot. This works even when the
// rows were still at the default order (a pairwise swap would no-op).
std::vector<std::string> newOrder;
newOrder.reserve(rows.size());
for (int i = 0; i < (int)rows.size(); ++i)
if (i != s_dragIdx) newOrder.push_back(rows[i].info->address);
int clamped = std::min(insertIdx, (int)rows.size());
int destIdx = (insertIdx > s_dragIdx) ? clamped - 1 : clamped;
destIdx = std::max(0, std::min(destIdx, (int)newOrder.size()));
newOrder.insert(newOrder.begin() + destIdx, rows[s_dragIdx].info->address);
app->reorderAddresses(newOrder);
}
}
}

View File

@@ -2491,6 +2491,27 @@ void testBalanceAddressListModel()
EXPECT_EQ(rows.size(), static_cast<size_t>(1));
EXPECT_EQ(rows[0].info->address, std::string("zs-high"));
// Mining-flagged addresses stay visible at 0 balance even with hide-zero on.
dragonx::AddressInfo tMine;
tMine.address = "R-miner"; tMine.label = "Payout"; tMine.balance = 0.0; tMine.type = "transparent";
std::vector<dragonx::ui::AddressListInput> mineInputs = {
{&addresses[0], true, false, false, false, "Vault", "", -1}, // funded
{&tMine, false, false, false, /*mining=*/true, "Payout", "", -1}, // 0-balance mining
};
auto mineRows = dragonx::ui::BuildAddressListRows(mineInputs, "", /*hideZero=*/true, false);
EXPECT_EQ(mineRows.size(), static_cast<size_t>(2));
bool sawMiner = false;
for (const auto& r : mineRows) if (r.info->address == std::string("R-miner")) sawMiner = true;
EXPECT_TRUE(sawMiner);
// Explicit sort order overrides starred: a non-favorite ordered row outranks a favorite.
std::vector<dragonx::ui::AddressListInput> ordInputs = {
{&addresses[0], true, false, false, false, "Vault", "", 0}, // ordered (0)
{&addresses[2], false, false, true, false, "Favorite", "", -1}, // favorite, unordered
};
auto ordRows = dragonx::ui::BuildAddressListRows(ordInputs, "", false, false);
EXPECT_EQ(ordRows[0].info->address, std::string("zs-high"));
auto layout = dragonx::ui::ComputeAddressRowLayout(10.0f, 20.0f, 300.0f, 50.0f,
12.0f, 16.0f, 6.0f, 4.0f, 2.0f);
EXPECT_NEAR(layout.contentStartX, 22.0, 0.0001);