Rework filter on the transactions list screen

This commit is contained in:
Serhii 2022-11-10 13:25:03 +02:00
parent d99aac9d98
commit 1f08d87471
8 changed files with 350 additions and 157 deletions

View file

@ -24,6 +24,9 @@ class ExchangeProviderDescription extends EnumerableItem<int>
static const simpleSwap = static const simpleSwap =
ExchangeProviderDescription(title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png'); ExchangeProviderDescription(title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png');
static const all =
ExchangeProviderDescription(title: 'All trades', raw: 5, image:'');
static ExchangeProviderDescription deserialize({required int raw}) { static ExchangeProviderDescription deserialize({required int raw}) {
switch (raw) { switch (raw) {
case 0: case 0:
@ -36,6 +39,8 @@ class ExchangeProviderDescription extends EnumerableItem<int>
return sideShift; return sideShift;
case 4: case 4:
return simpleSwap; return simpleSwap;
case 5:
return all;
default: default:
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize'); throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');
} }

View file

@ -9,12 +9,7 @@ class FilterTile extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.only( padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 24.0),
top: 18,
bottom: 18,
left: 24,
right: 24
),
child: child, child: child,
); );
} }

View file

@ -6,20 +6,21 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart'; import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart'; import 'package:cake_wallet/src/widgets/alert_close_button.dart';
import 'package:cake_wallet/src/widgets/checkbox_widget.dart'; import 'package:cake_wallet/src/widgets/rounded_checkbox.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
//import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker; //import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker;
class FilterWidget extends StatelessWidget { class FilterWidget extends StatelessWidget {
FilterWidget({required this.dashboardViewModel}); FilterWidget({required this.dashboardViewModel});
final DashboardViewModel dashboardViewModel; final DashboardViewModel dashboardViewModel;
final backVector = Image.asset('assets/images/back_vector.png', final closeIcon =
color: Palette.darkBlueCraiola Image.asset('assets/images/close.png', color: Palette.darkBlueCraiola);
);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const sectionDivider = SectionDivider();
return AlertBackground( return AlertBackground(
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
@ -38,118 +39,150 @@ class FilterWidget extends StatelessWidget {
), ),
), ),
Padding( Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(left: 24, right: 24, top: 24),
left: 24,
right: 24,
top: 24
),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(14)), borderRadius: BorderRadius.all(Radius.circular(24)),
child: Container( child: Container(
color: Theme.of(context).textTheme!.bodyText1!.decorationColor!, color: Theme.of(context)
child: ListView.separated( .textTheme!
shrinkWrap: true, .bodyText1!
physics: const NeverScrollableScrollPhysics(), .decorationColor!,
itemCount: dashboardViewModel.filterItems.length, child: Column(
separatorBuilder: (context, _) => Container( crossAxisAlignment: CrossAxisAlignment.start,
height: 1, children: [
color: Theme.of(context).accentTextTheme!.subtitle1!.backgroundColor!, Padding(
), padding: EdgeInsets.all(24.0),
itemBuilder: (_, index1) { child: Text(
final title = dashboardViewModel.filterItems.keys.elementAt(index1); S.of(context).filters,
final section = dashboardViewModel.filterItems.values.elementAt(index1); style: TextStyle(
color: Theme.of(context)
return Column( .textTheme!
crossAxisAlignment: CrossAxisAlignment.start, .bodyText1!
children: <Widget>[ .decorationColor!,
Padding( fontSize: 16,
padding: EdgeInsets.only( fontFamily: 'Lato',
top: 20, decoration: TextDecoration.none,
left: 24,
right: 24
),
child: Text(
title,
style: TextStyle(
color: Theme.of(context).accentTextTheme!.subtitle1!.color!,
fontSize: 16,
fontWeight: FontWeight.w500,
fontFamily: 'Lato',
decoration: TextDecoration.none
),
), ),
), ),
ListView.separated( ),
shrinkWrap: true, sectionDivider,
physics: const NeverScrollableScrollPhysics(), ListView.separated(
itemCount: section.length, padding: EdgeInsets.zero,
separatorBuilder: (context, _) => Container( shrinkWrap: true,
height: 1, physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(left: 24), itemCount: dashboardViewModel.filterItems.length,
color: Theme.of(context).textTheme!.bodyText1!.decorationColor!, separatorBuilder: (context, _) => sectionDivider,
child: Container( itemBuilder: (_, index1) {
height: 1, final title = dashboardViewModel.filterItems.keys
color: Theme.of(context).accentTextTheme!.subtitle1!.backgroundColor!, .elementAt(index1);
), final section = dashboardViewModel
), .filterItems.values
itemBuilder: (_, index2) { .elementAt(index1);
return Column(
final item = section[index2]; crossAxisAlignment: CrossAxisAlignment.start,
final content = item.onChanged != null children: <Widget>[
? CheckboxWidget( Padding(
value: item.value(), padding: EdgeInsets.only(
caption: item.caption, top: 20, left: 24, right: 24),
onChanged: item.onChanged
)
: GestureDetector(
onTap: () async {
//final List<DateTime> picked =
//await date_rage_picker.showDatePicker(
// context: context,
// initialFirstDate: DateTime.now()
// .subtract(Duration(days: 1)),
// initialLastDate: (DateTime.now()),
// firstDate: DateTime(2015),
// lastDate: DateTime.now()
// .add(Duration(days: 1)));
//if (picked != null && picked.length == 2) {
// dashboardViewModel.transactionFilterStore
// .changeStartDate(picked.first);
// dashboardViewModel.transactionFilterStore
// .changeEndDate(picked.last);
//}
},
child: Padding(
padding: EdgeInsets.only(left: 32),
child: Text( child: Text(
item.caption, title,
style: TextStyle( style: TextStyle(
color: Theme.of(context).primaryTextTheme!.headline6!.color!, color: Theme.of(context)
fontSize: 18, .primaryTextTheme!
.headline6!
.color!,
fontSize: 16,
fontFamily: 'Lato', fontFamily: 'Lato',
fontWeight: FontWeight.w500, fontWeight: FontWeight.bold,
decoration: TextDecoration.none decoration: TextDecoration.none),
),
), ),
), ),
); ListView.builder(
padding:
EdgeInsets.symmetric(vertical: 8.0),
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
itemCount: section.length,
itemBuilder: (_, index2) {
final item = section[index2];
final content = item.onChanged != null
? Observer(
builder: (_) =>
RoundedCheckboxWidget(
value: item.value.value,
caption: item.caption,
onChanged: item.onChanged,
currentTheme:
dashboardViewModel
.settingsStore
.currentTheme,
))
: GestureDetector(
onTap: () async {
//final List<DateTime> picked =
//await date_rage_picker.showDatePicker(
// context: context,
// initialFirstDate: DateTime.now()
// .subtract(Duration(days: 1)),
// initialLastDate: (DateTime.now()),
// firstDate: DateTime(2015),
// lastDate: DateTime.now()
// .add(Duration(days: 1)));
return FilterTile(child: content); //if (picked != null && picked.length == 2) {
}, // dashboardViewModel.transactionFilterStore
) // .changeStartDate(picked.first);
], // dashboardViewModel.transactionFilterStore
); // .changeEndDate(picked.last);
}, //}
), },
child: Padding(
padding:
EdgeInsets.only(left: 32),
child: Text(
item.caption,
style: TextStyle(
color: Colors.red,
//Theme.of(context).primaryTextTheme.title.color,//
fontSize: 18,
fontFamily: 'Lato',
fontWeight:
FontWeight.w500,
decoration:
TextDecoration.none),
),
),
);
return FilterTile(child: content);
},
)
],
);
},
),
]),
), ),
), ),
), ),
], ],
), ),
AlertCloseButton(image: backVector) AlertCloseButton(image: closeIcon)
], ],
), ),
); );
} }
}
class SectionDivider extends StatelessWidget {
const SectionDivider();
@override
Widget build(BuildContext context) {
return Container(
height: 1,
color: Colors.red,//Fixme Theme.of(context).accentTextTheme.subhead.backgroundColor,
);
}
} }

