CAKE-359 | fixed yat_record.dart for multiple addresses in yat; fixed ParsedAddress class; applied ParsedAddress in the parse_address_from_domain_alert.dart; added parsedAddress and renamed applyOpenaliasOrUnstoppableDomains() to fetchParsedAddress() in the output.dart; fixed send_page.dart, send_card.dart and exchange_page.dart; added choose_yat_address_alert.dart to the app; added current wallet address to request parameters in the yat_alert.dart

OleksandrSobol committed Sep 20, 2021 at 17:56 UTC 8955a10ca804895b6392c15a3431032255169eda
13 files changed +253 -66
lib/entities/parse_address_from_domain.dart
+12 -10
@@ -24,18 +24,20 @@ Future<ParsedAddress> parseAddressFromDomain(
24
25 if (domainParts.length <= 1 || domainParts.first.isEmpty || name.isEmpty) {
26 try {
27 - final address = await fetchYatAddress(domain, ticker);
27 + final addresses = await fetchYatAddress(domain, ticker);
28
29 - if (address?.isEmpty ?? true) {
30 - return ParsedAddress(address: domain);
29 + if (addresses?.isEmpty ?? true) {
30 + return ParsedAddress(
31 + addresses: [domain],
32 + parseFrom: ParseFrom.yatRecord);
33 }
34
35 return ParsedAddress(
34 - address: address,
36 + addresses: addresses,
37 name: domain,
38 parseFrom: ParseFrom.yatRecord);
39 } catch (e) {
38 - return ParsedAddress(address: domain);
40 + return ParsedAddress(addresses: [domain]);
41 }
42 }
43
@@ -44,11 +46,11 @@ Future<ParsedAddress> parseAddressFromDomain(
46 await fetchUnstoppableDomainAddress(domain, ticker);
47
48 if (address?.isEmpty ?? true) {
47 - return ParsedAddress(address: domain);
49 + return ParsedAddress(addresses: [domain]);
50 }
51
52 return ParsedAddress(
51 - address: address,
53 + addresses: [address],
54 name: domain,
55 parseFrom: ParseFrom.unstoppableDomains);
56 }
@@ -56,16 +58,16 @@ Future<ParsedAddress> parseAddressFromDomain(
58 final record = await OpenaliasRecord.fetchAddressAndName(formattedName);
59
60 if (record == null || record.address.contains(formattedName)) {
59 - return ParsedAddress(address: domain);
61 + return ParsedAddress(addresses: [domain]);
62 }
63
64 return ParsedAddress(
63 - address: record.address,
65 + addresses: [record.address],
66 name: record.name,
67 parseFrom: ParseFrom.openAlias);
68 } catch (e) {
69 print(e.toString());
70 }
71
70 - return ParsedAddress(address: domain);
72 + return ParsedAddress(addresses: [domain]);
73 }
\ No newline at end of file
lib/entities/parsed_address.dart
+2 -2
@@ -2,11 +2,11 @@ enum ParseFrom {unstoppableDomains, openAlias, yatRecord, notParsed}
2
3 class ParsedAddress {
4 ParsedAddress({
5 - this.address = '',
5 + this.addresses,
6 this.name = '',
7 this.parseFrom = ParseFrom.notParsed});
8
9 - final String address;
9 + final List<String> addresses;
10 final String name;
11 final ParseFrom parseFrom;
12 }
\ No newline at end of file
lib/src/screens/exchange/exchange_page.dart
+9 -11
@@ -232,7 +232,7 @@ class ExchangePage extends BasePage {
232 final ticker = exchangeViewModel
233 .depositCurrency.title.toLowerCase();
234 exchangeViewModel.depositAddress =
235 - await applyOpenaliasOrUnstoppableDomains(
235 + await fetchParsedAddress(
236 context, domain, ticker);
237 },
238 ),
@@ -288,7 +288,7 @@ class ExchangePage extends BasePage {
288 final ticker = exchangeViewModel
289 .receiveCurrency.title.toLowerCase();
290 exchangeViewModel.receiveAddress =
291 - await applyOpenaliasOrUnstoppableDomains(
291 + await fetchParsedAddress(
292 context, domain, ticker);
293 },
294 )),
@@ -518,12 +518,12 @@ class ExchangePage extends BasePage {
518 var domain = template.depositAddress;
519 var ticker = template.depositCurrency.toLowerCase();
520 exchangeViewModel.depositAddress =
521 - await applyOpenaliasOrUnstoppableDomains(context, domain, ticker);
521 + await fetchParsedAddress(context, domain, ticker);
522
523 domain = template.receiveAddress;
524 ticker = template.receiveCurrency.toLowerCase();
525 exchangeViewModel.receiveAddress =
526 - await applyOpenaliasOrUnstoppableDomains(context, domain, ticker);
526 + await fetchParsedAddress(context, domain, ticker);
527 }
528
529 void _setReactions(
@@ -696,7 +696,7 @@ class ExchangePage extends BasePage {
696 final domain = depositAddressController.text;
697 final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
698 exchangeViewModel.depositAddress =
699 - await applyOpenaliasOrUnstoppableDomains(context, domain, ticker);
699 + await fetchParsedAddress(context, domain, ticker);
700 }
701 });
702
@@ -706,7 +706,7 @@ class ExchangePage extends BasePage {
706 final domain = receiveAddressController.text;
707 final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
708 exchangeViewModel.receiveAddress =
709 - await applyOpenaliasOrUnstoppableDomains(context, domain, ticker);
709 + await fetchParsedAddress(context, domain, ticker);
710 }
711 });
712
@@ -782,12 +782,10 @@ class ExchangePage extends BasePage {
782 }
783 }
784
785 - Future<String> applyOpenaliasOrUnstoppableDomains(
785 + Future<String> fetchParsedAddress(
786 BuildContext context, String domain, String ticker) async {
787 final parsedAddress = await parseAddressFromDomain(domain, ticker);
788 -
789 - showAddressAlert(context, parsedAddress);
790 -
791 - return parsedAddress.address;
788 + final address = await defineAddress(context, parsedAddress);
789 + return address;
790 }
791 }
lib/src/screens/send/send_page.dart
+2 -3
@@ -215,9 +215,8 @@ class SendPage extends BasePage {
215 output.address =
216 template.address;
217 output.setCryptoAmount(template.amount);
218 - final parsedAddress = await output
219 - .applyOpenaliasOrUnstoppableDomains();
220 - showAddressAlert(context, parsedAddress);
218 + output.resetParsedAddress();
219 + await output.fetchParsedAddress(context);
220 },
221 onRemove: () {
222 showPopUp<void>(
lib/src/screens/send/widgets/choose_yat_address_alert.dart new
+72
@@ -0,0 +1,72 @@
1 +import 'package:flutter/material.dart';
2 +import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
3 +
4 +class ChooseYatAddressAlert extends BaseAlertDialog {
5 + ChooseYatAddressAlert({
6 + @required this.alertTitle,
7 + @required this.alertContent,
8 + @required this.addresses,
9 + });
10 +
11 + final String alertTitle;
12 + final String alertContent;
13 + final List<String> addresses;
14 +
15 + @override
16 + String get titleText => alertTitle;
17 +
18 + @override
19 + String get contentText => alertContent;
20 +
21 + @override
22 + bool get barrierDismissible => false;
23 +
24 + @override
25 + Widget actionButtons(BuildContext context) {
26 + return Container(
27 + width: 300,
28 + height: 105,
29 + color: Theme.of(context).accentTextTheme.body1.backgroundColor,
30 + child: ListView.separated(
31 + padding: EdgeInsets.all(0),
32 + itemCount: addresses.length,
33 + separatorBuilder: (_, __) => Container(
34 + height: 1,
35 + color: Theme.of(context).dividerColor,
36 + ),
37 + itemBuilder: (context, index) {
38 + final address = addresses[index];
39 +
40 + return GestureDetector(
41 + onTap: () => Navigator.of(context).pop<String>(address),
42 + child: Container(
43 + width: 300,
44 + height: 52,
45 + padding: EdgeInsets.only(left: 24, right: 24),
46 + child: Row(
47 + mainAxisAlignment: MainAxisAlignment.center,
48 + crossAxisAlignment: CrossAxisAlignment.center,
49 + children: [
50 + Expanded(
51 + child: Text(
52 + address,
53 + textAlign: TextAlign.center,
54 + maxLines: 1,
55 + overflow: TextOverflow.ellipsis,
56 + style: TextStyle(
57 + fontSize: 15,
58 + fontWeight: FontWeight.w600,
59 + fontFamily: 'Lato',
60 + color: Theme.of(context).primaryTextTheme.title.color,
61 + decoration: TextDecoration.none,
62 + ),
63 + )
64 + )
65 + ],
66 + )
67 + ),
68 + );
69 + })
70 + );
71 + }
72 +}
\ No newline at end of file
lib/src/screens/send/widgets/confirm_sending_alert.dart
+51 -14
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/entities/parsed_address.dart';
2 import 'package:cake_wallet/palette.dart';
3 import 'package:cake_wallet/view_model/send/output.dart';
4 import 'package:flutter/material.dart';
@@ -276,15 +277,32 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
277 final _address = item.address;
278 final _amount =
279 item.cryptoAmount.replaceAll(',', '.');
280 + final isParsedAddress =
281 + item.parsedAddress.parseFrom !=
282 + ParseFrom.notParsed;
283
284 return Column(
285 children: [
286 + if (isParsedAddress) Padding(
287 + padding: EdgeInsets.only(top: 8),
288 + child: Text(
289 + item.parsedAddress.name,
290 + textAlign: TextAlign.center,
291 + style: TextStyle(
292 + fontSize: 14,
293 + fontWeight: FontWeight.w600,
294 + fontFamily: 'Lato',
295 + color: PaletteDark.pigeonBlue,
296 + decoration: TextDecoration.none,
297 + ),
298 + )
299 + ),
300 Padding(
301 padding: EdgeInsets.only(top: 8),
302 child: Text(
303 _address,
304 style: TextStyle(
287 - fontSize: 12,
305 + fontSize: 10,
306 fontWeight: FontWeight.w600,
307 fontFamily: 'Lato',
308 color: PaletteDark.pigeonBlue,
@@ -301,7 +319,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
319 Text(
320 _amount,
321 style: TextStyle(
304 - fontSize: 12,
322 + fontSize: 10,
323 fontWeight: FontWeight.w600,
324 fontFamily: 'Lato',
325 color: PaletteDark.pigeonBlue,
@@ -314,18 +332,37 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
332 ],
333 );
334 })
317 - : Padding(
318 - padding: EdgeInsets.only(top: 8),
319 - child: Text(
320 - outputs.first.address,
321 - style: TextStyle(
322 - fontSize: 12,
323 - fontWeight: FontWeight.w600,
324 - fontFamily: 'Lato',
325 - color: PaletteDark.pigeonBlue,
326 - decoration: TextDecoration.none,
327 - ),
328 - ),
335 + : Column(
336 + children: [
337 + if (outputs.first.parsedAddress.parseFrom !=
338 + ParseFrom.notParsed) Padding(
339 + padding: EdgeInsets.only(top: 8),
340 + child: Text(
341 + outputs.first.parsedAddress.name,
342 + textAlign: TextAlign.center,
343 + style: TextStyle(
344 + fontSize: 14,
345 + fontWeight: FontWeight.w600,
346 + fontFamily: 'Lato',
347 + color: PaletteDark.pigeonBlue,
348 + decoration: TextDecoration.none,
349 + ),
350 + )
351 + ),
352 + Padding(
353 + padding: EdgeInsets.only(top: 8),
354 + child: Text(
355 + outputs.first.address,
356 + style: TextStyle(
357 + fontSize: 10,
358 + fontWeight: FontWeight.w600,
359 + fontFamily: 'Lato',
360 + color: PaletteDark.pigeonBlue,
361 + decoration: TextDecoration.none,
362 + ),
363 + )
364 + ),
365 + ]
366 )
367 ],
368 ),
lib/src/screens/send/widgets/parse_address_from_domain_alert.dart
+44 -3
@@ -4,25 +4,64 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
4 import 'package:flutter/material.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6
7 -void showAddressAlert(BuildContext context, ParsedAddress parsedAddress) async {
7 +import 'choose_yat_address_alert.dart';
8 +
9 +Future<String> defineAddress(
10 + BuildContext context,
11 + ParsedAddress parsedAddress) async {
12 var title = '';
13 var content = '';
14 + var address = '';
15
16 switch (parsedAddress.parseFrom) {
17 case ParseFrom.unstoppableDomains:
18 title = S.of(context).address_detected;
19 content = S.of(context).address_from_domain(parsedAddress.name);
20 + address = parsedAddress.addresses.first;
21 break;
22 case ParseFrom.openAlias:
23 title = S.of(context).openalias_alert_title;
24 content = S.of(context).openalias_alert_content(parsedAddress.name);
25 + address = parsedAddress.addresses.first;
26 break;
27 case ParseFrom.yatRecord:
28 + if (parsedAddress.name.isEmpty) {
29 + title = 'Yat error';
30 + content = 'No addresses linked with this Yat. Try another Yat';
31 + address = parsedAddress.addresses.first;
32 + break;
33 + }
34 +
35 title = S.of(context).address_detected;
36 content = S.of(context).address_from_yat(parsedAddress.name);
23 - break;
37 +
38 + if (parsedAddress.addresses.length == 1) {
39 + address = parsedAddress.addresses.first;
40 + break;
41 + }
42 +
43 + content += '\nPlease choose the address:';
44 +
45 + address = await showPopUp<String>(
46 + context: context,
47 + builder: (BuildContext context) {
48 +
49 + return WillPopScope(
50 + child: ChooseYatAddressAlert(
51 + alertTitle: title,
52 + alertContent: content,
53 + addresses: parsedAddress.addresses),
54 + onWillPop: () async => false);
55 + });
56 +
57 + if (address?.isEmpty ?? true) {
58 + return parsedAddress.name;
59 + }
60 +
61 + return address;
62 case ParseFrom.notParsed:
25 - return;
63 + address = parsedAddress.addresses.first;
64 + return address;
65 }
66
67 await showPopUp<void>(
@@ -35,4 +74,6 @@ void showAddressAlert(BuildContext context, ParsedAddress parsedAddress) async {
74 buttonText: S.of(context).ok,
75 buttonAction: () => Navigator.of(context).pop());
76 });
77 +
78 + return address;
79 }
\ No newline at end of file
lib/src/screens/send/widgets/send_card.dart
+6 -5
@@ -147,10 +147,11 @@ class SendCardState extends State<SendCard>
147 .headline
148 .decorationColor),
149 onPushPasteButton: (context) async {
150 - final parsedAddress =
151 - await output.applyOpenaliasOrUnstoppableDomains();
152 - showAddressAlert(context, parsedAddress);
150 + output.resetParsedAddress();
151 + await output.fetchParsedAddress(context);
152 },
153 + onPushAddressBookButton: (context) =>
154 + output.resetParsedAddress(),
155 validator: sendViewModel.addressValidator,
156 ),
157 Observer(
@@ -531,8 +532,8 @@ class SendCardState extends State<SendCard>
532
533 addressFocusNode.addListener(() async {
534 if (!addressFocusNode.hasFocus && addressController.text.isNotEmpty) {
534 - final parsedAddress = await output.applyOpenaliasOrUnstoppableDomains();
535 - showAddressAlert(context, parsedAddress);
535 + output.resetParsedAddress();
536 + await output.fetchParsedAddress(context);
537 }
538 });
539
lib/src/screens/yat/yat_alert.dart
+30 -3
@@ -1,3 +1,5 @@
1 +import 'package:cake_wallet/core/wallet_base.dart';
2 +import 'package:cake_wallet/entities/wallet_type.dart';
3 import 'package:cake_wallet/src/screens/yat/widgets/yat_bar.dart';
4 import 'package:cake_wallet/src/widgets/primary_button.dart';
5 import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
@@ -8,16 +10,20 @@ import 'package:url_launcher/url_launcher.dart';
10 import 'package:cake_wallet/generated/i18n.dart';
11
12 class YatAlert extends StatelessWidget {
11 - YatAlert({this.isYatDevMode = false})
12 - : baseUrl = isYatDevMode ? _baseDevUrl : _baseReleaseUrl;
13 + YatAlert({@required this.wallet, this.isYatDevMode = false})
14 + : baseUrl = isYatDevMode ? _baseDevUrl : _baseReleaseUrl,
15 + address = wallet.walletAddresses.address;
16
17 + final WalletBase wallet;
18 final bool isYatDevMode;
19 + final String address;
20 final String baseUrl;
21 static const aspectRatioImage = 1.133;
22 static const _baseDevUrl = 'https://yat.fyi';
23 static const _baseReleaseUrl = 'https://y.at';
24 static const _signInSuffix = '/partner/CW/link-email';
25 static const _createSuffix = '/create';
26 + static const _queryParameter = '?addresses=';
27 final image = Image.asset('assets/images/yat_crypto.png');
28
29 @override
@@ -107,7 +113,8 @@ class YatAlert extends StatelessWidget {
113 .arrow_up_right_square,
114 mainAxisAlignment: MainAxisAlignment.end,
115 onPressed: () {
110 - final url = baseUrl + _signInSuffix;
116 + final url = baseUrl + _signInSuffix + _queryParameter +
117 + _defineTag() + '%3D' + address;
118 launch(url);
119 })
120 )
@@ -116,4 +123,24 @@ class YatAlert extends StatelessWidget {
123 )
124 );
125 }
126 +
127 + String _defineTag() {
128 + String tag;
129 + switch (wallet.type) {
130 + case WalletType.monero:
131 + tag = address.startsWith('4')
132 + ? '0x1001'
133 + : '0x1002';
134 + break;
135 + case WalletType.bitcoin:
136 + tag = '0x1003';
137 + break;
138 + case WalletType.litecoin:
139 + tag = '0x3fff';
140 + break;
141 + default:
142 + tag = '0x3fff';
143 + }
144 + return tag;
145 + }
146 }
\ No newline at end of file
lib/src/widgets/address_text_field.dart
+4 -1
@@ -25,7 +25,8 @@ class AddressTextField extends StatelessWidget {
25 this.textStyle,
26 this.hintStyle,
27 this.validator,
28 - this.onPushPasteButton});
28 + this.onPushPasteButton,
29 + this.onPushAddressBookButton});
30
31 static const prefixIconWidth = 34.0;
32 static const prefixIconHeight = 34.0;
@@ -45,6 +46,7 @@ class AddressTextField extends StatelessWidget {
46 final TextStyle hintStyle;
47 final FocusNode focusNode;
48 final Function(BuildContext context) onPushPasteButton;
49 + final Function(BuildContext context) onPushAddressBookButton;
50
51 @override
52 Widget build(BuildContext context) {
@@ -216,6 +218,7 @@ class AddressTextField extends StatelessWidget {
218
219 if (contact is ContactBase && contact.address != null) {
220 controller.text = contact.address;
221 + onPushAddressBookButton?.call(context);
222 }
223 }
224
lib/view_model/send/output.dart
+11 -6
@@ -4,6 +4,7 @@ import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
4 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
5 import 'package:cake_wallet/entities/parsed_address.dart';
6 import 'package:cake_wallet/monero/monero_amount_format.dart';
7 +import 'package:cake_wallet/src/screens/send/widgets/parse_address_from_domain_alert.dart';
8 import 'package:flutter/material.dart';
9 import 'package:intl/intl.dart';
10 import 'package:mobx/mobx.dart';
@@ -46,6 +47,8 @@ abstract class OutputBase with Store {
47 @observable
48 bool sendAll;
49
50 + ParsedAddress parsedAddress;
51 +
52 @computed
53 int get formattedCryptoAmount {
54 int amount = 0;
@@ -127,6 +130,11 @@ abstract class OutputBase with Store {
130 fiatAmount = '';
131 address = '';
132 note = '';
133 + resetParsedAddress();
134 + }
135 +
136 + void resetParsedAddress() {
137 + parsedAddress = ParsedAddress(addresses: []);
138 }
139
140 @action
@@ -194,13 +202,10 @@ abstract class OutputBase with Store {
202 _cryptoNumberFormat.maximumFractionDigits = maximumFractionDigits;
203 }
204
197 - Future<ParsedAddress> applyOpenaliasOrUnstoppableDomains() async {
205 + Future<void> fetchParsedAddress(BuildContext context) async {
206 final domain = address;
207 final ticker = _wallet.currency.title.toLowerCase();
200 - final parsedAddress = await parseAddressFromDomain(domain, ticker);
201 -
202 - address = parsedAddress.address;
203 -
204 - return parsedAddress;
208 + parsedAddress = await parseAddressFromDomain(domain, ticker);
209 + address = await defineAddress(context, parsedAddress);
210 }
211 }
\ No newline at end of file
lib/view_model/settings/settings_view_model.dart
+1 -1
@@ -162,7 +162,7 @@ abstract class SettingsViewModelBase with Store {
162 await showPopUp<void>(
163 context: context,
164 builder: (BuildContext context) {
165 - return YatAlert(isYatDevMode: true);
165 + return YatAlert(wallet: wallet, isYatDevMode: true);
166 });
167 },
168 ),
lib/yat/yat_record.dart
+9 -7
@@ -2,9 +2,8 @@ import 'dart:convert';
2 import 'package:cake_wallet/yat/yat_exception.dart';
3 import 'package:http/http.dart';
4
5 -Future<String> fetchYatAddress(String emojiId, String ticker) async {
5 +Future<List<String>> fetchYatAddress(String emojiId, String ticker) async {
6 const _requestURL = 'https://a.y.at/emoji_id/';
7 -
7 final url = _requestURL + emojiId + '/' + ticker.toUpperCase();
8 final response = await get(url);
9
@@ -16,14 +15,17 @@ Future<String> fetchYatAddress(String emojiId, String ticker) async {
15 final result = responseJSON['result'] as List<dynamic>;
16
17 if (result?.isEmpty ?? true) {
19 - return '';
18 + return [];
19 }
20
22 - final yatAddress = result.first['data'] as String;
21 + final List<String> addresses = [];
22
24 - if (yatAddress?.isEmpty ?? true) {
25 - return '';
23 + for (var elem in result) {
24 + final yatAddress = elem['data'] as String;
25 + if (yatAddress?.isNotEmpty ?? false) {
26 + addresses.add(yatAddress);
27 + }
28 }
29
28 - return yatAddress;
30 + return addresses;
31 }
\ No newline at end of file