mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-29 21:55:58 +00:00
WIP: transaction filter+search fixes
This commit is contained in:
parent
e64c067212
commit
ccdfa8db44
3 changed files with 287 additions and 21 deletions
|
@ -1,14 +1,16 @@
|
|||
class TransactionFilter {
|
||||
final bool sent;
|
||||
final bool received;
|
||||
final DateTime from;
|
||||
final DateTime to;
|
||||
final bool trade;
|
||||
final DateTime? from;
|
||||
final DateTime? to;
|
||||
final int? amount;
|
||||
final String keyword;
|
||||
|
||||
TransactionFilter({
|
||||
required this.sent,
|
||||
required this.received,
|
||||
required this.trade,
|
||||
required this.from,
|
||||
required this.to,
|
||||
required this.amount,
|
||||
|
@ -18,6 +20,7 @@ class TransactionFilter {
|
|||
TransactionFilter copyWith({
|
||||
bool? sent,
|
||||
bool? received,
|
||||
bool? trade,
|
||||
DateTime? from,
|
||||
DateTime? to,
|
||||
int? amount,
|
||||
|
@ -26,6 +29,7 @@ class TransactionFilter {
|
|||
return TransactionFilter(
|
||||
sent: sent ?? this.sent,
|
||||
received: received ?? this.received,
|
||||
trade: trade ?? this.trade,
|
||||
from: from ?? this.from,
|
||||
to: to ?? this.to,
|
||||
amount: amount ?? this.amount,
|
||||
|
@ -35,6 +39,6 @@ class TransactionFilter {
|
|||
|
||||
@override
|
||||
String toString() {
|
||||
return "TxFilter { sent: $sent, received: $received, from: $from, to: $to, amount: $amount, keyword: $keyword }";
|
||||
return "TxFilter { sent: $sent, received: $received, trade: $trade, from: $from, to: $to, amount: $amount, keyword: $keyword }";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,8 +96,12 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
}
|
||||
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000);
|
||||
if (date.millisecondsSinceEpoch > filter.to.millisecondsSinceEpoch ||
|
||||
date.millisecondsSinceEpoch < filter.from.millisecondsSinceEpoch) {
|
||||
if ((filter.to != null &&
|
||||
date.millisecondsSinceEpoch >
|
||||
filter.to!.millisecondsSinceEpoch) ||
|
||||
(filter.from != null &&
|
||||
date.millisecondsSinceEpoch <
|
||||
filter.from!.millisecondsSinceEpoch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -125,13 +129,25 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
.isNotEmpty;
|
||||
|
||||
// check if address contains
|
||||
contains |= tx.address.contains(keyword);
|
||||
contains |= tx.address.toLowerCase().contains(keyword);
|
||||
|
||||
// check if note contains
|
||||
contains |= notes[tx.txid] != null && notes[tx.txid]!.contains(keyword);
|
||||
contains |= notes[tx.txid] != null &&
|
||||
notes[tx.txid]!.toLowerCase().contains(keyword);
|
||||
|
||||
// check if txid contains
|
||||
contains |= tx.txid.contains(keyword);
|
||||
contains |= tx.txid.toLowerCase().contains(keyword);
|
||||
|
||||
// check if subType contains
|
||||
contains |=
|
||||
tx.subType.isNotEmpty && tx.subType.toLowerCase().contains(keyword);
|
||||
|
||||
// check if txType contains
|
||||
contains |= tx.txType.toLowerCase().contains(keyword);
|
||||
|
||||
// check if date contains
|
||||
contains |=
|
||||
Format.extractDateFrom(tx.timestamp).toLowerCase().contains(keyword);
|
||||
|
||||
return contains;
|
||||
}
|
||||
|
@ -311,7 +327,7 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
)
|
||||
: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Search",
|
||||
"Search...",
|
||||
searchFieldFocusNode,
|
||||
context,
|
||||
desktopMed: isDesktop,
|
||||
|
@ -393,6 +409,22 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
],
|
||||
),
|
||||
),
|
||||
if (isDesktop)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isDesktop &&
|
||||
ref.watch(transactionFilterProvider.state).state != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
children: const [
|
||||
TransactionFilterOptionBar(),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
|
@ -463,6 +495,8 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: DesktopTransactionCardRow(
|
||||
key: Key(
|
||||
"transactionCard_key_${month.item2[index].txid}"),
|
||||
transaction: month.item2[index],
|
||||
walletId: walletId,
|
||||
),
|
||||
|
@ -506,6 +540,220 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
}
|
||||
}
|
||||
|
||||
class TransactionFilterOptionBar extends ConsumerStatefulWidget {
|
||||
const TransactionFilterOptionBar({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
ConsumerState<TransactionFilterOptionBar> createState() =>
|
||||
_TransactionFilterOptionBarState();
|
||||
}
|
||||
|
||||
class _TransactionFilterOptionBarState
|
||||
extends ConsumerState<TransactionFilterOptionBar> {
|
||||
final List<TransactionFilterOptionBarItem> items = [];
|
||||
TransactionFilter? _filter;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_filter = ref.read(transactionFilterProvider.state).state;
|
||||
|
||||
if (_filter != null) {
|
||||
if (_filter!.sent) {
|
||||
const label = "Sent";
|
||||
final item = TransactionFilterOptionBarItem(
|
||||
label: label,
|
||||
onPressed: (s) {
|
||||
items.removeWhere((e) => e.label == label);
|
||||
if (items.isEmpty) {
|
||||
ref.read(transactionFilterProvider.state).state = null;
|
||||
} else {
|
||||
ref.read(transactionFilterProvider.state).state =
|
||||
ref.read(transactionFilterProvider.state).state?.copyWith(
|
||||
sent: false,
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
items.add(item);
|
||||
}
|
||||
if (_filter!.received) {
|
||||
const label = ("Received");
|
||||
final item = TransactionFilterOptionBarItem(
|
||||
label: label,
|
||||
onPressed: (s) {
|
||||
items.removeWhere((e) => e.label == label);
|
||||
if (items.isEmpty) {
|
||||
ref.read(transactionFilterProvider.state).state = null;
|
||||
} else {
|
||||
ref.read(transactionFilterProvider.state).state =
|
||||
ref.read(transactionFilterProvider.state).state?.copyWith(
|
||||
received: false,
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
if (_filter!.to != null) {
|
||||
final label = _filter!.from.toString();
|
||||
final item = TransactionFilterOptionBarItem(
|
||||
label: label,
|
||||
onPressed: (s) {
|
||||
items.removeWhere((e) => e.label == label);
|
||||
if (items.isEmpty) {
|
||||
ref.read(transactionFilterProvider.state).state = null;
|
||||
} else {
|
||||
ref.read(transactionFilterProvider.state).state =
|
||||
ref.read(transactionFilterProvider.state).state?.copyWith(
|
||||
to: null,
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
items.add(item);
|
||||
}
|
||||
if (_filter!.from != null) {
|
||||
final label2 = _filter!.to.toString();
|
||||
final item2 = TransactionFilterOptionBarItem(
|
||||
label: label2,
|
||||
onPressed: (s) {
|
||||
items.removeWhere((e) => e.label == label2);
|
||||
if (items.isEmpty) {
|
||||
ref.read(transactionFilterProvider.state).state = null;
|
||||
} else {
|
||||
ref.read(transactionFilterProvider.state).state =
|
||||
ref.read(transactionFilterProvider.state).state?.copyWith(
|
||||
from: null,
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
items.add(item2);
|
||||
}
|
||||
|
||||
if (_filter!.amount != null) {
|
||||
final label = _filter!.amount!.toString();
|
||||
final item = TransactionFilterOptionBarItem(
|
||||
label: label,
|
||||
onPressed: (s) {
|
||||
items.removeWhere((e) => e.label == label);
|
||||
if (items.isEmpty) {
|
||||
ref.read(transactionFilterProvider.state).state = null;
|
||||
} else {
|
||||
ref.read(transactionFilterProvider.state).state =
|
||||
ref.read(transactionFilterProvider.state).state?.copyWith(
|
||||
amount: null,
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
items.add(item);
|
||||
}
|
||||
if (_filter!.keyword.isNotEmpty) {
|
||||
final label = _filter!.keyword;
|
||||
final item = TransactionFilterOptionBarItem(
|
||||
label: label,
|
||||
onPressed: (s) {
|
||||
items.removeWhere((e) => e.label == label);
|
||||
if (items.isEmpty) {
|
||||
ref.read(transactionFilterProvider.state).state = null;
|
||||
} else {
|
||||
ref.read(transactionFilterProvider.state).state =
|
||||
ref.read(transactionFilterProvider.state).state?.copyWith(
|
||||
keyword: "",
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
items.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 32,
|
||||
child: ListView.separated(
|
||||
primary: false,
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
itemCount: items.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
itemBuilder: (context, index) => items[index],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TransactionFilterOptionBarItem extends StatelessWidget {
|
||||
const TransactionFilterOptionBarItem({
|
||||
Key? key,
|
||||
required this.label,
|
||||
this.onPressed,
|
||||
}) : super(key: key);
|
||||
|
||||
final String label;
|
||||
final void Function(String)? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => onPressed?.call(label),
|
||||
child: Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.buttonBackSecondary,
|
||||
borderRadius: BorderRadius.circular(1000)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
style: STextStyles.labelExtraExtraSmall(context).copyWith(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
XIcon(
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopTransactionCardRow extends ConsumerStatefulWidget {
|
||||
const DesktopTransactionCardRow({
|
||||
Key? key,
|
||||
|
|
|
@ -68,8 +68,11 @@ class _TransactionSearchViewState
|
|||
_selectedFromDate = filterState.from;
|
||||
_keywordTextEditingController.text = filterState.keyword;
|
||||
|
||||
_fromDateString = Format.formatDate(_selectedFromDate);
|
||||
_toDateString = Format.formatDate(_selectedToDate);
|
||||
_fromDateString = _selectedFromDate == null
|
||||
? ""
|
||||
: Format.formatDate(_selectedFromDate!);
|
||||
_toDateString =
|
||||
_selectedToDate == null ? "" : Format.formatDate(_selectedToDate!);
|
||||
|
||||
// TODO: Fix XMR (modify Format.funcs to take optional Coin parameter)
|
||||
// final amt = Format.satoshisToAmount(widget.coin == Coin.monero ? )
|
||||
|
@ -118,8 +121,8 @@ class _TransactionSearchViewState
|
|||
);
|
||||
}
|
||||
|
||||
var _selectedFromDate = DateTime(2007);
|
||||
var _selectedToDate = DateTime.now();
|
||||
DateTime? _selectedFromDate = DateTime(2007);
|
||||
DateTime? _selectedToDate = DateTime.now();
|
||||
|
||||
MaterialRoundedDatePickerStyle _buildDatePickerStyle() {
|
||||
return MaterialRoundedDatePickerStyle(
|
||||
|
@ -226,17 +229,22 @@ class _TransactionSearchViewState
|
|||
_selectedFromDate = date;
|
||||
|
||||
// flag to adjust date so from date is always before to date
|
||||
final flag = !_selectedFromDate.isBefore(_selectedToDate);
|
||||
final flag = _selectedToDate != null &&
|
||||
!_selectedFromDate!.isBefore(_selectedToDate!);
|
||||
if (flag) {
|
||||
_selectedToDate = DateTime.fromMillisecondsSinceEpoch(
|
||||
_selectedFromDate.millisecondsSinceEpoch);
|
||||
_selectedFromDate!.millisecondsSinceEpoch);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
if (flag) {
|
||||
_toDateString = Format.formatDate(_selectedToDate);
|
||||
_toDateString = _selectedToDate == null
|
||||
? ""
|
||||
: Format.formatDate(_selectedToDate!);
|
||||
}
|
||||
_fromDateString = Format.formatDate(_selectedFromDate);
|
||||
_fromDateString = _selectedFromDate == null
|
||||
? ""
|
||||
: Format.formatDate(_selectedFromDate!);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -333,17 +341,22 @@ class _TransactionSearchViewState
|
|||
_selectedToDate = date;
|
||||
|
||||
// flag to adjust date so from date is always before to date
|
||||
final flag = !_selectedToDate.isAfter(_selectedFromDate);
|
||||
final flag = _selectedFromDate != null &&
|
||||
!_selectedToDate!.isAfter(_selectedFromDate!);
|
||||
if (flag) {
|
||||
_selectedFromDate = DateTime.fromMillisecondsSinceEpoch(
|
||||
_selectedToDate.millisecondsSinceEpoch);
|
||||
_selectedToDate!.millisecondsSinceEpoch);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
if (flag) {
|
||||
_fromDateString = Format.formatDate(_selectedFromDate);
|
||||
_fromDateString = _selectedFromDate == null
|
||||
? ""
|
||||
: Format.formatDate(_selectedFromDate!);
|
||||
}
|
||||
_toDateString = Format.formatDate(_selectedToDate);
|
||||
_toDateString = _selectedToDate == null
|
||||
? ""
|
||||
: Format.formatDate(_selectedToDate!);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -975,6 +988,7 @@ class _TransactionSearchViewState
|
|||
final TransactionFilter filter = TransactionFilter(
|
||||
sent: _isActiveSentCheckbox,
|
||||
received: _isActiveReceivedCheckbox,
|
||||
trade: _isActiveTradeCheckbox,
|
||||
from: _selectedFromDate,
|
||||
to: _selectedToDate,
|
||||
amount: amount,
|
||||
|
|
Loading…
Reference in a new issue