View file

@ -0,0 +1,91 @@
import 'package:cake_wallet/palette.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/themes/theme_base.dart';
class RoundedCheckboxWidget extends StatelessWidget {
RoundedCheckboxWidget(
{required this.value,
required this.caption,
required this.onChanged,
this.currentTheme});
final bool value;
final String caption;
final Function onChanged;
final ThemeBase? currentTheme;
bool get darkTheme => currentTheme!.type == ThemeType.dark;
@override
Widget build(BuildContext context) {
final baseGradient = LinearGradient(colors: [
Colors.red, //Fixme Theme.of(context).primaryTextTheme!.subtitle!.color!,
Colors.red //Fixme Theme.of(context).primaryTextTheme!.subtitle!.decorationColor!,
], begin: Alignment.centerLeft, end: Alignment.centerRight);
final darkThemeGradient = LinearGradient(colors: [
Palette.blueCraiola,
Palette.blueGreyCraiola,
], begin: Alignment.topLeft, end: Alignment.bottomRight);
final gradient = darkTheme ? darkThemeGradient : baseGradient;
final uncheckedColor = darkTheme
? Colors.red //Fixme Theme.of(context).accentTextTheme.subhead.decorationColor
: Colors.white;
final borderColor = darkTheme
? Colors.red //Fixme Theme.of(context).accentTextTheme.subtitle.backgroundColor
: Colors.transparent;
final checkedOuterBoxDecoration =
BoxDecoration(shape: BoxShape.circle, gradient: gradient);
final outerBoxDecoration = BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).accentTextTheme.overline!.color!,
border: Border.all(color: borderColor));
final checkedInnerBoxDecoration =
BoxDecoration(shape: BoxShape.circle, color: Colors.white);
final innerBoxDecoration =
BoxDecoration(shape: BoxShape.circle, color: uncheckedColor);
return GestureDetector(
onTap: () => onChanged(),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
height: 24.0,
width: 24.0,
child: DecoratedBox(
decoration:
value ? checkedOuterBoxDecoration : outerBoxDecoration,
child: Padding(
padding: EdgeInsets.all(value ? 4.0 : 1.0),
child: DecoratedBox(
decoration:
value ? checkedInnerBoxDecoration : innerBoxDecoration,
),
),
),
),
Padding(
padding: EdgeInsets.only(left: 16),
child: Text(
caption,
style: TextStyle(
color: Colors.red, //Fixme Theme.of(context).primaryTextTheme.title.color,
fontSize: 18,
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
decoration: TextDecoration.none),
),
)
],
),
);
}
}

