dev
dart 251 lines 9.16 KB
Raw
1 import 'package:cake_wallet/core/address_resolver/parsed_address.dart';
2 import 'package:cake_wallet/core/address_validator.dart';
3 import 'package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart';
4 import 'package:cake_wallet/utils/show_pop_up.dart';
5 import 'package:cw_core/currency.dart';
6 import 'package:flutter/material.dart';
7 import 'package:flutter_mobx/flutter_mobx.dart';
8 import 'package:mobx/mobx.dart';
9 import 'package:cake_wallet/generated/i18n.dart';
10 import 'package:cake_wallet/core/contact_name_validator.dart';
11 import 'package:cake_wallet/core/execution_state.dart';
12 import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
13 import 'package:cw_core/crypto_currency.dart';
14 import 'package:cake_wallet/src/screens/base_page.dart';
15 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
16 import 'package:cake_wallet/src/widgets/primary_button.dart';
17 import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
18 import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
19 import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
20
21 class ContactPage extends BasePage {
22 ContactPage(this.contactViewModel)
23 : _formKey = GlobalKey<FormState>(),
24 _nameController = TextEditingController(),
25 _addressController = TextEditingController(),
26 _currencyTypeController = TextEditingController(),
27 addressFocusNode = FocusNode() {
28 _nameController.text = contactViewModel.name;
29 _addressController.text = contactViewModel.address;
30 _nameController.addListener(() => contactViewModel.name = _nameController.text);
31 _addressController.addListener(() {
32 final address = _addressController.text;
33
34 if (address != contactViewModel.address) {
35 contactViewModel.displayName = '';
36 }
37
38 contactViewModel.address = address;
39 });
40
41 autorun((_) => _currencyTypeController.text =
42 "${contactViewModel.currency?.toString() ?? ""} ${contactViewModel.currency?.tag != null ? "(${contactViewModel.currency?.tag})" : ""}");
43 }
44
45 @override
46 String get title => S.current.contact;
47
48 FocusNode addressFocusNode;
49
50 final ContactViewModel contactViewModel;
51 final GlobalKey<FormState> _formKey;
52 final TextEditingController _nameController;
53 final TextEditingController _currencyTypeController;
54 final TextEditingController _addressController;
55 bool _isEffectsApplied = false;
56
57 @override
58 Widget body(BuildContext context) {
59 final downArrow = Image.asset(
60 'assets/images/arrow_bottom_purple_icon.png',
61 color: Theme.of(context).colorScheme.primary,
62 height: 8,
63 );
64
65 _setEffects(context);
66
67 return Observer(
68 builder: (_) => ScrollableWithBottomSection(
69 contentPadding: EdgeInsets.all(24),
70 content: Form(
71 key: _formKey,
72 child: Column(
73 mainAxisSize: MainAxisSize.min,
74 children: <Widget>[
75 BaseTextFormField(
76 controller: _nameController,
77 hintText: S.of(context).contact_name,
78 validator: ContactNameValidator(),
79 ),
80 Padding(
81 padding: EdgeInsets.only(top: 20),
82 child: Container(
83 child: InkWell(
84 onTap: () => _presentCurrencyPicker(context),
85 child: IgnorePointer(
86 child: BaseTextFormField(
87 controller: _currencyTypeController,
88 hintText: S.of(context).settings_currency,
89 suffixIcon: Row(
90 mainAxisSize: MainAxisSize.min,
91 mainAxisAlignment: MainAxisAlignment.end,
92 children: <Widget>[
93 downArrow,
94 SizedBox(width: 16),
95 ],
96 ),
97 ),
98 ),
99 ),
100 ),
101 ),
102 if (contactViewModel.currency != null)
103 Padding(
104 padding: EdgeInsets.only(top: 20),
105 child: Observer(
106 builder: (_) {
107 return NewSendAddressInput(
108 focusNode: addressFocusNode,
109 displayName: contactViewModel.displayName,
110 addressController: _addressController,
111 onEditingComplete: () {},
112 onPushPasteButton: (context) => _extractParsedAddress(context),
113 validator: AddressValidator(type: contactViewModel.currency!),
114 selectedCurrency: contactViewModel.currency!,
115 );
116 },
117 ),
118 )
119 ],
120 ),
121 ),
122 bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
123 bottomSection: Row(
124 children: <Widget>[
125 Expanded(
126 child: PrimaryButton(
127 onPressed: () {
128 contactViewModel.reset();
129 _nameController.text = '';
130 _addressController.text = '';
131 },
132 text: S.of(context).reset,
133 color: Theme.of(context).colorScheme.surfaceContainer,
134 textColor: Theme.of(context).colorScheme.onSecondaryContainer,
135 ),
136 ),
137 SizedBox(width: 20),
138 Expanded(
139 child: Observer(
140 builder: (_) => PrimaryButton(
141 onPressed: () async {
142 FocusScope.of(context).unfocus();
143 _extractParsedAddress(context);
144
145 if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
146 return;
147 }
148
149 await contactViewModel.save();
150 },
151 text: S.of(context).save,
152 color: Theme.of(context).colorScheme.primary,
153 textColor: Theme.of(context).colorScheme.onPrimary,
154 isDisabled: !contactViewModel.isReady,
155 ),
156 ),
157 )
158 ],
159 )),
160 );
161 }
162
163 Future<void> _extractParsedAddress(BuildContext context) async {
164 final parsedAddress = await contactViewModel.extractParsedAddress(context);
165 if (parsedAddress == null) return;
166
167 final confirmed = await showParsedAddressConfirmationAlert(context, parsedAddress);
168 if (!confirmed) return;
169
170 contactViewModel.applyParsedAddress(parsedAddress);
171 }
172
173 void _presentCurrencyPicker(BuildContext context) {
174 showPopUp<void>(
175 builder: (_) => CurrencyPicker(
176 selectedAtIndex: contactViewModel.currency != null
177 ? contactViewModel.currencies.indexOf(contactViewModel.currency!)
178 : -1,
179 items: contactViewModel.currencies,
180 title: S.of(context).please_select,
181 hintText: S.of(context).search_currency,
182 onItemSelected: (Currency item) => contactViewModel.currency = item as CryptoCurrency,
183 ),
184 context: context,
185 );
186 }
187
188 void _onContactSavingFailure(BuildContext context, String error) {
189 showPopUp<void>(
190 context: context,
191 builder: (BuildContext context) {
192 return AlertWithOneAction(
193 alertTitle: S.current.contact,
194 alertContent: error,
195 buttonText: S.of(context).ok,
196 buttonAction: () => Navigator.of(context).pop(),
197 );
198 });
199 }
200
201 void _onContactSavedSuccessfully(BuildContext context) => Navigator.of(context).pop();
202
203 void _setEffects(BuildContext context) {
204 if (_isEffectsApplied) {
205 return;
206 }
207
208 _isEffectsApplied = true;
209
210 reaction((_) => contactViewModel.state, (ExecutionState state) {
211 if (state is FailureState) {
212 _onContactSavingFailure(context, state.error);
213 }
214
215 if (state is ExecutedSuccessfullyState) {
216 _onContactSavedSuccessfully(context);
217 }
218 });
219
220 reaction((_) => contactViewModel.address, (val) {
221 if (val != _addressController.text) {
222 _addressController.text = val;
223 }
224 });
225 }
226
227 Future<bool> showParsedAddressConfirmationAlert(
228 BuildContext context,
229 ParsedAddress parsedAddress,
230 ) async {
231 final confirmed = await showPopUp<bool>(
232 context: context,
233 builder: (BuildContext context) {
234 return AlertWithOneAction(
235 alertTitle: S.of(context).address_detected,
236 headerTitleText: parsedAddress.profileName.isEmpty ? null : parsedAddress.profileName,
237 headerImageProfileUrl: parsedAddress.profileImageUrl.isEmpty
238 ? parsedAddress.addressSource.iconPath
239 : parsedAddress.profileImageUrl,
240 alertContent: S.of(context).extracted_address_content(
241 '${parsedAddress.handle} (${parsedAddress.addressSource.label})',
242 ),
243 buttonText: S.of(context).ok,
244 buttonAction: () => Navigator.of(context).pop(true),
245 );
246 },
247 );
248
249 return confirmed ?? false;
250 }
251 }