CWA-215 | added cryptocurrency icons to address book; applied new design to contact page; added suffixIcon parameter to base text form field; fixed seed widget

Oleksandr Sobol committed May 20, 2020 at 20:28 UTC 3a8bae74e4d3f8146829a5a2710859053ae1bb8e
16 files changed +243 -241
assets/images/ada.png
Binary files /dev/null and b/assets/images/ada.png differ
assets/images/bch.png
Binary files /dev/null and b/assets/images/bch.png differ
assets/images/bnb.png
Binary files /dev/null and b/assets/images/bnb.png differ
assets/images/dash.png
Binary files /dev/null and b/assets/images/dash.png differ
assets/images/eos.png
Binary files /dev/null and b/assets/images/eos.png differ
assets/images/eth.png
Binary files /dev/null and b/assets/images/eth.png differ
assets/images/nano.png
Binary files /dev/null and b/assets/images/nano.png differ
assets/images/trx.png
Binary files /dev/null and b/assets/images/trx.png differ
assets/images/usdt.png
Binary files /dev/null and b/assets/images/usdt.png differ
assets/images/xlm.png
Binary files /dev/null and b/assets/images/xlm.png differ
assets/images/xrp.png
Binary files /dev/null and b/assets/images/xrp.png differ
lib/src/screens/address_book/address_book_page.dart
+11 -11
@@ -216,43 +216,43 @@ class AddressBookPage extends BasePage {
216 image = Image.asset('assets/images/monero.png', height: 24, width: 24);
217 break;
218 case CryptoCurrency.ada:
219 - image = null;
219 + image = Image.asset('assets/images/ada.png', height: 24, width: 24);
220 break;
221 case CryptoCurrency.bch:
222 - image = null;
222 + image = Image.asset('assets/images/bch.png', height: 24, width: 24);
223 break;
224 case CryptoCurrency.bnb:
225 - image = null;
225 + image = Image.asset('assets/images/bnb.png', height: 24, width: 24);
226 break;
227 case CryptoCurrency.btc:
228 image = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
229 break;
230 case CryptoCurrency.dash:
231 - image = null;
231 + image = Image.asset('assets/images/dash.png', height: 24, width: 24);
232 break;
233 case CryptoCurrency.eos:
234 - image = null;
234 + image = Image.asset('assets/images/eos.png', height: 24, width: 24);
235 break;
236 case CryptoCurrency.eth:
237 - image = null;
237 + image = Image.asset('assets/images/eth.png', height: 24, width: 24);
238 break;
239 case CryptoCurrency.ltc:
240 image = Image.asset('assets/images/litecoin.png', height: 24, width: 24);
241 break;
242 case CryptoCurrency.nano:
243 - image = null;
243 + image = Image.asset('assets/images/nano.png', height: 24, width: 24);
244 break;
245 case CryptoCurrency.trx:
246 - image = null;
246 + image = Image.asset('assets/images/trx.png', height: 24, width: 24);
247 break;
248 case CryptoCurrency.usdt:
249 - image = null;
249 + image = Image.asset('assets/images/usdt.png', height: 24, width: 24);
250 break;
251 case CryptoCurrency.xlm:
252 - image = null;
252 + image = Image.asset('assets/images/xlm.png', height: 24, width: 24);
253 break;
254 case CryptoCurrency.xrp:
255 - image = null;
255 + image = Image.asset('assets/images/xrp.png', height: 24, width: 24);
256 break;
257 default:
258 image = null;
lib/src/screens/address_book/contact_page.dart
+166 -184
@@ -1,5 +1,6 @@
1 import 'package:flutter/material.dart';
2 import 'package:flutter/cupertino.dart';
3 +import 'package:flutter_mobx/flutter_mobx.dart';
4 import 'package:provider/provider.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
@@ -10,6 +11,9 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
11 import 'package:cake_wallet/src/widgets/address_text_field.dart';
12 import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
13 import 'package:cake_wallet/palette.dart';
14 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
15 +import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
16 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
17
18 class ContactPage extends BasePage {
19 ContactPage({this.contact});
@@ -19,6 +23,9 @@ class ContactPage extends BasePage {
23 @override
24 String get title => S.current.contact;
25
26 + @override
27 + Color get backgroundColor => PaletteDark.historyPanel;
28 +
29 @override
30 Widget body(BuildContext context) => ContactForm(contact);
31 }
@@ -37,22 +44,31 @@ class ContactFormState extends State<ContactForm> {
44 final _contactNameController = TextEditingController();
45 final _currencyTypeController = TextEditingController();
46 final _addressController = TextEditingController();
47 + final currencies = CryptoCurrency.all;
48 + final downArrow = Image.asset(
49 + 'assets/images/arrow_bottom_purple_icon.png',
50 + color: PaletteDark.walletCardText,
51 + height: 8);
52
41 - CryptoCurrency _selectectCrypto = CryptoCurrency.xmr;
53 + CryptoCurrency _selectectCrypto;
54
55 @override
56 void initState() {
57 super.initState();
46 - if (widget.contact == null) {
47 - _currencyTypeController.text = _selectectCrypto.toString();
48 - } else {
58 + if (widget.contact != null) {
59 _selectectCrypto = widget.contact.type;
60 _contactNameController.text = widget.contact.name;
61 _currencyTypeController.text = _selectectCrypto.toString();
62 _addressController.text = widget.contact.address;
63 + WidgetsBinding.instance.addPostFrameCallback(afterLayout);
64 }
65 }
66
67 + void afterLayout(dynamic _) {
68 + final addressBookStore = Provider.of<AddressBookStore>(context);
69 + addressBookStore.setDisabledStatus(false);
70 + }
71 +
72 @override
73 void dispose() {
74 _contactNameController.dispose();
@@ -61,198 +77,164 @@ class ContactFormState extends State<ContactForm> {
77 super.dispose();
78 }
79
64 - Future<void> _setCurrencyType(BuildContext context) async {
65 - var currencyType = CryptoCurrency.all[0].toString();
66 - var selectedCurrency = CryptoCurrency.all[0];
67 -
68 - await showDialog<void>(
69 - context: context,
70 - builder: (BuildContext context) {
71 - return AlertDialog(
72 - title: Text(S.of(context).please_select),
73 - backgroundColor: Theme.of(context).backgroundColor,
74 - content: Container(
75 - height: 150.0,
76 - child: CupertinoPicker(
77 - backgroundColor: Theme.of(context).backgroundColor,
78 - itemExtent: 45.0,
79 - onSelectedItemChanged: (int index) {
80 - selectedCurrency = CryptoCurrency.all[index];
81 - currencyType = CryptoCurrency.all[index].toString();
82 - },
83 - children:
84 - List.generate(CryptoCurrency.all.length, (int index) {
85 - return Center(
86 - child: Text(
87 - CryptoCurrency.all[index].toString(),
88 - style: TextStyle(
89 - color: Theme.of(context)
90 - .primaryTextTheme
91 - .caption
92 - .color),
93 - ),
94 - );
95 - })),
96 - ),
97 - actions: <Widget>[
98 - FlatButton(
99 - onPressed: () {
100 - Navigator.pop(context);
101 - },
102 - child: Text(S.of(context).cancel)),
103 - FlatButton(
104 - onPressed: () {
105 - _selectectCrypto = selectedCurrency;
106 - _currencyTypeController.text = currencyType;
107 - Navigator.of(context).pop();
108 - },
109 - child: Text(S.of(context).ok))
110 - ],
111 - );
112 - });
80 + void onHandleControllers(AddressBookStore addressBookStore) {
81 + if (_contactNameController.text.isNotEmpty &&
82 + _addressController.text.isNotEmpty &&
83 + _currencyTypeController.text.isNotEmpty) {
84 + addressBookStore.setDisabledStatus(false);
85 + } else {
86 + addressBookStore.setDisabledStatus(true);
87 + }
88 }
89
90 @override
91 Widget build(BuildContext context) {
92 final addressBookStore = Provider.of<AddressBookStore>(context);
93
119 - return ScrollableWithBottomSection(
120 - content: Form(
121 - key: _formKey,
122 - child: Column(
123 - mainAxisSize: MainAxisSize.min,
124 - children: <Widget>[
125 - TextFormField(
126 - style: TextStyle(
127 - fontSize: 14.0,
128 - color: Theme.of(context).primaryTextTheme.headline.color),
129 - decoration: InputDecoration(
130 - hintStyle: TextStyle(color: Theme.of(context).hintColor),
131 - hintText: S.of(context).contact_name,
132 - focusedBorder: UnderlineInputBorder(
133 - borderSide:
134 - BorderSide(color: Palette.cakeGreen, width: 2.0)),
135 - enabledBorder: UnderlineInputBorder(
136 - borderSide: BorderSide(
137 - color: Theme.of(context).focusColor, width: 1.0))),
138 - controller: _contactNameController,
139 - validator: (value) {
140 - addressBookStore.validateContactName(value);
141 - return addressBookStore.errorMessage;
142 - },
143 - ),
144 - SizedBox(height: 14.0),
145 - Container(
146 - child: InkWell(
147 - onTap: () => _setCurrencyType(context),
148 - child: IgnorePointer(
149 - child: TextFormField(
150 - style: TextStyle(
151 - fontSize: 14.0,
152 - color: Theme.of(context)
153 - .primaryTextTheme
154 - .headline
155 - .color),
156 - decoration: InputDecoration(
157 - focusedBorder: UnderlineInputBorder(
158 - borderSide: BorderSide(
159 - color: Palette.cakeGreen, width: 2.0)),
160 - enabledBorder: UnderlineInputBorder(
161 - borderSide: BorderSide(
162 - color: Theme.of(context).focusColor,
163 - width: 1.0))),
164 - controller: _currencyTypeController,
94 + _contactNameController.addListener(() {onHandleControllers(addressBookStore);});
95 + _currencyTypeController.addListener(() {onHandleControllers(addressBookStore);});
96 + _addressController.addListener(() {onHandleControllers(addressBookStore);});
97 +
98 + return Container(
99 + color: PaletteDark.historyPanel,
100 + child: ScrollableWithBottomSection(
101 + contentPadding: EdgeInsets.all(24),
102 + content: Form(
103 + key: _formKey,
104 + child: Column(
105 + mainAxisSize: MainAxisSize.min,
106 + children: <Widget>[
107 + BaseTextFormField(
108 + controller: _contactNameController,
109 + hintText: S.of(context).contact_name,
110 + borderColor: PaletteDark.walletCardSubAddressField,
111 + validator: (value) {
112 + addressBookStore.validateContactName(value);
113 + return addressBookStore.errorMessage;
114 + }
115 + ),
116 + Padding(
117 + padding: EdgeInsets.only(top: 20),
118 + child: Container(
119 + child: InkWell(
120 + onTap: () => _presentPicker(context),
121 + child: IgnorePointer(
122 + child: BaseTextFormField(
123 + controller: _currencyTypeController,
124 + hintText: S.of(context).settings_currency,
125 + borderColor: PaletteDark.walletCardSubAddressField,
126 + suffixIcon: Row(
127 + mainAxisSize: MainAxisSize.min,
128 + mainAxisAlignment: MainAxisAlignment.end,
129 + children: <Widget>[
130 + downArrow
131 + ],
132 + ),
133 + )
134 + ),
135 ),
136 ),
137 ),
168 - ),
169 - SizedBox(height: 14.0),
170 - AddressTextField(
171 - controller: _addressController,
172 - options: [AddressTextFieldOption.qrCode],
173 - validator: (value) {
174 - addressBookStore.validateAddress(value,
175 - cryptoCurrency: _selectectCrypto);
176 - return addressBookStore.errorMessage;
177 - },
178 - )
179 - ],
180 - ),
181 - ),
182 - bottomSection: Row(
183 - children: <Widget>[
184 - Expanded(
185 - child: PrimaryButton(
186 - onPressed: () {
187 - setState(() {
188 - _selectectCrypto = CryptoCurrency.xmr;
189 - _contactNameController.text = '';
190 - _currencyTypeController.text =
191 - _selectectCrypto.toString();
192 - _addressController.text = '';
193 - });
194 - },
195 - text: S.of(context).reset,
196 - color:
197 - Theme.of(context).accentTextTheme.button.backgroundColor,
198 - textColor:
199 - Theme.of(context).primaryTextTheme.button.color),
138 + Padding(
139 + padding: EdgeInsets.only(top: 20),
140 + child: AddressTextField(
141 + controller: _addressController,
142 + options: [AddressTextFieldOption.qrCode],
143 + validator: (value) {
144 + addressBookStore.validateAddress(value,
145 + cryptoCurrency: _selectectCrypto);
146 + return addressBookStore.errorMessage;
147 + },
148 + ),
149 + )
150 + ],
151 ),
201 - SizedBox(width: 20),
202 - Expanded(
152 + ),
153 + bottomSectionPadding: EdgeInsets.only(
154 + left: 24,
155 + right: 24,
156 + bottom: 24
157 + ),
158 + bottomSection: Row(
159 + children: <Widget>[
160 + Expanded(
161 child: PrimaryButton(
204 - onPressed: () async {
205 - if (!_formKey.currentState.validate()) {
206 - return;
207 - }
208 -
209 - try {
210 - if (widget.contact == null) {
211 - final newContact = Contact(
212 - name: _contactNameController.text,
213 - address: _addressController.text,
214 - type: _selectectCrypto);
215 -
216 - await addressBookStore.add(contact: newContact);
217 - } else {
218 - widget.contact.name = _contactNameController.text;
219 - widget.contact.address = _addressController.text;
220 - widget.contact
221 - .updateCryptoCurrency(currency: _selectectCrypto);
162 + onPressed: () {
163 + setState(() {
164 + _selectectCrypto = null;
165 + _contactNameController.text = '';
166 + _currencyTypeController.text = '';
167 + _addressController.text = '';
168 + });
169 + },
170 + text: S.of(context).reset,
171 + color: Colors.red,
172 + textColor: Colors.white
173 + ),
174 + ),
175 + SizedBox(width: 20),
176 + Expanded(
177 + child: Observer(
178 + builder: (_) => PrimaryButton(
179 + onPressed: () async {
180 + if (!_formKey.currentState.validate()) {
181 + return;
182 + }
183
223 - await addressBookStore.update(
224 - contact: widget.contact);
184 + try {
185 + if (widget.contact == null) {
186 + final newContact = Contact(
187 + name: _contactNameController.text,
188 + address: _addressController.text,
189 + type: _selectectCrypto);
190 +
191 + await addressBookStore.add(contact: newContact);
192 + } else {
193 + widget.contact.name = _contactNameController.text;
194 + widget.contact.address = _addressController.text;
195 + widget.contact
196 + .updateCryptoCurrency(currency: _selectectCrypto);
197 +
198 + await addressBookStore.update(
199 + contact: widget.contact);
200 + }
201 + Navigator.pop(context);
202 + } catch (e) {
203 + await showDialog<void>(
204 + context: context,
205 + builder: (BuildContext context) {
206 + return AlertWithOneAction(
207 + alertTitle: S.current.contact,
208 + alertContent: e.toString(),
209 + buttonText: S.of(context).ok,
210 + buttonAction: () => Navigator.of(context).pop()
211 + );
212 + });
213 }
226 - Navigator.pop(context);
227 - } catch (e) {
228 - await showDialog<void>(
229 - context: context,
230 - builder: (BuildContext context) {
231 - return AlertDialog(
232 - title: Text(
233 - e.toString(),
234 - textAlign: TextAlign.center,
235 - ),
236 - actions: <Widget>[
237 - FlatButton(
238 - onPressed: () =>
239 - Navigator.of(context).pop(),
240 - child: Text(S.of(context).ok))
241 - ],
242 - );
243 - });
244 - }
245 - },
246 - text: S.of(context).save,
247 - color: Theme.of(context)
248 - .primaryTextTheme
249 - .button
250 - .backgroundColor,
251 - textColor: Theme.of(context)
252 - .primaryTextTheme
253 - .button
254 - .color))
255 - ],
256 - ));
214 + },
215 + text: S.of(context).save,
216 + color: Colors.green,
217 + textColor: Colors.white,
218 + isDisabled: addressBookStore.isDisabledStatus,
219 + )
220 + )
221 + )
222 + ],
223 + )),
224 + );
225 + }
226 +
227 + void _presentPicker(BuildContext context) {
228 + showDialog<void>(
229 + builder: (_) => CurrencyPicker(
230 + selectedAtIndex: currencies.indexOf(_selectectCrypto),
231 + items: currencies,
232 + title: S.of(context).please_select,
233 + onItemSelected: (CryptoCurrency item) {
234 + _selectectCrypto = item;
235 + _currencyTypeController.text = _selectectCrypto.toString();
236 + }
237 + ),
238 + context: context);
239 }
240 }
lib/src/stores/address_book/address_book_store.dart
+9
@@ -12,11 +12,15 @@ class AddressBookStore = AddressBookStoreBase with _$AddressBookStore;
12 abstract class AddressBookStoreBase with Store {
13 AddressBookStoreBase({@required this.contacts}) {
14 updateContactList();
15 + isDisabledStatus = true;
16 }
17
18 @observable
19 List<Contact> contactList;
20
21 + @observable
22 + bool isDisabledStatus;
23 +
24 @observable
25 bool isValid;
26
@@ -37,6 +41,11 @@ abstract class AddressBookStoreBase with Store {
41 @action
42 Future delete({Contact contact}) async => await contact.delete();
43
44 + @action
45 + void setDisabledStatus(bool isDisabled) {
46 + isDisabledStatus = isDisabled;
47 + }
48 +
49 void validateContactName(String value) {
50 const pattern = '''^[^`,'"]{1,32}\$''';
51 final regExp = RegExp(pattern);
lib/src/widgets/base_text_form_field.dart
+3
@@ -17,6 +17,7 @@ class BaseTextFormField extends StatelessWidget {
17 this.borderColor = PaletteDark.menuList,
18 this.prefix,
19 this.suffix,
20 + this.suffixIcon,
21 this.enabled = true,
22 this.validator
23 });
@@ -34,6 +35,7 @@ class BaseTextFormField extends StatelessWidget {
35 final Color borderColor;
36 final Widget prefix;
37 final Widget suffix;
38 + final Widget suffixIcon;
39 final bool enabled;
40 final FormFieldValidator<String> validator;
41
@@ -55,6 +57,7 @@ class BaseTextFormField extends StatelessWidget {
57 decoration: InputDecoration(
58 prefix: prefix,
59 suffix: suffix,
60 + suffixIcon: suffixIcon,
61 hintStyle: TextStyle(
62 color: hintColor,
63 fontSize: 16
lib/src/widgets/seed_widget.dart
+54 -46
@@ -13,7 +13,6 @@ import 'package:cake_wallet/src/domain/monero/mnemonics/russian.dart';
13 import 'package:cake_wallet/src/domain/monero/mnemonics/spanish.dart';
14 import 'package:cake_wallet/src/domain/common/mnemotic_item.dart';
15 import 'package:cake_wallet/generated/i18n.dart';
16 -import 'package:cake_wallet/src/widgets/top_panel.dart';
16
17 class SeedWidget extends StatefulWidget {
18 SeedWidget({Key key, this.onMnemoticChange, this.onFinish, this.seedLanguage}) : super(key: key) {
@@ -237,52 +236,61 @@ class SeedWidgetState extends State<SeedWidget> {
236 Flexible(
237 fit: FlexFit.tight,
238 flex: 1,
240 - child: TopPanel(
241 - color: PaletteDark.menuList,
242 - widget: SingleChildScrollView(
243 - child: Column(
244 - mainAxisAlignment: MainAxisAlignment.start,
245 - crossAxisAlignment: CrossAxisAlignment.start,
246 - children: <Widget>[
247 - Text(
248 - S.of(context).restore_active_seed,
249 - style: TextStyle(
250 - fontSize: 14,
251 - color: PaletteDark.walletCardText
252 - ),
253 - ),
254 - Padding(
255 - padding: EdgeInsets.only(top: 5),
256 - child: Wrap(
257 - children: items.map((item) {
258 - final isValid = item.isCorrect();
259 - final isSelected = selectedItem == item;
260 -
261 - return InkWell(
262 - onTap: () => onMnemoticTap(item),
263 - child: Container(
264 - decoration: BoxDecoration(
265 - color: isValid ? Colors.transparent : Palette.red),
266 - margin: EdgeInsets.only(right: 7, bottom: 8),
267 - child: Text(
268 - item.toString(),
269 - style: TextStyle(
270 - color:
271 - isValid ? Colors.white : Palette.lightGrey,
272 - fontSize: 16,
273 - fontWeight:
274 - isSelected ? FontWeight.w900 : FontWeight.w400,
275 - decoration: isSelected
276 - ? TextDecoration.underline
277 - : TextDecoration.none),
278 - )),
279 - );
280 - }).toList(),)
281 - )
282 - ],
239 + child: Container(
240 + width: double.infinity,
241 + height: double.infinity,
242 + padding: EdgeInsets.all(24),
243 + decoration: BoxDecoration(
244 + borderRadius: BorderRadius.only(
245 + bottomLeft: Radius.circular(24),
246 + bottomRight: Radius.circular(24)
247 ),
284 - )
285 - )
248 + color: PaletteDark.menuList
249 + ),
250 + child: SingleChildScrollView(
251 + child: Column(
252 + mainAxisAlignment: MainAxisAlignment.start,
253 + crossAxisAlignment: CrossAxisAlignment.start,
254 + children: <Widget>[
255 + Text(
256 + S.of(context).restore_active_seed,
257 + style: TextStyle(
258 + fontSize: 14,
259 + color: PaletteDark.walletCardText
260 + ),
261 + ),
262 + Padding(
263 + padding: EdgeInsets.only(top: 5),
264 + child: Wrap(
265 + children: items.map((item) {
266 + final isValid = item.isCorrect();
267 + final isSelected = selectedItem == item;
268 +
269 + return InkWell(
270 + onTap: () => onMnemoticTap(item),
271 + child: Container(
272 + decoration: BoxDecoration(
273 + color: isValid ? Colors.transparent : Palette.red),
274 + margin: EdgeInsets.only(right: 7, bottom: 8),
275 + child: Text(
276 + item.toString(),
277 + style: TextStyle(
278 + color:
279 + isValid ? Colors.white : Palette.lightGrey,
280 + fontSize: 16,
281 + fontWeight:
282 + isSelected ? FontWeight.w900 : FontWeight.w400,
283 + decoration: isSelected
284 + ? TextDecoration.underline
285 + : TextDecoration.none),
286 + )),
287 + );
288 + }).toList(),)
289 + )
290 + ],
291 + ),
292 + ),
293 + ),
294 ),
295 Flexible(
296 fit: FlexFit.tight,