CAKE-356 | added "Manage Yats" item to the settings page; reworked primary icon button; added yat address to qr widget; created yat_alert.dart, yat_webview_page.dart, yat_store.dart, yat_view_model.dart, yat_exception.dart, yat_record.dart

OleksandrSobol committed Aug 20, 2021 at 16:26 UTC 92aece5e31fb5c4ba284ec9ee4ba4ee7755a2d49
18 files changed +400 -14
assets/images/yat_crypto.png
Binary files /dev/null and b/assets/images/yat_crypto.png differ
assets/images/yat_logo.png
Binary files /dev/null and b/assets/images/yat_logo.png differ
lib/di.dart
+18 -1
@@ -45,6 +45,7 @@ import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.da
45 import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
46 import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
47 import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
48 +import 'package:cake_wallet/src/screens/yat/yat_webview_page.dart';
49 import 'package:cake_wallet/store/dashboard/orders_store.dart';
50 import 'package:cake_wallet/store/node_list_store.dart';
51 import 'package:cake_wallet/store/secret_store.dart';
@@ -62,6 +63,7 @@ import 'package:cake_wallet/src/screens/send/send_page.dart';
63 import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
64 import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
65 import 'package:cake_wallet/store/wallet_list_store.dart';
66 +import 'package:cake_wallet/store/yat_store.dart';
67 import 'package:cake_wallet/view_model/backup_view_model.dart';
68 import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
69 import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
@@ -97,6 +99,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
99 import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
100 import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
101 import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
102 +import 'package:cake_wallet/view_model/yat_view_model.dart';
103 import 'package:flutter/widgets.dart';
104 import 'package:get_it/get_it.dart';
105 import 'package:hive/hive.dart';
@@ -192,6 +195,7 @@ Future setup(
195 SendTemplateStore(templateSource: _templates));
196 getIt.registerSingleton<ExchangeTemplateStore>(
197 ExchangeTemplateStore(templateSource: _exchangeTemplates));
198 + getIt.registerSingleton<YatStore>(YatStore());
199
200 final secretStore =
201 await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
@@ -235,7 +239,10 @@ Future setup(
239 });
240
241 getIt.registerFactory<WalletAddressListViewModel>(
238 - () => WalletAddressListViewModel(appStore: getIt.get<AppStore>()));
242 + () => WalletAddressListViewModel(
243 + appStore: getIt.get<AppStore>(),
244 + yatStore: getIt.get<YatStore>()
245 + ));
246
247 getIt.registerFactory(() => BalanceViewModel(
248 appStore: getIt.get<AppStore>(),
@@ -629,5 +636,15 @@ Future setup(
636 param2: unspentCoinsListViewModel));
637 });
638
639 + getIt.registerFactory(() => YatViewModel(
640 + yatStore: getIt.get<YatStore>()
641 + ));
642 +
643 + getIt.registerFactoryParam<YatWebViewPage, YatMode, void>((YatMode mode, _) =>
644 + YatWebViewPage(
645 + mode: mode,
646 + yatViewModel: getIt.get<YatViewModel>(),
647 + ));
648 +
649 _isSetupFinished = true;
650 }
lib/palette.dart
+3
@@ -46,6 +46,9 @@ class Palette {
46 static const Color protectiveBlue = Color.fromRGBO(33, 148, 255, 1.0);
47 static const Color darkBlue = Color.fromRGBO(109, 128, 178, 1.0);
48 static const Color paleCornflowerBlue = Color.fromRGBO(185, 196, 237, 1.0);
49 + static const Color manatee = Color.fromRGBO(153, 161, 176, 1.0);
50 + static const Color stateGray = Color.fromRGBO(68, 74, 89, 1.0);
51 + static const Color frostySky = Color.fromRGBO(0, 184, 250, 1.0);
52 }
53
54 class PaletteDark {
lib/router.dart
+6
@@ -13,6 +13,7 @@ import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
13 import 'package:cake_wallet/src/screens/support/support_page.dart';
14 import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
15 import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
16 +import 'package:cake_wallet/src/screens/yat/yat_webview_page.dart';
17 import 'package:cake_wallet/store/settings_store.dart';
18 import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
19 import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
@@ -379,6 +380,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
380 getIt.get<UnspentCoinsDetailsPage>(
381 param1: args));
382
383 + case Routes.yat:
384 + return MaterialPageRoute<void>(
385 + builder: (_) =>
386 + getIt.get<YatWebViewPage>(param1: settings.arguments as YatMode));
387 +
388 default:
389 return MaterialPageRoute<void>(
390 builder: (_) => Scaffold(
lib/routes.dart
+1
@@ -57,4 +57,5 @@ class Routes {
57 static const buyWebView = '/buy_web_view';
58 static const unspentCoinsList = '/unspent_coins_list';
59 static const unspentCoinsDetails = '/unspent_coins_details';
60 + static const yat = '/yat';
61 }
\ No newline at end of file
lib/src/screens/receive/widgets/qr_widget.dart
+43 -3
@@ -66,7 +66,7 @@ class QRWidget extends StatelessWidget {
66 ]),
67 if (isAmountFieldShow)
68 Padding(
69 - padding: EdgeInsets.only(top: 20),
69 + padding: EdgeInsets.only(top: 10),
70 child: Row(
71 children: <Widget>[
72 Expanded(
@@ -101,7 +101,7 @@ class QRWidget extends StatelessWidget {
101 ),
102 ),
103 Padding(
104 - padding: EdgeInsets.only(top: 20, bottom: 20),
104 + padding: EdgeInsets.only(top: 16, bottom: 16),
105 child: Builder(
106 builder: (context) => Observer(
107 builder: (context) => GestureDetector(
@@ -133,7 +133,47 @@ class QRWidget extends StatelessWidget {
133 ],
134 ),
135 ))),
136 - )
136 + ),
137 + Observer(builder: (_) {
138 + return addressListViewModel.yatAddress.isNotEmpty
139 + ? Padding(
140 + padding: EdgeInsets.only(bottom: 10),
141 + child: Builder(
142 + builder: (context) => GestureDetector(
143 + onTap: () {
144 + Clipboard.setData(ClipboardData(
145 + text: addressListViewModel.yatAddress));
146 + showBar<void>(
147 + context, S.of(context).copied_to_clipboard);
148 + },
149 + child: Column(
150 + crossAxisAlignment: CrossAxisAlignment.center,
151 + children: [
152 + Text(
153 + 'Yat Address',
154 + textAlign: TextAlign.center,
155 + style: TextStyle(
156 + fontSize: 13,
157 + fontWeight: FontWeight.normal,
158 + color: Theme.of(context).accentTextTheme.
159 + display3.backgroundColor),
160 + ),
161 + Padding(
162 + padding: EdgeInsets.only(top: 5),
163 + child: Text(
164 + addressListViewModel.yatAddress,
165 + textAlign: TextAlign.center,
166 + style: TextStyle(
167 + fontSize: 13,
168 + ),
169 + )
170 + )
171 + ]
172 + )
173 + )),
174 + )
175 + : Container();
176 + })
177 ],
178 );
179 }
lib/src/screens/yat/widgets/yat_bar.dart new
+27
@@ -0,0 +1,27 @@
1 +import 'package:cake_wallet/src/screens/yat/widgets/yat_close_button.dart';
2 +import 'package:flutter/material.dart';
3 +
4 +class YatBar extends StatelessWidget {
5 + YatBar({this.onClose});
6 +
7 + final VoidCallback onClose;
8 + final image = Image.asset('assets/images/yat_logo.png');
9 +
10 + @override
11 + Widget build(BuildContext context) {
12 + return Stack(
13 + alignment: Alignment.bottomCenter,
14 + children: [
15 + Positioned(
16 + top: 0,
17 + right: 0,
18 + child: YatCloseButton(onClose: onClose)
19 + ),
20 + Positioned(
21 + top: 16,
22 + child: image
23 + )
24 + ]
25 + );
26 + }
27 +}
\ No newline at end of file
lib/src/screens/yat/widgets/yat_close_button.dart new
+28
@@ -0,0 +1,28 @@
1 +import 'package:cake_wallet/palette.dart';
2 +import 'package:flutter/material.dart';
3 +
4 +class YatCloseButton extends StatelessWidget {
5 + YatCloseButton({this.onClose});
6 +
7 + final VoidCallback onClose;
8 +
9 + @override
10 + Widget build(BuildContext context) {
11 + return GestureDetector(
12 + onTap: onClose,
13 + child: Container(
14 + height: 28,
15 + width: 28,
16 + alignment: Alignment.center,
17 + decoration: BoxDecoration(
18 + color: Palette.manatee,
19 + shape: BoxShape.circle
20 + ),
21 + child: Icon(
22 + Icons.clear,
23 + color: Colors.white,
24 + size: 20)
25 + )
26 + );
27 + }
28 +}
\ No newline at end of file
lib/src/screens/yat/yat_alert.dart new
+106
@@ -0,0 +1,106 @@
1 +import 'package:cake_wallet/routes.dart';
2 +import 'package:cake_wallet/src/screens/yat/widgets/yat_bar.dart';
3 +import 'package:cake_wallet/src/screens/yat/yat_webview_page.dart';
4 +import 'package:cake_wallet/src/widgets/primary_button.dart';
5 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
6 +import 'package:flutter/cupertino.dart';
7 +import 'package:flutter/material.dart';
8 +import 'package:cake_wallet/palette.dart';
9 +
10 +class YatAlert extends StatelessWidget {
11 + static const aspectRatioImage = 1.133;
12 + final image = Image.asset('assets/images/yat_crypto.png');
13 +
14 + @override
15 + Widget build(BuildContext context) {
16 + final screenHeight = MediaQuery.of(context).size.height;
17 + final screenWidth = MediaQuery.of(context).size.width;
18 +
19 + return Container(
20 + height: screenHeight,
21 + width: screenWidth,
22 + color: Colors.white,
23 + child: ScrollableWithBottomSection(
24 + contentPadding: EdgeInsets.only(top: 40, bottom: 40),
25 + content: Column(
26 + children: [
27 + Container(
28 + height: 90,
29 + padding: EdgeInsets.only(left: 24, right: 24),
30 + child: YatBar(onClose: () => Navigator.of(context).pop())
31 + ),
32 + AspectRatio(
33 + aspectRatio: aspectRatioImage,
34 + child: FittedBox(child: image, fit: BoxFit.fill)
35 + ),
36 + Container(
37 + padding: EdgeInsets.only(left: 30, right: 30),
38 + child: Column(
39 + children: [
40 + Text(
41 + 'Send and receive crypto more easily with Yat',
42 + textAlign: TextAlign.center,
43 + style: TextStyle(
44 + fontSize: 24,
45 + fontWeight: FontWeight.bold,
46 + fontFamily: 'Lato',
47 + color: Colors.black,
48 + decoration: TextDecoration.none,
49 + )
50 + ),
51 + Padding(
52 + padding: EdgeInsets.only(top: 20),
53 + child: Text(
54 + 'Cake Wallet users can now send and receive all their favorite currencies with a one-of-a-kind emoji-based username.',
55 + textAlign: TextAlign.center,
56 + style: TextStyle(
57 + fontSize: 16,
58 + fontWeight: FontWeight.normal,
59 + fontFamily: 'Lato',
60 + color: Colors.black,
61 + decoration: TextDecoration.none,
62 + )
63 + )
64 + )
65 + ]
66 + )
67 + )
68 + ]
69 + ),
70 + bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 40),
71 + bottomSection: Column(
72 + crossAxisAlignment: CrossAxisAlignment.center,
73 + children: [
74 + PrimaryIconButton(
75 + text: 'Get your Yat',
76 + textColor: Colors.white,
77 + color: Palette.protectiveBlue,
78 + borderColor: Palette.protectiveBlue,
79 + iconColor: Colors.white,
80 + iconBackgroundColor: Colors.transparent,
81 + iconData: CupertinoIcons
82 + .arrow_up_right_square,
83 + mainAxisAlignment: MainAxisAlignment.end,
84 + onPressed: () => Navigator.of(context)
85 + .popAndPushNamed(Routes.yat, arguments: YatMode.create)),
86 + Padding(
87 + padding: EdgeInsets.only(top: 24),
88 + child: PrimaryIconButton(
89 + text: 'Connect an existing Yat',
90 + textColor: Colors.black,
91 + color: Palette.blueAlice,
92 + borderColor: Palette.blueAlice,
93 + iconColor: Colors.black,
94 + iconBackgroundColor: Colors.transparent,
95 + iconData: CupertinoIcons
96 + .arrow_up_right_square,
97 + mainAxisAlignment: MainAxisAlignment.end,
98 + onPressed: () => Navigator.of(context)
99 + .popAndPushNamed(Routes.yat, arguments: YatMode.connect))
100 + )
101 + ]
102 + ),
103 + )
104 + );
105 + }
106 +}
\ No newline at end of file
lib/src/screens/yat/yat_webview_page.dart new
+45
@@ -0,0 +1,45 @@
1 +import 'package:cake_wallet/palette.dart';
2 +import 'package:cake_wallet/src/screens/base_page.dart';
3 +import 'package:cake_wallet/view_model/yat_view_model.dart';
4 +import 'package:flutter/material.dart';
5 +import 'package:webview_flutter/webview_flutter.dart';
6 +
7 +enum YatMode {create, connect}
8 +
9 +class YatWebViewPage extends BasePage {
10 + YatWebViewPage({this.yatViewModel, this.mode}) {
11 + switch (mode) {
12 + case YatMode.create:
13 + url = _baseUrl + _createSuffix;
14 + break;
15 + case YatMode.connect:
16 + url = _baseUrl + _signInSuffix;
17 + break;
18 + default:
19 + url = _baseUrl + _createSuffix;
20 + }
21 + }
22 +
23 + static const _baseUrl = 'https://y.at';
24 + static const _signInSuffix = '/sign-in';
25 + static const _createSuffix = '/create';
26 +
27 + final YatMode mode;
28 + final YatViewModel yatViewModel;
29 +
30 + String url;
31 +
32 + @override
33 + String get title => 'Yat';
34 +
35 + @override
36 + Color get backgroundDarkColor => Colors.white;
37 +
38 + @override
39 + Color get titleColor => Palette.darkBlueCraiola;
40 +
41 + @override
42 + Widget body(BuildContext context) => WebView(
43 + initialUrl: url,
44 + javascriptMode: JavascriptMode.unrestricted);
45 +}
\ No newline at end of file
lib/src/widgets/primary_button.dart
+16 -9
@@ -110,6 +110,9 @@ class PrimaryIconButton extends StatelessWidget {
110 @required this.borderColor,
111 @required this.iconColor,
112 @required this.iconBackgroundColor,
113 + @required this.textColor,
114 + this.mainAxisAlignment = MainAxisAlignment.start,
115 + this.radius = 26
116 });
117
118 final VoidCallback onPressed;
@@ -119,40 +122,44 @@ class PrimaryIconButton extends StatelessWidget {
122 final Color iconColor;
123 final Color iconBackgroundColor;
124 final String text;
125 + final MainAxisAlignment mainAxisAlignment;
126 + final Color textColor;
127 + final double radius;
128
129 @override
130 Widget build(BuildContext context) {
131 return ButtonTheme(
132 minWidth: double.infinity,
127 - height: 56.0,
133 + height: 52.0,
134 child: FlatButton(
135 onPressed: onPressed,
136 color: color,
137 shape: RoundedRectangleBorder(
138 side: BorderSide(color: borderColor),
133 - borderRadius: BorderRadius.circular(10.0)),
139 + borderRadius: BorderRadius.circular(radius)),
140 child: Stack(
141 children: <Widget>[
142 Row(
137 - mainAxisAlignment: MainAxisAlignment.start,
143 + mainAxisAlignment: mainAxisAlignment,
144 children: <Widget>[
145 Container(
140 - width: 28.0,
141 - height: 56.0,
146 + width: 26.0,
147 + height: 52.0,
148 decoration: BoxDecoration(
149 shape: BoxShape.circle, color: iconBackgroundColor),
144 - child: Icon(iconData, color: iconColor, size: 22.0),
150 + child: Center(
151 + child: Icon(iconData, color: iconColor, size: 22.0)
152 + ),
153 ),
154 ],
155 ),
156 Container(
149 - height: 56.0,
157 + height: 52.0,
158 child: Center(
159 child: Text(text,
160 style: TextStyle(
161 fontSize: 16.0,
154 - color:
155 - Theme.of(context).primaryTextTheme.button.color)),
162 + color: textColor)),
163 ),
164 )
165 ],
lib/store/yat_store.dart new
+12
@@ -0,0 +1,12 @@
1 +import 'package:mobx/mobx.dart';
2 +
3 +part 'yat_store.g.dart';
4 +
5 +class YatStore = YatStoreBase with _$YatStore;
6 +
7 +abstract class YatStoreBase with Store {
8 + YatStoreBase() : yatAddress = '';
9 +
10 + @observable
11 + String yatAddress;
12 +}
\ No newline at end of file
lib/view_model/settings/settings_view_model.dart
+12
@@ -1,3 +1,5 @@
1 +import 'package:cake_wallet/src/screens/yat/yat_alert.dart';
2 +import 'package:cake_wallet/utils/show_pop_up.dart';
3 import 'package:flutter/cupertino.dart';
4 import 'package:mobx/mobx.dart';
5 import 'package:package_info/package_info.dart';
@@ -154,6 +156,16 @@ abstract class SettingsViewModelBase with Store {
156 _settingsStore.currentTheme = theme)
157 ],
158 [
159 + RegularListItem(
160 + title: 'Manage Yats',
161 + handler: (BuildContext context) async {
162 + await showPopUp<void>(
163 + context: context,
164 + builder: (BuildContext context) {
165 + return YatAlert();
166 + });
167 + },
168 + ),
169 RegularListItem(
170 title: S.current.settings_terms_and_conditions,
171 handler: (BuildContext context) =>
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+10 -1
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/store/yat_store.dart';
2 import 'package:flutter/foundation.dart';
3 import 'package:mobx/mobx.dart';
4 import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
@@ -59,7 +60,10 @@ class BitcoinURI extends PaymentURI {
60 }
61
62 abstract class WalletAddressListViewModelBase with Store {
62 - WalletAddressListViewModelBase({@required AppStore appStore}) {
63 + WalletAddressListViewModelBase({
64 + @required AppStore appStore,
65 + @required this.yatStore
66 + }) {
67 _appStore = appStore;
68 _wallet = _appStore.wallet;
69 hasAccounts = _wallet?.type == WalletType.monero;
@@ -150,6 +154,9 @@ abstract class WalletAddressListViewModelBase with Store {
154 @computed
155 bool get hasAddressList => _wallet.type == WalletType.monero;
156
157 + @computed
158 + String get yatAddress => yatStore.yatAddress;
159 +
160 @observable
161 WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
162 _wallet;
@@ -158,6 +165,8 @@ abstract class WalletAddressListViewModelBase with Store {
165
166 AppStore _appStore;
167
168 + final YatStore yatStore;
169 +
170 ReactionDisposer _onWalletChangeReaction;
171
172 @action
lib/view_model/yat_view_model.dart new
+37
@@ -0,0 +1,37 @@
1 +import 'package:cake_wallet/store/yat_store.dart';
2 +import 'package:flutter/foundation.dart';
3 +import 'package:mobx/mobx.dart';
4 +import 'dart:convert';
5 +import 'package:cake_wallet/yat/yat_exception.dart';
6 +import 'package:http/http.dart';
7 +
8 +part 'yat_view_model.g.dart';
9 +
10 +class YatViewModel = YatViewModelBase with _$YatViewModel;
11 +
12 +abstract class YatViewModelBase with Store {
13 + YatViewModelBase({@required this.yatStore});
14 +
15 + final YatStore yatStore;
16 +
17 + Future<void> fetchCartInfo() async {
18 + const _apiKey = ''; // FIXME
19 +
20 + final url = 'https://api.y.at/cart';
21 +
22 + final response = await get(
23 + url,
24 + headers: {
25 + 'Accept': '*/*',
26 + 'Authorization,X-Api-Key': _apiKey
27 + }
28 + );
29 +
30 + if (response.statusCode != 200) {
31 + throw YatException(text: response.body.toString());
32 + }
33 +
34 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
35 + yatStore.yatAddress = responseJSON[''] as String; // FIXME
36 + }
37 +}
\ No newline at end of file
lib/yat/yat_exception.dart new
+10
@@ -0,0 +1,10 @@
1 +import 'package:flutter/foundation.dart';
2 +
3 +class YatException implements Exception {
4 + YatException({@required this.text});
5 +
6 + final String text;
7 +
8 + @override
9 + String toString() => 'Yat exception: $text';
10 +}
\ No newline at end of file
lib/yat/yat_record.dart new
+26
@@ -0,0 +1,26 @@
1 +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) async {
6 + const _apiKey = ''; // FIXME
7 + const _requestURL = 'https://api.y.at/emoji_id/';
8 +
9 + final url = _requestURL + emojiId;
10 +
11 + final response = await get(
12 + url,
13 + headers: {
14 + 'Accept': '*/*',
15 + 'Authorization': 'Bearer $_apiKey'
16 + }
17 + );
18 +
19 + if (response.statusCode != 200) {
20 + throw YatException(text: response.body.toString());
21 + }
22 +
23 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
24 + final yatAddress = responseJSON[''] as String; // FIXME
25 + return yatAddress;
26 +}
\ No newline at end of file