From 647eae0ed80762b38efc726d5ab5ec65d464eb42 Mon Sep 17 00:00:00 2001 From: Hanh Date: Sat, 18 Sep 2021 09:14:08 +0800 Subject: [PATCH] Sort notes/tx by time --- lib/account.dart | 130 +-------------------------------------------- lib/history.dart | 47 +++++++++-------- lib/note.dart | 135 +++++++++++++++++++++++++++++++++++++++++++++++ lib/store.dart | 70 +++++++++++++++--------- pubspec.yaml | 4 +- pubspec.yaml.tpl | 2 +- 6 files changed, 209 insertions(+), 179 deletions(-) create mode 100644 lib/note.dart diff --git a/lib/account.dart b/lib/account.dart index ce60284..72a32db 100644 --- a/lib/account.dart +++ b/lib/account.dart @@ -23,6 +23,7 @@ import 'contact.dart'; import 'history.dart'; import 'main.dart'; import 'generated/l10n.dart'; +import 'note.dart'; class AccountPage extends StatefulWidget { @override @@ -563,135 +564,6 @@ class _AccountPageState extends State } } -class NoteWidget extends StatefulWidget { - final void Function(int index) tabTo; - - NoteWidget(this.tabTo); - - @override - State createState() => _NoteState(); -} - -class _NoteState extends State with AutomaticKeepAliveClientMixin { - @override - bool get wantKeepAlive => true; //Set to true - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - padding: EdgeInsets.all(8), - scrollDirection: Axis.vertical, - child: Observer(builder: (context) { - var amountHeader = S.of(context).amount; - switch (accountManager.noteSortOrder) { - case SortOrder.Ascending: - amountHeader += ' \u2191'; - break; - case SortOrder.Descending: - amountHeader += ' \u2193'; - break; - default: - } - return PaginatedDataTable( - columns: [ - DataColumn( - label: settings.showConfirmations - ? Text(S.of(context).confs) - : Text(S.of(context).height), - onSort: (_, __) { - setState(() { - settings.toggleShowConfirmations(); - }); - }), - DataColumn(label: Text(S.of(context).datetime)), - DataColumn( - label: Text(amountHeader), - numeric: true, - onSort: (_, __) { - setState(() { - accountManager.sortNoteAmount(); - }); - }), - ], - header: Text(S.of(context).selectNotesToExcludeFromPayments, - style: Theme.of(context).textTheme.bodyText2), - columnSpacing: 16, - showCheckboxColumn: false, - availableRowsPerPage: [5, 10, 25, 100], - onRowsPerPageChanged: (int? value) { - settings.setRowsPerPage(value ?? 25); - }, - showFirstLastButtons: true, - rowsPerPage: settings.rowsPerPage, - source: NotesDataSource(context, _onRowSelected), - ); - })); - } - - _onRowSelected(Note note) { - accountManager.excludeNote(note); - } -} - -class NotesDataSource extends DataTableSource { - final BuildContext context; - final Function(Note) onRowSelected; - - NotesDataSource(this.context, this.onRowSelected); - - @override - DataRow getRow(int index) { - final note = accountManager.sortedNotes[index]; - final theme = Theme.of(context); - final confsOrHeight = settings.showConfirmations - ? syncStatus.latestHeight - note.height + 1 - : note.height; - - var style = theme.textTheme.bodyText2!; - if (!_confirmed(note.height)) - style = style.copyWith(color: style.color!.withOpacity(0.5)); - - if (note.spent) - style = style.merge(TextStyle(decoration: TextDecoration.lineThrough)); - - final amountStyle = fontWeight(style, note.value); - - return DataRow.byIndex( - index: index, - selected: note.excluded, - color: MaterialStateColor.resolveWith((states) => - states.contains(MaterialState.selected) - ? theme.primaryColor.withOpacity(0.5) - : theme.backgroundColor), - cells: [ - DataCell(Text("$confsOrHeight", style: style)), - DataCell(Text("${note.timestamp}", style: style)), - DataCell(Text("${note.value.toStringAsFixed(8)}", style: amountStyle)), - ], - onSelectChanged: (selected) => _noteSelected(note, selected), - ); - } - - @override - bool get isRowCountApproximate => false; - - @override - int get rowCount => accountManager.notes.length; - - @override - int get selectedRowCount => 0; - - bool _confirmed(int height) { - return syncStatus.latestHeight - height >= settings.anchorOffset; - } - - void _noteSelected(Note note, bool? selected) { - note.excluded = !note.excluded; - notifyListeners(); - onRowSelected(note); - } -} - class BudgetWidget extends StatefulWidget { @override State createState() => BudgetState(); diff --git a/lib/history.dart b/lib/history.dart index 5a21ed0..4bff1ce 100644 --- a/lib/history.dart +++ b/lib/history.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'main.dart'; -import 'store.dart'; import 'generated/l10n.dart'; class HistoryWidget extends StatefulWidget { @@ -39,18 +38,28 @@ class HistoryState extends State settings.toggleShowConfirmations(); }); }), - DataColumn(label: Text(S.of(context).datetime)), DataColumn( - label: - Text(S.of(context).amount + _sortIndicator("amount")), - numeric: true, - onSort: (_, __) { - setState(() { - accountManager.sortTx("amount"); - }); - }), + label: Text(S.of(context).datetime + + accountManager.txSortConfig.getIndicator("time")), + onSort: (_, __) { + setState(() { + accountManager.sortTx("time"); + }); + }, + ), DataColumn( - label: Text(S.of(context).txId + _sortIndicator("txid")), + label: Text(S.of(context).amount + + accountManager.txSortConfig.getIndicator("amount")), + numeric: true, + onSort: (_, __) { + setState(() { + accountManager.sortTx("amount"); + }); + }, + ), + DataColumn( + label: Text(S.of(context).txId + + accountManager.txSortConfig.getIndicator("txid")), onSort: (_, __) { setState(() { accountManager.sortTx("txid"); @@ -58,8 +67,8 @@ class HistoryState extends State }, ), DataColumn( - label: - Text(S.of(context).address + _sortIndicator("address")), + label: Text(S.of(context).address + + accountManager.txSortConfig.getIndicator("address")), onSort: (_, __) { setState(() { accountManager.sortTx("address"); @@ -67,7 +76,8 @@ class HistoryState extends State }, ), DataColumn( - label: Text(S.of(context).memo + _sortIndicator("memo")), + label: Text(S.of(context).memo + + accountManager.txSortConfig.getIndicator("memo")), onSort: (_, __) { setState(() { accountManager.sortTx("memo"); @@ -86,15 +96,6 @@ class HistoryState extends State source: HistoryDataSource(context)); })); } - - String _sortIndicator(String field) { - if (accountManager.txSortOrder.field != field) return ''; - if (accountManager.txSortOrder.order == SortOrder.Ascending) - return ' \u2191'; - if (accountManager.txSortOrder.order == SortOrder.Descending) - return ' \u2193'; - return ''; - } } class HistoryDataSource extends DataTableSource { diff --git a/lib/note.dart b/lib/note.dart new file mode 100644 index 0000000..ef64e79 --- /dev/null +++ b/lib/note.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +import 'main.dart'; +import 'store.dart'; +import 'generated/l10n.dart'; + +class NoteWidget extends StatefulWidget { + final void Function(int index) tabTo; + + NoteWidget(this.tabTo); + + @override + State createState() => _NoteState(); +} + +class _NoteState extends State with AutomaticKeepAliveClientMixin { + @override + bool get wantKeepAlive => true; //Set to true + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + padding: EdgeInsets.all(8), + scrollDirection: Axis.vertical, + child: Observer(builder: (context) { + return PaginatedDataTable( + columns: [ + DataColumn( + label: settings.showConfirmations + ? Text(S.of(context).confs) + : Text(S.of(context).height), + onSort: (_, __) { + setState(() { + settings.toggleShowConfirmations(); + }); + }), + DataColumn( + label: Text(S.of(context).datetime + + accountManager.noteSortConfig.getIndicator("time")), + onSort: (_, __) { + setState(() { + accountManager.sortNotes("time"); + }); + }, + ), + DataColumn( + numeric: true, + label: Text(S.of(context).amount + + accountManager.noteSortConfig.getIndicator("amount")), + onSort: (_, __) { + setState(() { + accountManager.sortNotes("amount"); + }); + }, + ), + ], + header: Text(S.of(context).selectNotesToExcludeFromPayments, + style: Theme.of(context).textTheme.bodyText2), + columnSpacing: 16, + showCheckboxColumn: false, + availableRowsPerPage: [5, 10, 25, 100], + onRowsPerPageChanged: (int? value) { + settings.setRowsPerPage(value ?? 25); + }, + showFirstLastButtons: true, + rowsPerPage: settings.rowsPerPage, + source: NotesDataSource(context, _onRowSelected), + ); + })); + } + + _onRowSelected(Note note) { + accountManager.excludeNote(note); + } +} + +class NotesDataSource extends DataTableSource { + final BuildContext context; + final Function(Note) onRowSelected; + + NotesDataSource(this.context, this.onRowSelected); + + @override + DataRow getRow(int index) { + final note = accountManager.sortedNotes[index]; + final theme = Theme.of(context); + final confsOrHeight = settings.showConfirmations + ? syncStatus.latestHeight - note.height + 1 + : note.height; + + var style = theme.textTheme.bodyText2!; + if (!_confirmed(note.height)) + style = style.copyWith(color: style.color!.withOpacity(0.5)); + + if (note.spent) + style = style.merge(TextStyle(decoration: TextDecoration.lineThrough)); + + final amountStyle = fontWeight(style, note.value); + + return DataRow.byIndex( + index: index, + selected: note.excluded, + color: MaterialStateColor.resolveWith((states) => + states.contains(MaterialState.selected) + ? theme.primaryColor.withOpacity(0.5) + : theme.backgroundColor), + cells: [ + DataCell(Text("$confsOrHeight", style: style)), + DataCell(Text("${note.timestamp}", style: style)), + DataCell(Text("${note.value.toStringAsFixed(8)}", style: amountStyle)), + ], + onSelectChanged: (selected) => _noteSelected(note, selected), + ); + } + + @override + bool get isRowCountApproximate => false; + + @override + int get rowCount => accountManager.notes.length; + + @override + int get selectedRowCount => 0; + + bool _confirmed(int height) { + return syncStatus.latestHeight - height >= settings.anchorOffset; + } + + void _noteSelected(Note note, bool? selected) { + note.excluded = !note.excluded; + notifyListeners(); + onRowSelected(note); + } +} diff --git a/lib/store.dart b/lib/store.dart index b802475..41f4316 100644 --- a/lib/store.dart +++ b/lib/store.dart @@ -310,10 +310,10 @@ abstract class _AccountManager with Store { List accounts = []; @observable - SortOrder noteSortOrder = SortOrder.Unsorted; + SortConfig noteSortConfig = SortConfig("", SortOrder.Unsorted); @observable - TxSortConfig txSortOrder = TxSortConfig("", SortOrder.Unsorted); + SortConfig txSortConfig = SortConfig("", SortOrder.Unsorted); @observable int pnlSeriesIndex = 0; @@ -489,7 +489,7 @@ abstract class _AccountManager with Store { final List res = await db.rawQuery( "SELECT n.id_note, n.height, n.value, t.timestamp, n.excluded, n.spent FROM received_notes n, transactions t " "WHERE n.account = ?1 AND (n.spent IS NULL OR n.spent = 0) " - "AND n.tx = t.id_tx", + "AND n.tx = t.id_tx ORDER BY n.height DESC", [accountId]); notes = res.map((row) { final id = row['id_note']; @@ -523,12 +523,16 @@ abstract class _AccountManager with Store { @computed List get sortedNotes { var notes2 = [...notes]; - return _sortNoteAmount(notes2, noteSortOrder); + switch (noteSortConfig.field) { + case "time": return _sort(notes2, (Note note) => note.height, noteSortConfig.order); + case "amount": return _sort(notes2, (Note note) => note.value, noteSortConfig.order); + } + return notes2; } @action - Future sortNoteAmount() async { - noteSortOrder = nextSortOrder(noteSortOrder); + Future sortNotes(String field) async { + noteSortConfig.sortOn(field); } List _sortNoteAmount(List notes, SortOrder order) { @@ -549,25 +553,22 @@ abstract class _AccountManager with Store { @computed List get sortedTxs { var txs2 = [...txs]; - switch (txSortOrder.field) { - case "amount": return _sortTx(txs2, (Tx tx) => tx.value, txSortOrder.order); - case "txid": return _sortTx(txs2, (Tx tx) => tx.txid, txSortOrder.order); - case "address": return _sortTx(txs2, (Tx tx) => tx.contact ?? tx.address, txSortOrder.order); - case "memo": return _sortTx(txs2, (Tx tx) => tx.memo, txSortOrder.order); + switch (txSortConfig.field) { + case "time": return _sort(txs2, (Tx tx) => tx.height, txSortConfig.order); + case "amount": return _sort(txs2, (Tx tx) => tx.value, txSortConfig.order); + case "txid": return _sort(txs2, (Tx tx) => tx.txid, txSortConfig.order); + case "address": return _sort(txs2, (Tx tx) => tx.contact ?? tx.address, txSortConfig.order); + case "memo": return _sort(txs2, (Tx tx) => tx.memo, txSortConfig.order); } return txs2; } @action Future sortTx(String field) async { - if (field != txSortOrder.field) { - txSortOrder = TxSortConfig(field, SortOrder.Ascending); - } - else - txSortOrder = TxSortConfig(field, nextSortOrder(txSortOrder.order)); + txSortConfig.sortOn(field); } - List _sortTx(List txs, T Function(Tx) project, SortOrder order) { + List _sort(List txs, T Function(C) project, SortOrder order) { switch (order) { case SortOrder.Ascending: txs.sort((a, b) => project(a).compareTo(project(b))); @@ -990,7 +991,11 @@ var progressStream = progressPort.asBroadcastStream(); var syncPort = ReceivePort(); var syncStream = syncPort.asBroadcastStream(); -class Note { +abstract class HasHeight { + int height = 0; +} + +class Note extends HasHeight { int id; int height; String timestamp; @@ -1002,7 +1007,7 @@ class Note { this.spent); } -class Tx { +class Tx extends HasHeight { int id; int height; String timestamp; @@ -1136,13 +1141,30 @@ class TimeRange { TimeRange(this.start, this.end); } -class TxSortConfig { +class SortConfig { @observable - final String field; + String field; @observable - final SortOrder order; + SortOrder order; - TxSortConfig(this.field, this.order); + SortConfig(this.field, this.order); + + @action + void sortOn(String field) { + if (field != this.field) + order = SortOrder.Ascending; + else + order = nextSortOrder(order); + this.field = field; + } + + String getIndicator(String field) { + if (this.field != field) return ''; + if (order == SortOrder.Ascending) + return ' \u2191'; + if (order == SortOrder.Descending) + return ' \u2193'; + return ''; + } } - diff --git a/pubspec.yaml b/pubspec.yaml index c0ac21a..d328b1e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.9+138 +version: 1.0.9+141 environment: sdk: ">=2.12.0 <3.0.0" @@ -79,7 +79,7 @@ dev_dependencies: flutter_native_splash: ^1.2.3 flutter_app_name: - name: "YWallet" + name: "ZWalletTest" flutter_icons: android: true diff --git a/pubspec.yaml.tpl b/pubspec.yaml.tpl index f389a13..aa0424f 100644 --- a/pubspec.yaml.tpl +++ b/pubspec.yaml.tpl @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.9+140 +version: 1.0.9+141 environment: sdk: ">=2.12.0 <3.0.0"