feat(history): add date and amount sorting to the History tab

Add a sort selector next to the type filter with four modes: Newest first
(default), Oldest first, Largest amount, Smallest amount. The mode folds into
the merged-list memoization cache key (so the list re-sorts only when the mode
changes) and the comparator branches on it, keeping txid as a deterministic
tiebreak. Changing the sort resets to page 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 09:55:24 -05:00
parent b71f8ae0a8
commit 5796664b51
2 changed files with 44 additions and 10 deletions

View File

@@ -207,6 +207,8 @@ void RenderTransactionsTab(App* app)
// Clickable type filter (clicking a card sets the type filter)
static int type_filter = 0;
// Sort mode: 0 = newest first, 1 = oldest first, 2 = largest amount, 3 = smallest amount.
static int s_sort_mode = 0;
// --- Received card ---
{
@@ -332,6 +334,13 @@ void RenderTransactionsTab(App* app)
const char* types[] = { TR("all_filter"), TR("sent_filter"), TR("received_filter"), TR("mined_filter") };
ImGui::Combo("##TxType", &type_filter, types, IM_ARRAYSIZE(types));
// Sort selector
ImGui::SameLine(0, filterGap);
ImGui::SetNextItemWidth(comboW);
const char* sorts[] = { TR("sort_date_newest"), TR("sort_date_oldest"),
TR("sort_amount_high"), TR("sort_amount_low") };
ImGui::Combo("##TxSort", &s_sort_mode, sorts, IM_ARRAYSIZE(sorts));
ImGui::SameLine(0, filterGap);
if (TactileButton(TrId("refresh", "tx").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
app->refreshNow();
@@ -386,6 +395,7 @@ void RenderTransactionsTab(App* app)
auto mix = [&h](std::uint64_t v) { h = (h ^ v) * 1099511628211ULL; };
mix(state.transactions.size());
mix(static_cast<std::uint64_t>(state.last_tx_update));
mix(static_cast<std::uint64_t>(s_sort_mode)); // re-sort the cache when the mode changes
for (const auto& t : state.transactions) {
mix(static_cast<std::uint64_t>(t.confirmations));
mix(static_cast<std::uint64_t>(t.timestamp));
@@ -469,16 +479,36 @@ void RenderTransactionsTab(App* app)
}
}
// Sort: pending (0-conf) transactions first, then newest-first by timestamp, with txid as
// a final deterministic tiebreak so same-block transactions keep a stable order across
// rebuilds (otherwise equal timestamps reorder every time a new block bumps confirmations).
// Sort per the selected mode. txid is the final deterministic tiebreak so same-block
// transactions keep a stable order across rebuilds (otherwise equal keys reorder every
// time a new block bumps confirmations).
const int sortMode = s_sort_mode;
std::sort(display_txns.begin(), display_txns.end(),
[](const DisplayTx& a, const DisplayTx& b) {
bool aPending = (a.confirmations == 0);
bool bPending = (b.confirmations == 0);
if (aPending != bPending) return aPending;
if (a.timestamp != b.timestamp) return a.timestamp > b.timestamp;
return a.txid > b.txid;
[sortMode](const DisplayTx& a, const DisplayTx& b) {
switch (sortMode) {
case 1: // Oldest first
if (a.timestamp != b.timestamp) return a.timestamp < b.timestamp;
return a.txid < b.txid;
case 2: { // Largest amount first
double aa = std::abs(a.amount), ba = std::abs(b.amount);
if (aa != ba) return aa > ba;
if (a.timestamp != b.timestamp) return a.timestamp > b.timestamp;
return a.txid > b.txid;
}
case 3: { // Smallest amount first
double aa = std::abs(a.amount), ba = std::abs(b.amount);
if (aa != ba) return aa < ba;
if (a.timestamp != b.timestamp) return a.timestamp > b.timestamp;
return a.txid > b.txid;
}
default: { // 0: Newest first — pending (0-conf) on top, then by date
bool aPending = (a.confirmations == 0);
bool bPending = (b.confirmations == 0);
if (aPending != bPending) return aPending;
if (a.timestamp != b.timestamp) return a.timestamp > b.timestamp;
return a.txid > b.txid;
}
}
});
s_display_cache_key = displayKey;
@@ -516,7 +546,7 @@ void RenderTransactionsTab(App* app)
int totalPages = std::max(1, (filtered_count + perPage - 1) / perPage);
// Reset page when filters change
int filterHash = type_filter * 1000003 + filtered_count * 31 + static_cast<int>(search_str.size());
int filterHash = type_filter * 1000003 + s_sort_mode * 7919 + filtered_count * 31 + static_cast<int>(search_str.size());
if (filterHash != s_prev_filter_hash) {
s_current_page = 0;
s_prev_filter_hash = filterHash;