import { Button, ListView, HorizontalBox, LineEdit } from "std-widgets.slint"; import { Cell, CurrencyIcon, DateTimeCell, ElidingText, SmallButton, TableHeader, TextCell, HeaderCell, MonoTextCell } from "./common.slint"; import { Facade } from "./global.slint"; import { UiTransactionType, UiTransaction } from "./structs.slint"; component TransactionsHeader inherits TableHeader { Rectangle { height: date-text.preferred-height; DateTimeCell { padding-left: 0; visible: false; // only used for alignment purposes } date-text := HeaderCell { text: "Date"; width: 100%; } } HeaderCell { text: "Action"; } HeaderCell { text: "Outgoing"; horizontal-stretch: 2; } HeaderCell { text: "Incoming"; horizontal-stretch: 2; } HeaderCell { text: "Fee"; horizontal-alignment: right; } HeaderCell { text: "Value (€)"; horizontal-alignment: right; } HeaderCell { text: "Gain (€)"; horizontal-alignment: right; } Rectangle { horizontal-stretch: 0.05; } HorizontalLayout { height: 0; visible: false; // only used for alignment purposes spacing: 4px; SmallButton { text: "#"; } SmallButton { text: "?"; } } } component TransactionDisplay inherits Rectangle { in property tx: Facade.transactions[0]; in property even; in property selected; property is-hovered: touch.has-hover || tx-btn.has-hover || desc-btn.has-hover; callback pressed; background: selected ? #4568 : is-hovered ? #4564 : even ? #ffffff06 : transparent; border-radius: self.height / 4; border-width: self.selected ? 1px : 0px; border-color: #456; touch := TouchArea { pointer-event(e) => { if (e.kind == PointerEventKind.down) { root.pressed(); } } } HorizontalLayout { padding-left: 7px; padding-right: 17px; padding-top: 3px; padding-bottom: 3px; spacing: 6px; DateTimeCell { padding-left: 0; date: tx.date; time: tx.time; } TextCell { text: tx.tx-type == UiTransactionType.buy ? "Buy" : tx.tx-type == UiTransactionType.sell ? "Sell" : tx.tx-type == UiTransactionType.trade ? "Trade" : tx.tx-type == UiTransactionType.swap ? "Swap" : tx.tx-type == UiTransactionType.deposit ? "Deposit" : tx.tx-type == UiTransactionType.withdrawal ? "Withdrawal" : tx.tx-type == UiTransactionType.fee ? "Fee" : tx.tx-type == UiTransactionType.receive ? "Receive" : tx.tx-type == UiTransactionType.send ? "Send" : tx.tx-type == UiTransactionType.transfer ? "Transfer" : tx.tx-type == UiTransactionType.chain-split ? "Chain Split" : tx.tx-type == UiTransactionType.expense ? "Expense" : tx.tx-type == UiTransactionType.stolen ? "Stolen" : tx.tx-type == UiTransactionType.lost ? "Lost" : tx.tx-type == UiTransactionType.burn ? "Burn" : tx.tx-type == UiTransactionType.income ? "Income" : tx.tx-type == UiTransactionType.airdrop ? "Airdrop" : tx.tx-type == UiTransactionType.staking ? "Staking" : tx.tx-type == UiTransactionType.cashback ? "Cashback" : tx.tx-type == UiTransactionType.gift ? "Gift" : tx.tx-type == UiTransactionType.spam ? "Spam" : "Unknown"; font-italic: tx.tx-type == UiTransactionType.sell || tx.tx-type == UiTransactionType.buy; } Cell { horizontal-stretch: 2; CurrencyIcon { cmc-id: tx.sent-cmc-id; } ElidingText { text: tx.sent == "" ? "" : "-\{tx.sent}"; max-width: self.preferred-width; font-family: "DejaVu Sans Mono"; } ElidingText { text: tx.from; font-size: 10px; opacity: 0.5; } } Cell { horizontal-stretch: 2; CurrencyIcon { cmc-id: tx.received-cmc-id; } ElidingText { text: tx.received == "" ? "" : "+\{tx.received}"; max-width: self.preferred-width; font-family: "DejaVu Sans Mono"; } ElidingText { text: tx.to; font-size: 10px; opacity: 0.5; } } MonoTextCell { text: tx.fee; font-size: 10px; opacity: 0.5; } MonoTextCell { text: tx.value; font-size: 10px; opacity: 0.5; } TextCell { text: tx.gain-error == "" ? tx.gain : tx.gain_error; font-family: tx.gain-error != "" ? "" : "DejaVu Sans Mono"; color: tx.gain < 0 || tx.gain-error != "" ? #ff0000 : tx.gain > 0 ? #00ff00 : transparent; horizontal-alignment: right; } Rectangle { horizontal-stretch: 0.05; } HorizontalLayout { spacing: 4px; tx-btn := SmallButton { visible: tx.tx-hash != ""; text: "#"; tooltip: tx.tx-hash; tooltip-font-family: "DejaVu Sans Mono"; clicked => { Facade.open-transaction(tx.blockchain, tx.tx-hash) } } desc-btn := SmallButton { visible: tx.description != ""; text: "?"; tooltip: tx.description; } } } } export component Transactions inherits VerticalLayout { in property <[UiTransaction]> transactions: Facade.transactions; property selected-id: -1; property current-index: Facade.ui-index-for-transaction(self.selected-id); property item-height: transactions-view.viewport-height / transactions.length; property current-item-y: current-index * item-height; public function select-transaction(id: int) { self.selected-id = id; // make sure the selected transaction is visible if (self.current-index != -1) { transactions-view.viewport-y = clamp( transactions-view.viewport-y, -self.current-item-y, transactions-view.visible-height - self.current-item-y - self.item-height); } } spacing: 2px; forward-focus: transactions-focus; HorizontalBox { Image { source: @image-url("icons/filter.svg"); opacity: 0.5; } LineEdit { placeholder-text: "Filter"; edited => { Facade.set-text-filter(self.text); } } Button { icon: @image-url("icons/alert-triangle.svg"); text: Facade.transaction-warning-count; enabled: Facade.transaction-warning-count > 0; opacity: self.enabled ? 1.0 : 0.3; checkable: true; clicked => { Facade.set-warnings-filter(self.checked); } } if (Facade.wallet-filter != -1): HorizontalBox { padding: 0; Text { text: "Wallet: " + Facade.wallets[Facade.wallet-filter].name; vertical-alignment: center; } Button { icon: @image-url("icons/delete.svg"); clicked => { Facade.set-wallet-filter(-1); } } } if (Facade.currency-filter != ""): HorizontalBox { padding: 0; Text { text: "Currency: " + Facade.currency-filter; vertical-alignment: center; } Button { icon: @image-url("icons/delete.svg"); clicked => { Facade.set-currency-filter(""); } } } Rectangle {} Button { text: "Export (JSON)"; clicked => { Facade.export-transactions-json() } } Button { text: "Export (CSV)"; clicked => { Facade.export-transactions-csv() } } } TransactionsHeader {} transactions-focus := FocusScope { transactions-view := ListView { for tx[index] in transactions: TransactionDisplay { tx: tx; even: mod(index, 2) == 0; selected: tx.id == root.selected-id; pressed => { root.select-transaction(tx.id); transactions-focus.focus(); } } } function move-selection(delta: int) { if (root.current-index == -1) { return; } root.select-transaction(root.transactions[clamp(root.current-index + delta, 0, root.transactions.length - 1)].id); } key-pressed(event) => { if (event.text == Key.UpArrow) { move-selection(-1); accept } else if (event.text == Key.DownArrow) { move-selection(1); accept } else if (event.text == Key.PageUp) { move-selection(-transactions-view.visible-height / root.item-height); accept } else if (event.text == Key.PageDown) { move-selection(transactions-view.visible-height / root.item-height); accept } else { reject } } } }