Cw 372 improve monero haven account selection picker (#939)

* refactor: Improve Monero/Haven account selection picker * feat: If the amount would be wrapped, instead display it under the account name * fix: balance str * refactor: add theme changes * refactor: remove duplicate observer

Rafael Saes committed Jul 10, 2023 at 21:40 UTC cb0ca169fbd0ba5397a2e6402e8558de6b6b2771
3 files changed +182 -172
lib/src/screens/monero_accounts/monero_account_list_page.dart
+65 -147
@@ -1,176 +1,94 @@
1 +import 'package:cake_wallet/src/widgets/picker_inner_wrapper_widget.dart';
2 import 'package:cake_wallet/src/widgets/section_divider.dart';
2 -import 'package:cake_wallet/utils/responsive_layout_util.dart';
3 import 'package:flutter/material.dart';
4 import 'package:flutter_mobx/flutter_mobx.dart';
5 import 'package:cake_wallet/routes.dart';
6 import 'package:cake_wallet/generated/i18n.dart';
7 import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
8 import 'package:cake_wallet/src/screens/monero_accounts/widgets/account_tile.dart';
9 -import 'package:cake_wallet/src/widgets/alert_background.dart';
10 -import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
11 -import 'package:cake_wallet/src/widgets/alert_close_button.dart';
9
10 class MoneroAccountListPage extends StatelessWidget {
14 - MoneroAccountListPage({required this.accountListViewModel})
15 - : backgroundHeight = 194,
16 - thumbHeight = 72,
17 - isAlwaysShowScrollThumb = false,
18 - controller = ScrollController() {
19 - controller.addListener(() {
20 - final scrollOffsetFromTop = controller.hasClients
21 - ? (controller.offset / controller.position.maxScrollExtent * (backgroundHeight - thumbHeight))
22 - : 0.0;
23 - accountListViewModel.setScrollOffsetFromTop(scrollOffsetFromTop);
24 - });
25 - }
11 + MoneroAccountListPage({required this.accountListViewModel});
12
13 final MoneroAccountListViewModel accountListViewModel;
28 -
29 - ScrollController controller;
30 - double backgroundHeight;
31 - double thumbHeight;
32 - bool isAlwaysShowScrollThumb;
14 + final ScrollController controller = ScrollController();
15
16 @override
17 Widget build(BuildContext context) {
36 - return AlertBackground(
37 - child: Column(
18 + double itemHeight = 80;
19 + double buttonHeight = 62;
20 +
21 + return Observer(builder: (_) {
22 + final accounts = accountListViewModel.accounts;
23 +
24 + return PickerInnerWrapperWidget(
25 + title: S.of(context).choose_account,
26 + itemsHeight: (itemHeight * accounts.length) + buttonHeight,
27 children: [
28 Expanded(
40 - child: Stack(
41 - alignment: Alignment.center,
42 - children: <Widget>[
43 - Column(
29 + child: Scrollbar(
30 + controller: controller,
31 + child: ListView.separated(
32 + padding: EdgeInsets.zero,
33 + controller: controller,
34 + separatorBuilder: (context, index) => const SectionDivider(),
35 + itemCount: accounts.length,
36 + itemBuilder: (context, index) {
37 + final account = accounts[index];
38 +
39 + return AccountTile(
40 + isCurrent: account.isSelected,
41 + accountName: account.label,
42 + accountBalance: account.balance ?? '0.00',
43 + currency: accountListViewModel.currency.toString(),
44 + onTap: () {
45 + if (account.isSelected) {
46 + return;
47 + }
48 +
49 + accountListViewModel.select(account);
50 + Navigator.of(context).pop();
51 + },
52 + onEdit: () async => await Navigator.of(context)
53 + .pushNamed(Routes.accountCreation, arguments: account));
54 + },
55 + ),
56 + )),
57 + GestureDetector(
58 + onTap: () async =>
59 + await Navigator.of(context).pushNamed(Routes.accountCreation),
60 + child: Container(
61 + height: buttonHeight,
62 + color: Theme.of(context).cardColor,
63 + padding: EdgeInsets.symmetric(horizontal: 24),
64 + child: Center(
65 + child: Row(
66 mainAxisSize: MainAxisSize.min,
67 children: <Widget>[
46 - Container(
47 - padding: EdgeInsets.only(left: 24, right: 24),
48 - child: Text(
49 - S.of(context).choose_account,
50 - textAlign: TextAlign.center,
51 - style: TextStyle(
52 - fontSize: 18,
53 - fontWeight: FontWeight.bold,
54 - fontFamily: 'Lato',
55 - decoration: TextDecoration.none,
56 - color: Colors.white
57 - ),
58 - ),
68 + Icon(
69 + Icons.add,
70 + color: Colors.white,
71 ),
72 Padding(
61 - padding: EdgeInsets.only(left: 24, right: 24, top: 24),
62 - child: GestureDetector(
63 - onTap: () => null,
64 - child: ClipRRect(
65 - borderRadius: BorderRadius.all(Radius.circular(14)),
66 - child: Container(
67 - height: 296,
68 - color: Theme.of(context).textTheme!.displayLarge!.decorationColor!,
69 - child: Column(
70 - children: <Widget>[
71 - Expanded(
72 - child: Observer(
73 - builder: (_) {
74 - final accounts = accountListViewModel.accounts;
75 - isAlwaysShowScrollThumb = accounts == null
76 - ? false
77 - : accounts.length > 3;
78 -
79 - return Stack(
80 - alignment: Alignment.center,
81 - children: <Widget>[
82 - ListView.separated(
83 - padding: EdgeInsets.zero,
84 - controller: controller,
85 - separatorBuilder: (context, index) =>
86 - const SectionDivider(),
87 - itemCount: accounts.length ?? 0,
88 - itemBuilder: (context, index) {
89 - final account = accounts[index];
90 -
91 - return AccountTile(
92 - isCurrent: account.isSelected,
93 - accountName: account.label,
94 - accountBalance: account.balance ?? '0.00',
95 - currency: accountListViewModel
96 - .currency.toString(),
97 - onTap: () {
98 - if (account.isSelected) {
99 - return;
100 - }
101 -
102 - accountListViewModel
103 - .select(account);
104 - Navigator.of(context).pop();
105 - },
106 - onEdit: () async =>
107 - await Navigator.of(context)
108 - .pushNamed(
109 - Routes.accountCreation,
110 - arguments: account));
111 - },
112 - ),
113 - isAlwaysShowScrollThumb
114 - ? CakeScrollbar(
115 - backgroundHeight: backgroundHeight,
116 - thumbHeight: thumbHeight,
117 - fromTop: accountListViewModel
118 - .scrollOffsetFromTop
119 - )
120 - : Offstage(),
121 - ],
122 - );
123 - }
124 - )
125 - ),
126 - GestureDetector(
127 - onTap: () async => await Navigator.of(context)
128 - .pushNamed(Routes.accountCreation),
129 - child: Container(
130 - height: 62,
131 - color: Theme.of(context).cardColor,
132 - padding: EdgeInsets.only(left: 24, right: 24),
133 - child: Center(
134 - child: Row(
135 - mainAxisSize: MainAxisSize.min,
136 - children: <Widget>[
137 - Icon(
138 - Icons.add,
139 - color: Colors.white,
140 - ),
141 - Padding(
142 - padding: EdgeInsets.only(left: 5),
143 - child: Text(
144 - S.of(context).create_new_account,
145 - style: TextStyle(
146 - fontSize: 15,
147 - fontWeight: FontWeight.w600,
148 - fontFamily: 'Lato',
149 - color: Colors.white,
150 - decoration: TextDecoration.none,
151 - ),
152 - ),
153 - )
154 - ],
155 - ),
156 - ),
157 - ),
158 - )
159 - ],
160 - ),
161 - ),
73 + padding: EdgeInsets.only(left: 5),
74 + child: Text(
75 + S.of(context).create_new_account,
76 + style: TextStyle(
77 + fontSize: 15,
78 + fontWeight: FontWeight.w600,
79 + fontFamily: 'Lato',
80 + color: Colors.white,
81 + decoration: TextDecoration.none,
82 ),
83 ),
84 )
85 ],
86 ),
167 - SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight),
168 - AlertCloseButton()
169 - ],
87 + ),
88 ),
171 - ),
89 + )
90 ],
173 - ),
174 - );
91 + );
92 + });
93 }
94 }
lib/src/screens/monero_accounts/widgets/account_tile.dart
+26 -25
@@ -1,14 +1,13 @@
1 import 'package:flutter/material.dart';
2
3 class AccountTile extends StatelessWidget {
4 - AccountTile({
5 - required this.isCurrent,
6 - required this.accountName,
7 - this.accountBalance,
8 - required this.currency,
9 - required this.onTap,
10 - required this.onEdit
11 - });
4 + AccountTile(
5 + {required this.isCurrent,
6 + required this.accountName,
7 + this.accountBalance,
8 + required this.currency,
9 + required this.onTap,
10 + required this.onEdit});
11
12 final bool isCurrent;
13 final String accountName;
@@ -32,11 +31,13 @@ class AccountTile extends StatelessWidget {
31 height: 77,
32 padding: EdgeInsets.only(left: 24, right: 24),
33 color: color,
35 - child: Row(
36 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
34 + child: Wrap(
35 + direction: Axis.horizontal,
36 + alignment: WrapAlignment.spaceBetween,
37 + runAlignment: WrapAlignment.center,
38 + crossAxisAlignment: WrapCrossAlignment.center,
39 children: [
38 - Expanded(
39 - flex: 2,
40 + Container(
41 child: Text(
42 accountName,
43 style: TextStyle(
@@ -49,19 +50,19 @@ class AccountTile extends StatelessWidget {
50 ),
51 ),
52 if (accountBalance != null)
52 - Expanded(
53 - child: Text(
54 - '${accountBalance.toString()} $currency',
55 - textAlign: TextAlign.end,
56 - style: TextStyle(
57 - fontSize: 15,
58 - fontWeight: FontWeight.w600,
59 - fontFamily: 'Lato',
60 - color: Theme.of(context).textTheme!.headlineMedium!.color!,
61 - decoration: TextDecoration.none,
53 + Container(
54 + child: Text(
55 + '${accountBalance.toString()} $currency',
56 + textAlign: TextAlign.end,
57 + style: TextStyle(
58 + fontSize: 15,
59 + fontWeight: FontWeight.w600,
60 + fontFamily: 'Lato',
61 + color: Theme.of(context).textTheme!.headlineMedium!.color!,
62 + decoration: TextDecoration.none,
63 + ),
64 ),
63 - ),
64 - ),
65 + ),
66 ],
67 ),
68 ),
@@ -80,4 +81,4 @@ class AccountTile extends StatelessWidget {
81 // onTap: () => onEdit?.call())
82 // ]);
83 }
83 -}
\ No newline at end of file
84 +}
lib/src/widgets/picker_inner_wrapper_widget.dart new
+91
@@ -0,0 +1,91 @@
1 +import 'package:cake_wallet/utils/responsive_layout_util.dart';
2 +import 'package:flutter/material.dart';
3 +import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
4 +
5 +class PickerInnerWrapperWidget extends StatelessWidget {
6 + PickerInnerWrapperWidget(
7 + {required this.children, this.title, this.itemsHeight});
8 +
9 + final List<Widget> children;
10 + final String? title;
11 + final double? itemsHeight;
12 +
13 + @override
14 + Widget build(BuildContext context) {
15 + final mq = MediaQuery.of(context);
16 + final bottom = mq.viewInsets.bottom;
17 + final height = mq.size.height - bottom;
18 +
19 + double containerHeight = height * 0.65;
20 + if (bottom > 0) {
21 + // increase a bit or it gets too squished in the top
22 + containerHeight = height * 0.75;
23 + }
24 +
25 + if (title != null) {
26 + return PickerWrapperWidget(
27 + hasTitle: true,
28 + children: <Widget>[
29 + Container(
30 + padding: EdgeInsets.symmetric(horizontal: 24),
31 + child: Text(
32 + title!,
33 + textAlign: TextAlign.center,
34 + style: TextStyle(
35 + fontSize: 18,
36 + fontWeight: FontWeight.bold,
37 + fontFamily: 'Lato',
38 + decoration: TextDecoration.none,
39 + color: Colors.white),
40 + ),
41 + ),
42 + Padding(
43 + padding: EdgeInsets.symmetric(horizontal: 24),
44 + child: ClipRRect(
45 + borderRadius: BorderRadius.all(Radius.circular(14)),
46 + child: Container(
47 + color: Theme.of(context).textTheme.displayLarge!.decorationColor!,
48 + child: ConstrainedBox(
49 + constraints: BoxConstraints(
50 + maxHeight:
51 + itemsHeight != null && itemsHeight! <= containerHeight
52 + ? itemsHeight!
53 + : containerHeight,
54 + maxWidth: ResponsiveLayoutUtil.kPopupWidth,
55 + ),
56 + child: Column(
57 + children: children,
58 + ),
59 + ),
60 + ),
61 + ),
62 + )
63 + ],
64 + );
65 + }
66 +
67 + return PickerWrapperWidget(
68 + hasTitle: false,
69 + children: <Widget>[
70 + Padding(
71 + padding: EdgeInsets.symmetric(horizontal: 24),
72 + child: ClipRRect(
73 + borderRadius: BorderRadius.all(Radius.circular(14)),
74 + child: Container(
75 + color: Theme.of(context).textTheme.displayLarge!.decorationColor!,
76 + child: ConstrainedBox(
77 + constraints: BoxConstraints(
78 + maxHeight: containerHeight,
79 + maxWidth: ResponsiveLayoutUtil.kPopupWidth,
80 + ),
81 + child: Column(
82 + children: children,
83 + ),
84 + ),
85 + ),
86 + ),
87 + )
88 + ],
89 + );
90 + }
91 +}