View file

@ -8,39 +8,61 @@ part'trade_filter_store.g.dart';
class TradeFilterStore = TradeFilterStoreBase with _$TradeFilterStore; class TradeFilterStore = TradeFilterStoreBase with _$TradeFilterStore;
abstract class TradeFilterStoreBase with Store { abstract class TradeFilterStoreBase with Store {
TradeFilterStoreBase( TradeFilterStoreBase();
{this.displayXMRTO = true,
this.displayChangeNow = true,
this.displayMorphToken = true,
this.displaySimpleSwap = true,
});
@observable Observable<bool> displayXMRTO = Observable(true);
bool displayXMRTO; Observable<bool> displayAllTrades = Observable(true);
Observable<bool> displayChangeNow = Observable(true);
@observable Observable<bool> displaySideShift = Observable(true);
bool displayChangeNow; Observable<bool> displayMorphToken = Observable(true);
Observable<bool> displaySimpleSwap = Observable(true);
@observable
bool displayMorphToken;
@observable
bool displaySimpleSwap;
@action @action
void toggleDisplayExchange(ExchangeProviderDescription provider) { void toggleDisplayExchange(ExchangeProviderDescription provider) {
switch (provider) { switch (provider) {
case ExchangeProviderDescription.changeNow: case ExchangeProviderDescription.changeNow:
displayChangeNow = !displayChangeNow; displayAllTrades.value = false;
displayChangeNow.value = !displayChangeNow.value;
if (displayChangeNow.value && displaySideShift.value && displaySimpleSwap.value) {
displayAllTrades.value = true;
}
break; break;
case ExchangeProviderDescription.xmrto: case ExchangeProviderDescription.sideShift:
displayXMRTO = !displayXMRTO; displayAllTrades.value = false;
break; displaySideShift.value = !displaySideShift.value;
case ExchangeProviderDescription.morphToken: if (displayChangeNow.value && displaySideShift.value && displaySimpleSwap.value) {
displayMorphToken = !displayMorphToken; displayAllTrades.value = true;
}
break; break;
case ExchangeProviderDescription.simpleSwap: case ExchangeProviderDescription.simpleSwap:
displaySimpleSwap = !displaySimpleSwap; displayAllTrades.value = false;
displaySimpleSwap.value = !displaySimpleSwap.value;
if (displayChangeNow.value && displaySideShift.value && displaySimpleSwap.value) {
displayAllTrades.value = true;
}
break;
case ExchangeProviderDescription.xmrto:
displayXMRTO.value = !displayXMRTO.value;
break;
case ExchangeProviderDescription.morphToken:
displayMorphToken.value = !displayMorphToken.value;
break;
case ExchangeProviderDescription.all:
displayAllTrades.value = !displayAllTrades.value;
if (displayAllTrades.value) {
displayChangeNow.value = true;
displaySideShift.value = true;
displayXMRTO.value = true;
displayMorphToken.value = true;
displaySimpleSwap.value = true;
}
if (!displayAllTrades.value) {
displayChangeNow.value = false;
displaySideShift.value = false;
displayXMRTO.value = false;
displayMorphToken.value = false;
displaySimpleSwap.value = false;
}
break; break;
} }
} }
@ -48,20 +70,22 @@ abstract class TradeFilterStoreBase with Store {
List<TradeListItem> filtered({required List<TradeListItem> trades, required WalletBase wallet}) { List<TradeListItem> filtered({required List<TradeListItem> trades, required WalletBase wallet}) {
final _trades = final _trades =
trades.where((item) => item.trade.walletId == wallet.id).toList(); trades.where((item) => item.trade.walletId == wallet.id).toList();
final needToFilter = !displayChangeNow || !displayXMRTO || !displayMorphToken || !displaySimpleSwap; final needToFilter = !displayChangeNow.value || !displaySideShift.value
|| !displayXMRTO.value || !displayMorphToken.value
|| !displaySimpleSwap.value;
return needToFilter return needToFilter
? _trades ? _trades
.where((item) => .where((item) =>
(displayXMRTO && (displayXMRTO.value &&
item.trade.provider == ExchangeProviderDescription.xmrto) || item.trade.provider == ExchangeProviderDescription.xmrto) ||
(displayChangeNow && (displayChangeNow.value &&
item.trade.provider == item.trade.provider ==
ExchangeProviderDescription.changeNow) || ExchangeProviderDescription.changeNow) ||
(displayMorphToken && (displayMorphToken.value &&
item.trade.provider == item.trade.provider ==
ExchangeProviderDescription.morphToken) ExchangeProviderDescription.morphToken)
||(displaySimpleSwap && ||(displaySimpleSwap.value &&
item.trade.provider == item.trade.provider ==
ExchangeProviderDescription.simpleSwap)) ExchangeProviderDescription.simpleSwap))
.toList() .toList()

View file

@ -1,6 +1,8 @@
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_direction.dart';
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
import 'package:cake_wallet/generated/i18n.dart';
part 'transaction_filter_store.g.dart'; part 'transaction_filter_store.g.dart';
@ -8,14 +10,11 @@ class TransactionFilterStore = TransactionFilterStoreBase
with _$TransactionFilterStore; with _$TransactionFilterStore;
abstract class TransactionFilterStoreBase with Store { abstract class TransactionFilterStoreBase with Store {
TransactionFilterStoreBase( TransactionFilterStoreBase();
{this.displayIncoming = true, this.displayOutgoing = true});
@observable Observable<bool> displayAll = Observable(true);
bool displayIncoming; Observable<bool> displayIncoming = Observable(true);
Observable<bool> displayOutgoing = Observable(true);
@observable
bool displayOutgoing;
@observable @observable
DateTime? startDate; DateTime? startDate;
@ -24,10 +23,40 @@ abstract class TransactionFilterStoreBase with Store {
DateTime? endDate; DateTime? endDate;
@action @action
void toggleIncoming() => displayIncoming = !displayIncoming; void toggleIAll() {
displayAll.value = (!displayAll.value);
if (displayAll.value) {
displayOutgoing.value = true;
displayIncoming.value = true;
}
if (!displayAll.value) {
displayOutgoing.value = false;
displayIncoming.value = false;
}
}
@action @action
void toggleOutgoing() => displayOutgoing = !displayOutgoing; void toggleIncoming() {
displayIncoming.value = (!displayIncoming.value);
if (displayIncoming.value && displayOutgoing.value) {
displayAll.value = true;
}
if (!displayIncoming.value || !displayOutgoing.value) {
displayAll.value = false;
}
}
@action
void toggleOutgoing() {
displayOutgoing.value = (!displayOutgoing.value);
if (displayIncoming.value && displayOutgoing.value) {
displayAll.value = true;
}
if (!displayIncoming.value || !displayOutgoing.value) {
displayAll.value = false;
}
}
@action @action
void changeStartDate(DateTime date) => startDate = date; void changeStartDate(DateTime date) => startDate = date;
@ -37,8 +66,8 @@ abstract class TransactionFilterStoreBase with Store {
List<TransactionListItem> filtered({required List<TransactionListItem> transactions}) { List<TransactionListItem> filtered({required List<TransactionListItem> transactions}) {
var _transactions = <TransactionListItem>[]; var _transactions = <TransactionListItem>[];
final needToFilter = !displayOutgoing || final needToFilter = !displayOutgoing.value ||
!displayIncoming || !displayIncoming.value ||
(startDate != null && endDate != null); (startDate != null && endDate != null);
if (needToFilter) { if (needToFilter) {
@ -50,11 +79,11 @@ abstract class TransactionFilterStoreBase with Store {
&& (endDate?.isAfter(item.transaction.date) ?? false); && (endDate?.isAfter(item.transaction.date) ?? false);
} }
if (allowed && (!displayOutgoing || !displayIncoming)) { if (allowed && (!displayOutgoing.value || !displayIncoming.value)) {
allowed = (displayOutgoing && allowed = (displayOutgoing.value &&
item.transaction.direction == item.transaction.direction ==
TransactionDirection.outgoing) || TransactionDirection.outgoing) ||
(displayIncoming && (displayIncoming.value &&
item.transaction.direction == TransactionDirection.incoming); item.transaction.direction == TransactionDirection.incoming);
} }

View file

@ -60,13 +60,17 @@ abstract class DashboardViewModelBase with Store {
filterItems = { filterItems = {
S.current.transactions: [ S.current.transactions: [
FilterItem( FilterItem(
value: () => transactionFilterStore.displayIncoming, value: transactionFilterStore.displayAll,
caption: S.current.incoming, caption: 'S.current.all_transactions',//Fixme
onChanged: (value) => transactionFilterStore.toggleIncoming()), onChanged: () => transactionFilterStore.toggleIAll()),
FilterItem( FilterItem(
value: () => transactionFilterStore.displayOutgoing, value: transactionFilterStore.displayIncoming,
caption: S.current.incoming,
onChanged: () => transactionFilterStore.toggleIncoming()),
FilterItem(
value: transactionFilterStore.displayOutgoing,
caption: S.current.outgoing, caption: S.current.outgoing,
onChanged: (value) => transactionFilterStore.toggleOutgoing()), onChanged: () => transactionFilterStore.toggleOutgoing()),
// FilterItem( // FilterItem(
// value: () => false, // value: () => false,
// caption: S.current.transactions_by_date, // caption: S.current.transactions_by_date,
@ -74,10 +78,20 @@ abstract class DashboardViewModelBase with Store {
], ],
S.current.trades: [ S.current.trades: [
FilterItem( FilterItem(
value: () => tradeFilterStore.displayChangeNow, value: tradeFilterStore.displayAllTrades,
caption: 'S.current.all_trades',//Fixme
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.all)),
FilterItem(
value: tradeFilterStore.displayChangeNow,
caption: 'Change.NOW', caption: 'Change.NOW',
onChanged: (value) => tradeFilterStore onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.changeNow)), .toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
FilterItem(
value: tradeFilterStore.displaySideShift,
caption: 'SideShift',
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.sideShift)),
] ]
}, },
subname = '', subname = '',

View file

@ -1,10 +1,12 @@
import 'package:mobx/mobx.dart';
class FilterItem { class FilterItem {
FilterItem({ FilterItem({
required this.value, required this.value,
required this.caption, required this.caption,
required this.onChanged}); required this.onChanged});
bool Function() value; Observable<bool> value;
String caption; String caption;
Function(bool) onChanged; Function onChanged;
} }