Cw 268 contact address validation (#905)

* feat: Proper Bitcoin address validation in exchange screen * fix: use custom validation in addition to RegEx to make sure all address cases are validated - for cases like P2SH addresses starting with a 3, which are not validated by bitcoin_flutter functions * feat: add bitcoin_flutter to root project * refactor: improve conditional isValid return chain * feat: enhance contact page address validation - only shows address box when a currency is selected, to prevent the selected value from being null - changes TextValidator() for the new AddressValidator() * fix: use -1 for initial selectedAtIndex, since there is no currency yet selected

Rafael Saes committed May 15, 2023 at 09:43 UTC 40f3ccbe42da8de3623b5913ef77f4e308eee5c7
1 file changed +87 -80
lib/src/screens/contact/contact_page.dart
+87 -80
@@ -1,13 +1,11 @@
1 -import 'package:cake_wallet/core/validator.dart';
1 +import 'package:cake_wallet/core/address_validator.dart';
2 import 'package:cake_wallet/palette.dart';
3 import 'package:cake_wallet/utils/show_pop_up.dart';
4 import 'package:cw_core/currency.dart';
5 import 'package:flutter/material.dart';
6 -import 'package:flutter/cupertino.dart';
6 import 'package:flutter_mobx/flutter_mobx.dart';
7 import 'package:mobx/mobx.dart';
8 import 'package:cake_wallet/generated/i18n.dart';
10 -import 'package:cake_wallet/core/address_validator.dart';
9 import 'package:cake_wallet/core/contact_name_validator.dart';
10 import 'package:cake_wallet/core/execution_state.dart';
11 import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
@@ -33,8 +31,8 @@ class ContactPage extends BasePage {
31 _addressController
32 .addListener(() => contactViewModel.address = _addressController.text);
33
36 - autorun((_) =>
37 - _currencyTypeController.text = contactViewModel.currency?.toString()??'');
34 + autorun((_) => _currencyTypeController.text =
35 + contactViewModel.currency?.toString() ?? '');
36 }
37
38 @override
@@ -61,96 +59,105 @@ class ContactPage extends BasePage {
59 }
60 });
61
64 - return ScrollableWithBottomSection(
65 - contentPadding: EdgeInsets.all(24),
66 - content: Form(
67 - key: _formKey,
68 - child: Column(
69 - mainAxisSize: MainAxisSize.min,
70 - children: <Widget>[
71 - BaseTextFormField(
72 - controller: _nameController,
73 - hintText: S.of(context).contact_name,
74 - validator: ContactNameValidator()),
75 - Padding(
76 - padding: EdgeInsets.only(top: 20),
77 - child: Container(
78 - child: InkWell(
79 - onTap: () => _presentCurrencyPicker(context),
80 - child: IgnorePointer(
81 - child: BaseTextFormField(
82 - controller: _currencyTypeController,
83 - hintText: S.of(context).settings_currency,
84 - suffixIcon: Row(
85 - mainAxisSize: MainAxisSize.min,
86 - mainAxisAlignment: MainAxisAlignment.end,
87 - children: <Widget>[downArrow],
88 - ),
89 - )),
62 + return Observer(
63 + builder: (_) => ScrollableWithBottomSection(
64 + contentPadding: EdgeInsets.all(24),
65 + content: Form(
66 + key: _formKey,
67 + child: Column(
68 + mainAxisSize: MainAxisSize.min,
69 + children: <Widget>[
70 + BaseTextFormField(
71 + controller: _nameController,
72 + hintText: S.of(context).contact_name,
73 + validator: ContactNameValidator()),
74 + Padding(
75 + padding: EdgeInsets.only(top: 20),
76 + child: Container(
77 + child: InkWell(
78 + onTap: () => _presentCurrencyPicker(context),
79 + child: IgnorePointer(
80 + child: BaseTextFormField(
81 + controller: _currencyTypeController,
82 + hintText: S.of(context).settings_currency,
83 + suffixIcon: Row(
84 + mainAxisSize: MainAxisSize.min,
85 + mainAxisAlignment: MainAxisAlignment.end,
86 + children: <Widget>[downArrow],
87 + ),
88 + )),
89 + ),
90 ),
91 ),
92 - ),
93 - Padding(
94 - padding: EdgeInsets.only(top: 20),
95 - child: Observer(
96 - builder: (_) => AddressTextField(
92 + if (contactViewModel.currency != null)
93 + Padding(
94 + padding: EdgeInsets.only(top: 20),
95 + child: AddressTextField(
96 controller: _addressController,
97 options: [
98 AddressTextFieldOption.paste,
99 AddressTextFieldOption.qrCode,
100 ],
102 - buttonColor: Theme.of(context).accentTextTheme!.headline3!.color!,
101 + buttonColor:
102 + Theme.of(context).accentTextTheme!.headline3!.color!,
103 iconColor: PaletteDark.gray,
104 - borderColor: Theme.of(context).primaryTextTheme!.headline6!.backgroundColor!,
105 - validator: TextValidator()
106 - // AddressValidator(
107 - // type: contactViewModel.currency),
108 - )),
109 - )
110 - ],
111 - ),
112 - ),
113 - bottomSectionPadding:
114 - EdgeInsets.only(left: 24, right: 24, bottom: 24),
115 - bottomSection: Row(
116 - children: <Widget>[
117 - Expanded(
118 - child: PrimaryButton(
119 - onPressed: () {
120 - contactViewModel.reset();
121 - _nameController.text = '';
122 - _addressController.text = '';
123 - },
124 - text: S.of(context).reset,
125 - color: Colors.orange,
126 - textColor: Colors.white),
104 + borderColor: Theme.of(context)
105 + .primaryTextTheme!
106 + .headline6!
107 + .backgroundColor!,
108 + validator:
109 + AddressValidator(type: contactViewModel.currency!),
110 + ),
111 + )
112 + ],
113 ),
128 - SizedBox(width: 20),
129 - Expanded(
130 - child: Observer(
131 - builder: (_) => PrimaryButton(
132 - onPressed: () async {
133 - if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
134 - return;
135 - }
114 + ),
115 + bottomSectionPadding:
116 + EdgeInsets.only(left: 24, right: 24, bottom: 24),
117 + bottomSection: Row(
118 + children: <Widget>[
119 + Expanded(
120 + child: PrimaryButton(
121 + onPressed: () {
122 + contactViewModel.reset();
123 + _nameController.text = '';
124 + _addressController.text = '';
125 + },
126 + text: S.of(context).reset,
127 + color: Colors.orange,
128 + textColor: Colors.white),
129 + ),
130 + SizedBox(width: 20),
131 + Expanded(
132 + child: Observer(
133 + builder: (_) => PrimaryButton(
134 + onPressed: () async {
135 + if (_formKey.currentState != null &&
136 + !_formKey.currentState!.validate()) {
137 + return;
138 + }
139
137 - await contactViewModel.save();
138 - },
139 - text: S.of(context).save,
140 - color: Theme.of(context).accentTextTheme!.bodyText1!.color!,
141 - textColor: Colors.white,
142 - isDisabled: !contactViewModel.isReady)))
143 - ],
144 - ));
140 + await contactViewModel.save();
141 + },
142 + text: S.of(context).save,
143 + color: Theme.of(context)
144 + .accentTextTheme!
145 + .bodyText1!
146 + .color!,
147 + textColor: Colors.white,
148 + isDisabled: !contactViewModel.isReady)))
149 + ],
150 + )),
151 + );
152 }
153
154 void _presentCurrencyPicker(BuildContext context) {
155 showPopUp<void>(
156 builder: (_) => CurrencyPicker(
150 - selectedAtIndex:
151 - contactViewModel.currency != null
152 - ? contactViewModel.currencies.indexOf(contactViewModel.currency!)
153 - : 0,
157 + selectedAtIndex: contactViewModel.currency != null
158 + ? contactViewModel.currencies
159 + .indexOf(contactViewModel.currency!)
160 + : -1,
161 items: contactViewModel.currencies,
162 title: S.of(context).please_select,
163 hintText: S.of(context).search_currency,