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:
@@ -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;
|
||||
|
||||
@@ -576,6 +576,10 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["sent_filter"] = "Sent";
|
||||
strings_["received_filter"] = "Received";
|
||||
strings_["mined_filter"] = "Mined";
|
||||
strings_["sort_date_newest"] = "Newest first";
|
||||
strings_["sort_date_oldest"] = "Oldest first";
|
||||
strings_["sort_amount_high"] = "Largest amount";
|
||||
strings_["sort_amount_low"] = "Smallest amount";
|
||||
strings_["export_csv"] = "Export CSV";
|
||||
strings_["transactions_upper"] = "TRANSACTIONS";
|
||||
strings_["received_upper"] = "RECEIVED";
|
||||
|
||||
Reference in New Issue
Block a user