Cw 314 trocador receive screen update (#823)

* Change receive screen ui * Upgrade flutter packages * revert Upgrade flutter packages * revert Upgrade flutter packages * Adjust flow for anon invoice page navigation * Add receive screen ui * Implement anonpay invoice * Add invoice detail to transactions page * Implement donation link * Fix transaction filter and details view * Save donation link * Fix transaction display issues * Fix formatting * Fix merge conflict * Fix localization * Fix transaction amount display * Fix transaction limit for fiat * Update fix from code review * Fix issues from code review * Make amountTo nullable to avoid potential * Remove encoding for description in donation link * Remove optional params from request * Fix QR image version * Refactor QRCode, fix issues from code review * Pass version to QRCode full page --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>

Godwin Asuquo committed Mar 24, 2023 at 17:26 UTC 3006679560ea83c11deb2e101c316eca28680bb8
67 files changed +2502 -288
.github/workflows/pr_test_build.yml
+1
@@ -113,6 +113,7 @@ jobs:
113 echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
114 echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart
115 echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
116 + echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
117
118 - name: Rename app
119 run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties
cw_core/lib/crypto_currency.dart
+20 -1
@@ -1,6 +1,7 @@
1 +import 'package:cw_core/currency.dart';
2 import 'package:cw_core/enumerable_item.dart';
3
3 -class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
4 +class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implements Currency {
5 const CryptoCurrency({
6 String title = '',
7 int raw = -1,
@@ -162,6 +163,14 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
163 return acc;
164 });
165
166 + static final Map<String, CryptoCurrency> _fullNameCurrencyMap =
167 + [...all, ...havenCurrencies].fold<Map<String, CryptoCurrency>>(<String, CryptoCurrency>{}, (acc, item) {
168 + if(item.fullName != null){
169 + acc.addAll({item.fullName!.toLowerCase(): item});
170 + }
171 + return acc;
172 + });
173 +
174 static CryptoCurrency deserialize({required int raw}) {
175
176 if (CryptoCurrency._rawCurrencyMap[raw] == null) {
@@ -180,6 +189,16 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
189 return CryptoCurrency._nameCurrencyMap[name.toLowerCase()]!;
190 }
191
192 + static CryptoCurrency fromFullName(String name) {
193 +
194 + if (CryptoCurrency._fullNameCurrencyMap[name.toLowerCase()] == null) {
195 + final s = 'Unexpected token: $name for CryptoCurrency fromFullName';
196 + throw ArgumentError.value(name, 'Fullname', s);
197 + }
198 + return CryptoCurrency._fullNameCurrencyMap[name.toLowerCase()]!;
199 + }
200 +
201 +
202 @override
203 String toString() => title;
204 }
cw_core/lib/currency.dart new
+6
@@ -0,0 +1,6 @@
1 +abstract class Currency {
2 + String get name;
3 + String? get tag;
4 + String? get fullName;
5 + String? get iconPath;
6 +}
\ No newline at end of file
lib/anonpay/anonpay_api.dart new
+211
@@ -0,0 +1,211 @@
1 +import 'dart:convert';
2 +import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
3 +import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
4 +import 'package:cake_wallet/anonpay/anonpay_request.dart';
5 +import 'package:cake_wallet/anonpay/anonpay_status_response.dart';
6 +import 'package:cake_wallet/core/fiat_conversion_service.dart';
7 +import 'package:cake_wallet/entities/fiat_currency.dart';
8 +import 'package:cake_wallet/exchange/limits.dart';
9 +import 'package:cw_core/wallet_base.dart';
10 +import 'package:http/http.dart';
11 +import 'package:cw_core/crypto_currency.dart';
12 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
13 +
14 +class AnonPayApi {
15 + const AnonPayApi({
16 + this.useTorOnly = false,
17 + required this.wallet,
18 + });
19 + final bool useTorOnly;
20 + final WalletBase wallet;
21 +
22 + static const anonpayRef = secrets.anonPayReferralCode;
23 + static const onionApiAuthority = 'trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion';
24 + static const clearNetAuthority = 'trocador.app';
25 + static const markup = secrets.trocadorExchangeMarkup;
26 + static const anonPayPath = '/anonpay';
27 + static const anonPayStatus = '/anonpay/status';
28 + static const coinPath = 'api/coin';
29 + static const apiKey = secrets.trocadorApiKey;
30 +
31 + Future<AnonpayStatusResponse> paymentStatus(String id) async {
32 + final authority = await _getAuthority();
33 + final response = await get(Uri.https(authority, "$anonPayStatus/$id"));
34 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
35 + final status = responseJSON['Status'] as String;
36 + final fiatAmount = responseJSON['Fiat_Amount'] as double?;
37 + final fiatEquiv = responseJSON['Fiat_Equiv'] as String?;
38 + final amountTo = responseJSON['AmountTo'] as double?;
39 + final coinTo = responseJSON['CoinTo'] as String;
40 + final address = responseJSON['Address'] as String;
41 +
42 + return AnonpayStatusResponse(
43 + status: status,
44 + fiatAmount: fiatAmount,
45 + amountTo: amountTo,
46 + coinTo: coinTo,
47 + address: address,
48 + fiatEquiv: fiatEquiv,
49 + );
50 + }
51 +
52 + Future<AnonpayInvoiceInfo> createInvoice(AnonPayRequest request) async {
53 + final description = Uri.encodeComponent(request.description);
54 + final body = <String, dynamic>{
55 + 'ticker_to': request.cryptoCurrency.title.toLowerCase(),
56 + 'network_to': _networkFor(request.cryptoCurrency),
57 + 'address': request.address,
58 + 'name': request.name,
59 + 'description': description,
60 + 'email': request.email,
61 + 'ref': anonpayRef,
62 + 'markup': markup,
63 + 'direct': 'False',
64 + };
65 +
66 + if (request.amount != null) {
67 + body['amount'] = request.amount;
68 + }
69 + if (request.fiatEquivalent != null) {
70 + body['fiat_equiv'] = request.fiatEquivalent;
71 + }
72 + final authority = await _getAuthority();
73 +
74 + final response = await get(Uri.https(authority, anonPayPath, body));
75 +
76 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
77 + final id = responseJSON['ID'] as String;
78 + final url = responseJSON['url'] as String;
79 + final urlOnion = responseJSON['url_onion'] as String;
80 + final statusUrl = responseJSON['status_url'] as String;
81 + final statusUrlOnion = responseJSON['status_url_onion'] as String;
82 +
83 + final statusInfo = await paymentStatus(id);
84 +
85 + return AnonpayInvoiceInfo(
86 + invoiceId: id,
87 + clearnetUrl: url,
88 + onionUrl: urlOnion,
89 + status: statusInfo.status,
90 + fiatAmount: statusInfo.fiatAmount,
91 + fiatEquiv: statusInfo.fiatEquiv,
92 + amountTo: statusInfo.amountTo,
93 + coinTo: statusInfo.coinTo,
94 + address: statusInfo.address,
95 + clearnetStatusUrl: statusUrl,
96 + onionStatusUrl: statusUrlOnion,
97 + walletId: wallet.id,
98 + createdAt: DateTime.now(),
99 + provider: 'Trocador AnonPay invoice',
100 + );
101 + }
102 +
103 + Future<AnonpayDonationLinkInfo> generateDonationLink(AnonPayRequest request) async {
104 + final body = <String, dynamic>{
105 + 'ticker_to': request.cryptoCurrency.title.toLowerCase(),
106 + 'network_to': _networkFor(request.cryptoCurrency),
107 + 'address': request.address,
108 + 'ref': anonpayRef,
109 + 'direct': 'True',
110 + };
111 + if (request.name.isNotEmpty) {
112 + body['name'] = request.name;
113 + }
114 + if (request.description.isNotEmpty) {
115 + body['description'] = request.description;
116 + }
117 + if (request.email.isNotEmpty) {
118 + body['email'] = request.email;
119 + }
120 +
121 + final clearnetUrl = Uri.https(clearNetAuthority, anonPayPath, body);
122 + final onionUrl = Uri.https(onionApiAuthority, anonPayPath, body);
123 + return AnonpayDonationLinkInfo(
124 + clearnetUrl: clearnetUrl.toString(),
125 + onionUrl: onionUrl.toString(),
126 + address: request.address,
127 + );
128 + }
129 +
130 + Future<Limits> fetchLimits({
131 + FiatCurrency? fiatCurrency,
132 + required CryptoCurrency cryptoCurrency,
133 + }) async {
134 + double fiatRate = 0.0;
135 + if (fiatCurrency != null) {
136 + fiatRate = await FiatConversionService.fetchPrice(
137 + crypto: cryptoCurrency,
138 + fiat: fiatCurrency,
139 + torOnly: useTorOnly,
140 + );
141 + }
142 +
143 + final params = <String, String>{
144 + 'api_key': apiKey,
145 + 'ticker': cryptoCurrency.title.toLowerCase(),
146 + 'name': cryptoCurrency.name,
147 + };
148 +
149 + final String apiAuthority = await _getAuthority();
150 + final uri = Uri.https(apiAuthority, coinPath, params);
151 +
152 + final response = await get(uri);
153 +
154 + if (response.statusCode != 200) {
155 + throw Exception('Unexpected http status: ${response.statusCode}');
156 + }
157 +
158 + final responseJSON = json.decode(response.body) as List<dynamic>;
159 +
160 + if (responseJSON.isEmpty) {
161 + throw Exception('No data');
162 + }
163 +
164 + final coinJson = responseJSON.first as Map<String, dynamic>;
165 + final minimum = coinJson['minimum'] as double;
166 + final maximum = coinJson['maximum'] as double;
167 +
168 + if (fiatCurrency != null) {
169 + return Limits(
170 + min: double.tryParse((minimum * fiatRate).toStringAsFixed(2)),
171 + max: double.tryParse((maximum * fiatRate).toStringAsFixed(2)),
172 + );
173 + }
174 +
175 + return Limits(
176 + min: minimum,
177 + max: maximum,
178 + );
179 + }
180 +
181 + String _networkFor(CryptoCurrency currency) {
182 + switch (currency) {
183 + case CryptoCurrency.usdt:
184 + return CryptoCurrency.btc.title.toLowerCase();
185 + default:
186 + return currency.tag != null ? _normalizeTag(currency.tag!) : 'Mainnet';
187 + }
188 + }
189 +
190 + String _normalizeTag(String tag) {
191 + switch (tag) {
192 + case 'ETH':
193 + return 'ERC20';
194 + default:
195 + return tag.toLowerCase();
196 + }
197 + }
198 +
199 + Future<String> _getAuthority() async {
200 + try {
201 + if (useTorOnly) {
202 + return onionApiAuthority;
203 + }
204 + final uri = Uri.https(onionApiAuthority, '/anonpay');
205 + await get(uri);
206 + return onionApiAuthority;
207 + } catch (e) {
208 + return clearNetAuthority;
209 + }
210 + }
211 +}
lib/anonpay/anonpay_donation_link_info.dart new
+13
@@ -0,0 +1,13 @@
1 +import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
2 +
3 +class AnonpayDonationLinkInfo implements AnonpayInfoBase{
4 + final String clearnetUrl;
5 + final String onionUrl;
6 + final String address;
7 +
8 + AnonpayDonationLinkInfo({
9 + required this.clearnetUrl,
10 + required this.onionUrl,
11 + required this.address,
12 + });
13 +}
\ No newline at end of file
lib/anonpay/anonpay_info_base.dart new
+5
@@ -0,0 +1,5 @@
1 +abstract class AnonpayInfoBase {
2 + String get clearnetUrl;
3 + String get onionUrl;
4 + String get address;
5 +}
\ No newline at end of file
lib/anonpay/anonpay_invoice_info.dart new
+57
@@ -0,0 +1,57 @@
1 +import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
2 +import 'package:cw_core/keyable.dart';
3 +import 'package:hive/hive.dart';
4 +
5 +part 'anonpay_invoice_info.g.dart';
6 +
7 +@HiveType(typeId: AnonpayInvoiceInfo.typeId)
8 +class AnonpayInvoiceInfo extends HiveObject with Keyable implements AnonpayInfoBase {
9 + @HiveField(0)
10 + final String invoiceId;
11 + @HiveField(1)
12 + String status;
13 + @HiveField(2)
14 + final double? fiatAmount;
15 + @HiveField(3)
16 + final String? fiatEquiv;
17 + @HiveField(4)
18 + final double? amountTo;
19 + @HiveField(5)
20 + final String coinTo;
21 + @HiveField(6)
22 + final String address;
23 + @HiveField(7)
24 + final String clearnetUrl;
25 + @HiveField(8)
26 + final String onionUrl;
27 + @HiveField(9)
28 + final String clearnetStatusUrl;
29 + @HiveField(10)
30 + final String onionStatusUrl;
31 + @HiveField(11)
32 + final DateTime createdAt;
33 + @HiveField(12)
34 + final String walletId;
35 + @HiveField(13)
36 + final String provider;
37 +
38 + static const typeId = 10;
39 + static const boxName = 'AnonpayInvoiceInfo';
40 +
41 + AnonpayInvoiceInfo({
42 + required this.invoiceId,
43 + required this.clearnetUrl,
44 + required this.onionUrl,
45 + required this.clearnetStatusUrl,
46 + required this.onionStatusUrl,
47 + required this.status,
48 + this.fiatAmount,
49 + this.fiatEquiv,
50 + this.amountTo,
51 + required this.coinTo,
52 + required this.address,
53 + required this.createdAt,
54 + required this.walletId,
55 + required this.provider,
56 + });
57 +}
lib/anonpay/anonpay_request.dart new
+21
@@ -0,0 +1,21 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +
3 +class AnonPayRequest {
4 + CryptoCurrency cryptoCurrency;
5 + String address;
6 + String name;
7 + String? amount;
8 + String email;
9 + String description;
10 + String? fiatEquivalent;
11 +
12 + AnonPayRequest({
13 + required this.cryptoCurrency,
14 + required this.address,
15 + required this.name,
16 + required this.email,
17 + this.amount,
18 + required this.description,
19 + this.fiatEquivalent,
20 + });
21 +}
lib/anonpay/anonpay_status_response.dart new
+17
@@ -0,0 +1,17 @@
1 +class AnonpayStatusResponse {
2 + final String status;
3 + final double? fiatAmount;
4 + final String? fiatEquiv;
5 + final double? amountTo;
6 + final String coinTo;
7 + final String address;
8 +
9 + const AnonpayStatusResponse({
10 + required this.status,
11 + this.fiatAmount,
12 + this.fiatEquiv,
13 + this.amountTo,
14 + required this.coinTo,
15 + required this.address,
16 + });
17 +}
lib/di.dart
+70 -5
@@ -1,10 +1,18 @@
1 +import 'package:cake_wallet/anonpay/anonpay_api.dart';
2 +import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
3 +import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
4 import 'package:cake_wallet/core/yat_service.dart';
5 +import 'package:cake_wallet/entities/exchange_api_mode.dart';
6 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
7 +import 'package:cake_wallet/entities/receive_page_option.dart';
8 import 'package:cake_wallet/entities/wake_lock.dart';
9 import 'package:cake_wallet/ionia/ionia_anypay.dart';
10 import 'package:cake_wallet/ionia/ionia_gift_card.dart';
11 import 'package:cake_wallet/ionia/ionia_tip.dart';
12 +import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
13 import 'package:cake_wallet/src/screens/buy/onramper_page.dart';
14 +import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
15 +import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
16 import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
17 import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
18 import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
@@ -13,7 +21,11 @@ import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dar
21 import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
22 import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart';
23 import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
24 +import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
25 import 'package:cake_wallet/utils/payment_request.dart';
26 +import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
27 +import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
28 +import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
29 import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
30 import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart';
31 import 'package:cake_wallet/view_model/ionia/ionia_custom_tip_view_model.dart';
@@ -176,6 +188,7 @@ late Box<ExchangeTemplate> _exchangeTemplates;
188 late Box<TransactionDescription> _transactionDescriptionBox;
189 late Box<Order> _ordersSource;
190 late Box<UnspentCoinsInfo>? _unspentCoinsInfoSource;
191 +late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
192
193 Future setup(
194 {required Box<WalletInfo> walletInfoSource,
@@ -186,7 +199,9 @@ Future setup(
199 required Box<ExchangeTemplate> exchangeTemplates,
200 required Box<TransactionDescription> transactionDescriptionBox,
201 required Box<Order> ordersSource,
189 - Box<UnspentCoinsInfo>? unspentCoinsInfoSource}) async {
202 + Box<UnspentCoinsInfo>? unspentCoinsInfoSource,
203 + required Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource
204 + }) async {
205 _walletInfoSource = walletInfoSource;
206 _nodeSource = nodeSource;
207 _contactSource = contactSource;
@@ -196,6 +211,7 @@ Future setup(
211 _transactionDescriptionBox = transactionDescriptionBox;
212 _ordersSource = ordersSource;
213 _unspentCoinsInfoSource = unspentCoinsInfoSource;
214 + _anonpayInvoiceInfoSource = anonpayInvoiceInfoSource;
215
216 if (!_isSetupFinished) {
217 getIt.registerSingletonAsync<SharedPreferences>(
@@ -240,6 +256,8 @@ Future setup(
256 appStore: getIt.get<AppStore>(),
257 secureStorage: getIt.get<FlutterSecureStorage>())
258 ..init());
259 + getIt.registerSingleton<AnonpayTransactionsStore>(AnonpayTransactionsStore(
260 + anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
261
262 final secretStore =
263 await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
@@ -306,7 +324,9 @@ Future setup(
324 transactionFilterStore: getIt.get<TransactionFilterStore>(),
325 settingsStore: settingsStore,
326 yatStore: getIt.get<YatStore>(),
309 - ordersStore: getIt.get<OrdersStore>()));
327 + ordersStore: getIt.get<OrdersStore>(),
328 + anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>())
329 + );
330
331 getIt.registerFactory<AuthService>(() => AuthService(
332 secureStorage: getIt.get<FlutterSecureStorage>(),
@@ -360,11 +380,37 @@ Future setup(
380 BalancePage(dashboardViewModel: getIt.get<DashboardViewModel>(), settingsStore: getIt.get<SettingsStore>()));
381
382 getIt.registerFactory<DashboardPage>(() => DashboardPage( balancePage: getIt.get<BalancePage>(), walletViewModel: getIt.get<DashboardViewModel>(), addressListViewModel: getIt.get<WalletAddressListViewModel>()));
383 +
384 + getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>((pageOption, _) => ReceiveOptionViewModel(
385 + getIt.get<AppStore>().wallet!, pageOption));
386 +
387 + getIt.registerFactoryParam<AnonInvoicePageViewModel, List<dynamic>, void>((args, _) {
388 + final address = args.first as String;
389 + final pageOption = args.last as ReceivePageOption;
390 + return AnonInvoicePageViewModel(
391 + getIt.get<AnonPayApi>(),
392 + address,
393 + getIt.get<SettingsStore>(),
394 + getIt.get<AppStore>().wallet!,
395 + _anonpayInvoiceInfoSource,
396 + getIt.get<SharedPreferences>(),
397 + pageOption,
398 + );
399 + });
400 +
401 + getIt.registerFactoryParam<AnonPayInvoicePage, List<dynamic>, void>((List<dynamic> args, _) {
402 + final pageOption = args.last as ReceivePageOption;
403 + return AnonPayInvoicePage(
404 + getIt.get<AnonInvoicePageViewModel>(param1: args),
405 + getIt.get<ReceiveOptionViewModel>(param1: pageOption));
406 + });
407 +
408 getIt.registerFactory<ReceivePage>(() => ReceivePage(
409 addressListViewModel: getIt.get<WalletAddressListViewModel>()));
410 getIt.registerFactory<AddressPage>(() => AddressPage(
411 addressListViewModel: getIt.get<WalletAddressListViewModel>(),
367 - walletViewModel: getIt.get<DashboardViewModel>()));
412 + walletViewModel: getIt.get<DashboardViewModel>(),
413 + receiveOptionViewModel: getIt.get<ReceiveOptionViewModel>()));
414
415 getIt.registerFactoryParam<WalletAddressEditOrCreateViewModel, WalletAddressListItem?, void>(
416 (WalletAddressListItem? item, _) => WalletAddressEditOrCreateViewModel(
@@ -716,8 +762,8 @@ Future setup(
762 getIt.registerFactory(() => AddressResolver(yatService: getIt.get<YatService>(),
763 walletType: getIt.get<AppStore>().wallet!.type));
764
719 - getIt.registerFactoryParam<FullscreenQRPage, String, bool>(
720 - (String qrData, bool isLight) => FullscreenQRPage(qrData: qrData, isLight: isLight,));
765 + getIt.registerFactoryParam<FullscreenQRPage, String, int?>(
766 + (String qrData, int? version) => FullscreenQRPage(qrData: qrData, version: version,));
767
768 getIt.registerFactory(() => IoniaApi());
769
@@ -823,6 +869,25 @@ Future setup(
869 getIt.registerFactory(() => IoniaAccountPage(getIt.get<IoniaAccountViewModel>()));
870
871 getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get<IoniaAccountViewModel>()));
872 +
873 + getIt.registerFactory(() => AnonPayApi(useTorOnly: getIt.get<SettingsStore>().exchangeStatus == ExchangeApiMode.torOnly,
874 + wallet: getIt.get<AppStore>().wallet!)
875 + );
876 +
877 + getIt.registerFactoryParam<AnonpayDetailsViewModel, AnonpayInvoiceInfo, void>(
878 + (AnonpayInvoiceInfo anonpayInvoiceInfo, _)
879 + => AnonpayDetailsViewModel(
880 + anonPayApi: getIt.get<AnonPayApi>(),
881 + anonpayInvoiceInfo: anonpayInvoiceInfo,
882 + settingsStore: getIt.get<SettingsStore>(),
883 + ));
884 +
885 + getIt.registerFactoryParam<AnonPayReceivePage, AnonpayInfoBase, void>(
886 + (AnonpayInfoBase anonpayInvoiceInfo, _) => AnonPayReceivePage(invoiceInfo: anonpayInvoiceInfo));
887 +
888 + getIt.registerFactoryParam<AnonpayDetailsPage, AnonpayInvoiceInfo, void>(
889 + (AnonpayInvoiceInfo anonpayInvoiceInfo, _)
890 + => AnonpayDetailsPage(anonpayDetailsViewModel: getIt.get<AnonpayDetailsViewModel>(param1: anonpayInvoiceInfo)));
891
892 getIt.registerFactoryParam<IoniaPaymentStatusViewModel, IoniaAnyPayPaymentInfo, AnyPayPaymentCommittedInfo>(
893 (IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo)
lib/entities/fiat_currency.dart
+11 -1
@@ -1,6 +1,7 @@
1 +import 'package:cw_core/currency.dart';
2 import 'package:cw_core/enumerable_item.dart';
3
3 -class FiatCurrency extends EnumerableItem<String> with Serializable<String> {
4 +class FiatCurrency extends EnumerableItem<String> with Serializable<String> implements Currency {
5 const FiatCurrency({required String symbol, required this.countryCode, required this.fullName}) : super(title: symbol, raw: symbol);
6
7 final String countryCode;
@@ -118,4 +119,13 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> {
119
120 @override
121 int get hashCode => raw.hashCode ^ title.hashCode;
122 +
123 + @override
124 + String get name => raw;
125 +
126 + @override
127 + String? get tag => null;
128 +
129 + @override
130 + String get iconPath => "assets/images/flags/$countryCode.png";
131 }
lib/entities/preferences_key.dart
+2
@@ -37,4 +37,6 @@ class PreferencesKey {
37 => '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
38
39 static const exchangeProvidersSelection = 'exchange-providers-selection';
40 + static const clearnetDonationLink = 'clearnet_donation_link';
41 + static const onionDonationLink = 'onion_donation_link';
42 }
lib/entities/receive_page_option.dart new
+23
@@ -0,0 +1,23 @@
1 +
2 +enum ReceivePageOption {
3 + mainnet,
4 + anonPayInvoice,
5 + anonPayDonationLink;
6 +
7 + @override
8 + String toString() {
9 + String label = '';
10 + switch (this) {
11 + case ReceivePageOption.mainnet:
12 + label = 'Mainnet';
13 + break;
14 + case ReceivePageOption.anonPayInvoice:
15 + label = 'Trocador AnonPay Invoice';
16 + break;
17 + case ReceivePageOption.anonPayDonationLink:
18 + label = 'Trocador AnonPay Donation Link';
19 + break;
20 + }
21 + return label;
22 + }
23 +}
lib/main.dart
+9
@@ -1,4 +1,5 @@
1 import 'dart:async';
2 +import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
3 import 'package:cake_wallet/core/auth_service.dart';
4 import 'package:cake_wallet/entities/language_service.dart';
5 import 'package:cake_wallet/buy/order.dart';
@@ -100,6 +101,10 @@ Future<void> main() async {
101 Hive.registerAdapter(UnspentCoinsInfoAdapter());
102 }
103
104 + if (!Hive.isAdapterRegistered(AnonpayInvoiceInfo.typeId)) {
105 + Hive.registerAdapter(AnonpayInvoiceInfoAdapter());
106 + }
107 +
108 final secureStorage = FlutterSecureStorage();
109 final transactionDescriptionsBoxKey = await getEncryptionKey(
110 secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
@@ -120,6 +125,7 @@ Future<void> main() async {
125 final templates = await Hive.openBox<Template>(Template.boxName);
126 final exchangeTemplates =
127 await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
128 + final anonpayInvoiceInfo = await Hive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName);
129 Box<UnspentCoinsInfo>? unspentCoinsInfoSource;
130
131 if (!isMoneroOnly) {
@@ -139,6 +145,7 @@ Future<void> main() async {
145 exchangeTemplates: exchangeTemplates,
146 transactionDescriptions: transactionDescriptions,
147 secureStorage: secureStorage,
148 + anonpayInvoiceInfo: anonpayInvoiceInfo,
149 initialMigrationVersion: 19);
150 runApp(App());
151 }, (error, stackTrace) async {
@@ -158,6 +165,7 @@ Future<void> initialSetup(
165 required Box<ExchangeTemplate> exchangeTemplates,
166 required Box<TransactionDescription> transactionDescriptions,
167 required FlutterSecureStorage secureStorage,
168 + required Box<AnonpayInvoiceInfo> anonpayInvoiceInfo,
169 Box<UnspentCoinsInfo>? unspentCoinsInfoSource,
170 int initialMigrationVersion = 15}) async {
171 LanguageService.loadLocaleList();
@@ -178,6 +186,7 @@ Future<void> initialSetup(
186 exchangeTemplates: exchangeTemplates,
187 transactionDescriptionBox: transactionDescriptions,
188 ordersSource: ordersSource,
189 + anonpayInvoiceInfoSource: anonpayInvoiceInfo,
190 unspentCoinsInfoSource: unspentCoinsInfoSource,
191 );
192 await bootstrap(navigatorKey);
lib/router.dart
+20 -2
@@ -1,10 +1,15 @@
1 +import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
2 +import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
3 import 'package:cake_wallet/entities/contact_record.dart';
4 import 'package:cake_wallet/buy/order.dart';
5 +import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
6 import 'package:cake_wallet/src/screens/backup/backup_page.dart';
7 import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
8 import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
9 import 'package:cake_wallet/src/screens/buy/onramper_page.dart';
10 import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
11 +import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
12 +import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
13 import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
14 import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
15 import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
@@ -440,7 +445,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
445 builder: (_) =>
446 getIt.get<FullscreenQRPage>(
447 param1: args['qrData'] as String,
443 - param2: args['isLight'] as bool,
448 + param2: args['version'] as int?,
449 +
450 ));
451
452 case Routes.ioniaWelcomePage:
@@ -514,7 +520,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
520 getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
521 getIt.get<NodeCreateOrEditViewModel>(param1: type),
522 ));
517 -
523 +
524 + case Routes.anonPayInvoicePage:
525 + final args = settings.arguments as List;
526 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
527 +
528 + case Routes.anonPayReceivePage:
529 + final anonInvoiceViewData = settings.arguments as AnonpayInfoBase;
530 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonInvoiceViewData));
531 +
532 + case Routes.anonPayDetailsPage:
533 + final anonInvoiceViewData = settings.arguments as AnonpayInvoiceInfo;
534 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonpayDetailsPage>(param1: anonInvoiceViewData));
535 +
536 default:
537 return MaterialPageRoute<void>(
538 builder: (_) => Scaffold(
lib/routes.dart
+3
@@ -82,4 +82,7 @@ class Routes {
82 static const displaySettingsPage = '/display_settings_page';
83 static const otherSettingsPage = '/other_settings_page';
84 static const advancedPrivacySettings = '/advanced_privacy_settings';
85 + static const anonPayInvoicePage = '/anon_pay_invoice_page';
86 + static const anonPayReceivePage = '/anon_pay_receive_page';
87 + static const anonPayDetailsPage = '/anon_pay_details_page';
88 }
lib/src/screens/anonpay_details/anonpay_details_page.dart new
+56
@@ -0,0 +1,56 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/src/screens/base_page.dart';
3 +import 'package:cake_wallet/src/screens/trade_details/trade_details_list_card.dart';
4 +import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.dart';
5 +import 'package:cake_wallet/src/widgets/list_row.dart';
6 +import 'package:cake_wallet/src/widgets/standard_list.dart';
7 +import 'package:cake_wallet/src/widgets/standard_list_card.dart';
8 +import 'package:cake_wallet/src/widgets/standard_list_status_row.dart';
9 +import 'package:cake_wallet/utils/show_bar.dart';
10 +import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
11 +import 'package:flutter/material.dart';
12 +import 'package:flutter/services.dart';
13 +
14 +class AnonpayDetailsPage extends BasePage {
15 + AnonpayDetailsPage({required this.anonpayDetailsViewModel});
16 +
17 + @override
18 + String get title => S.current.invoice_details;
19 +
20 + final AnonpayDetailsViewModel anonpayDetailsViewModel;
21 +
22 + @override
23 + Widget body(BuildContext context) {
24 + return SectionStandardList(
25 + context: context,
26 + sectionCount: 1,
27 + itemCounter: (int _) => anonpayDetailsViewModel.items.length,
28 + itemBuilder: (_, __, index) {
29 + final item = anonpayDetailsViewModel.items[index];
30 +
31 + if (item is DetailsListStatusItem) {
32 + return StandardListStatusRow(title: item.title, value: item.value);
33 + }
34 +
35 + if (item is TradeDetailsListCardItem) {
36 + return TradeDetailsStandardListCard(
37 + id: item.id,
38 + create: item.createdAt,
39 + pair: item.pair,
40 + currentTheme: anonpayDetailsViewModel.settingsStore.currentTheme.type,
41 + onTap: item.onTap,
42 + );
43 + }
44 +
45 + return GestureDetector(
46 + onTap: () {
47 + Clipboard.setData(ClipboardData(text: item.value));
48 + showBar<void>(context, S.of(context).transaction_details_copied(item.title));
49 + },
50 + child: ListRow(title: '${item.title}:', value: item.value),
51 + );
52 +
53 +
54 + });
55 + }
56 +}
lib/src/screens/contact/contact_page.dart
+3 -2
@@ -1,6 +1,7 @@
1 import 'package:cake_wallet/core/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';
7 import 'package:flutter_mobx/flutter_mobx.dart';
@@ -153,8 +154,8 @@ class ContactPage extends BasePage {
154 items: contactViewModel.currencies,
155 title: S.of(context).please_select,
156 hintText: S.of(context).search_currency,
156 - onItemSelected: (CryptoCurrency item) =>
157 - contactViewModel.currency = item),
157 + onItemSelected: (Currency item) =>
158 + contactViewModel.currency = item as CryptoCurrency),
159 context: context);
160 }
161
lib/src/screens/dashboard/widgets/address_page.dart
+120 -83
@@ -1,9 +1,14 @@
1 +import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
2 +import 'package:cake_wallet/entities/preferences_key.dart';
3 +import 'package:cake_wallet/entities/receive_page_option.dart';
4 import 'package:cake_wallet/src/screens/base_page.dart';
5 +import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
6 import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
7 import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
8 import 'package:cake_wallet/themes/theme_base.dart';
9 import 'package:cake_wallet/utils/share_util.dart';
10 import 'package:cake_wallet/utils/show_pop_up.dart';
11 +import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
12 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
13 import 'package:flutter/material.dart';
14 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
@@ -13,24 +18,25 @@ import 'package:cake_wallet/generated/i18n.dart';
18 import 'package:flutter_mobx/flutter_mobx.dart';
19 import 'package:keyboard_actions/keyboard_actions.dart';
20 import 'package:mobx/mobx.dart';
21 +import 'package:shared_preferences/shared_preferences.dart';
22 +import 'package:cake_wallet/di.dart';
23
24 class AddressPage extends BasePage {
25 AddressPage({
26 required this.addressListViewModel,
20 - required this.walletViewModel})
21 - : _cryptoAmountFocus = FocusNode();
27 + required this.walletViewModel,
28 + required this.receiveOptionViewModel,
29 + }) : _cryptoAmountFocus = FocusNode();
30
31 final WalletAddressListViewModel addressListViewModel;
32 final DashboardViewModel walletViewModel;
33 + final ReceiveOptionViewModel receiveOptionViewModel;
34
35 final FocusNode _cryptoAmountFocus;
36
37 @override
29 - String get title => S.current.receive;
30 -
31 - @override
32 - Color get backgroundLightColor => currentTheme.type == ThemeType.bright
33 - ? Colors.transparent : Colors.white;
38 + Color get backgroundLightColor =>
39 + currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
40
41 @override
42 Color get backgroundDarkColor => Colors.transparent;
@@ -38,11 +44,15 @@ class AddressPage extends BasePage {
44 @override
45 bool get resizeToAvoidBottomInset => false;
46
47 + bool effectsInstalled = false;
48 +
49 @override
50 Widget leading(BuildContext context) {
43 - final _backButton = Icon(Icons.arrow_back_ios,
44 - color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
45 - size: 16,);
51 + final _backButton = Icon(
52 + Icons.arrow_back_ios,
53 + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
54 + size: 16,
55 + );
56
57 return SizedBox(
58 height: 37,
@@ -61,16 +71,8 @@ class AddressPage extends BasePage {
71 }
72
73 @override
64 - Widget middle(BuildContext context) {
65 - return Text(
66 - title,
67 - style: TextStyle(
68 - fontSize: 18.0,
69 - fontWeight: FontWeight.bold,
70 - fontFamily: 'Lato',
71 - color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!),
72 - );
73 - }
74 + Widget middle(BuildContext context) =>
75 + PresentReceiveOptionPicker(receiveOptionViewModel: receiveOptionViewModel);
76
77 @override
78 Widget Function(BuildContext, Widget) get rootWrapper =>
@@ -85,53 +87,56 @@ class AddressPage extends BasePage {
87
88 @override
89 Widget? trailing(BuildContext context) {
88 - final shareImage =
89 - Image.asset('assets/images/share.png',
90 + final shareImage = Image.asset('assets/images/share.png',
91 color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!);
92
92 - return !addressListViewModel.hasAddressList ? Material(
93 - color: Colors.transparent,
94 - child: IconButton(
95 - padding: EdgeInsets.zero,
96 - constraints: BoxConstraints(),
97 - highlightColor: Colors.transparent,
98 - splashColor: Colors.transparent,
99 - iconSize: 25,
100 - onPressed: () {
101 - ShareUtil.share(
102 - text: addressListViewModel.address.address,
103 - context: context,
104 - );
105 - },
106 - icon: shareImage,
107 - ),
108 - ) : null;
93 + return !addressListViewModel.hasAddressList
94 + ? Material(
95 + color: Colors.transparent,
96 + child: IconButton(
97 + padding: EdgeInsets.zero,
98 + constraints: BoxConstraints(),
99 + highlightColor: Colors.transparent,
100 + splashColor: Colors.transparent,
101 + iconSize: 25,
102 + onPressed: () {
103 + ShareUtil.share(
104 + text: addressListViewModel.address.address,
105 + context: context,
106 + );
107 + },
108 + icon: shareImage,
109 + ),
110 + )
111 + : null;
112 }
113
114 @override
115 Widget body(BuildContext context) {
116 + _setEffects(context);
117 +
118 autorun((_) async {
114 - if (!walletViewModel.isOutdatedElectrumWallet
115 - || !walletViewModel.settingsStore.shouldShowReceiveWarning) {
119 + if (!walletViewModel.isOutdatedElectrumWallet ||
120 + !walletViewModel.settingsStore.shouldShowReceiveWarning) {
121 return;
122 }
123
124 await Future<void>.delayed(Duration(seconds: 1));
125 if (context.mounted) {
126 await showPopUp<void>(
122 - context: context,
123 - builder: (BuildContext context) {
124 - return AlertWithTwoActions(
125 - alertTitle: S.of(context).pre_seed_title,
126 - alertContent: S.of(context).outdated_electrum_wallet_receive_warning,
127 - leftButtonText: S.of(context).understand,
128 - actionLeftButton: () => Navigator.of(context).pop(),
129 - rightButtonText: S.of(context).do_not_show_me,
130 - actionRightButton: () {
131 - walletViewModel.settingsStore.setShouldShowReceiveWarning(false);
132 - Navigator.of(context).pop();
133 - });
134 - });
127 + context: context,
128 + builder: (BuildContext context) {
129 + return AlertWithTwoActions(
130 + alertTitle: S.of(context).pre_seed_title,
131 + alertContent: S.of(context).outdated_electrum_wallet_receive_warning,
132 + leftButtonText: S.of(context).understand,
133 + actionLeftButton: () => Navigator.of(context).pop(),
134 + rightButtonText: S.of(context).do_not_show_me,
135 + actionRightButton: () {
136 + walletViewModel.settingsStore.setShouldShowReceiveWarning(false);
137 + Navigator.of(context).pop();
138 + });
139 + });
140 }
141 });
142
@@ -141,8 +146,7 @@ class AddressPage extends BasePage {
146 tapOutsideToDismiss: true,
147 config: KeyboardActionsConfig(
148 keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
144 - keyboardBarColor:
145 - Theme.of(context).accentTextTheme!.bodyText1!.backgroundColor!,
149 + keyboardBarColor: Theme.of(context).accentTextTheme.bodyText1!.backgroundColor!,
150 nextFocus: false,
151 actions: [
152 KeyboardActionsItem(
@@ -154,29 +158,25 @@ class AddressPage extends BasePage {
158 padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
159 child: Column(
160 children: <Widget>[
157 - Expanded(
158 - child: Observer(builder: (_) => QRWidget(
159 - addressListViewModel: addressListViewModel,
160 - amountTextFieldFocusNode: _cryptoAmountFocus,
161 - isAmountFieldShow: !addressListViewModel.hasAccounts,
162 - isLight: walletViewModel.settingsStore.currentTheme.type == ThemeType.light))
161 + Expanded(
162 + child: Observer(builder: (_) => QRWidget(
163 + addressListViewModel: addressListViewModel,
164 + amountTextFieldFocusNode: _cryptoAmountFocus,
165 + isAmountFieldShow: !addressListViewModel.hasAccounts,
166 + isLight: walletViewModel.settingsStore.currentTheme.type == ThemeType.light))
167 ),
168 Observer(builder: (_) {
169 return addressListViewModel.hasAddressList
170 ? GestureDetector(
167 - onTap: () =>
168 - Navigator.of(context).pushNamed(Routes.receive),
171 + onTap: () => Navigator.of(context).pushNamed(Routes.receive),
172 child: Container(
173 height: 50,
174 padding: EdgeInsets.only(left: 24, right: 12),
175 alignment: Alignment.center,
176 decoration: BoxDecoration(
174 - borderRadius:
175 - BorderRadius.all(Radius.circular(25)),
177 + borderRadius: BorderRadius.all(Radius.circular(25)),
178 border: Border.all(
177 - color:
178 - Theme.of(context).textTheme!.subtitle1!.color!,
179 - width: 1),
179 + color: Theme.of(context).textTheme.subtitle1!.color!, width: 1),
180 color: Theme.of(context).buttonColor),
181 child: Row(
182 mainAxisSize: MainAxisSize.max,
@@ -185,42 +185,79 @@ class AddressPage extends BasePage {
185 Observer(
186 builder: (_) => Text(
187 addressListViewModel.hasAccounts
188 - ? S
189 - .of(context)
190 - .accounts_subaddresses
188 + ? S.of(context).accounts_subaddresses
189 : S.of(context).addresses,
190 style: TextStyle(
191 fontSize: 14,
192 fontWeight: FontWeight.w500,
193 color: Theme.of(context)
196 - .accentTextTheme!
194 + .accentTextTheme
195 .headline2!
196 .backgroundColor!),
197 )),
198 Icon(
199 Icons.arrow_forward_ios,
200 size: 14,
203 - color: Theme.of(context)
204 - .accentTextTheme!
205 - .headline2!
206 - .backgroundColor!,
201 + color:
202 + Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
203 )
204 ],
205 ),
206 ),
207 )
212 - : Text(
213 - S.of(context).electrum_address_disclaimer,
208 + : Text(S.of(context).electrum_address_disclaimer,
209 textAlign: TextAlign.center,
210 style: TextStyle(
211 fontSize: 15,
217 - color: Theme.of(context)
218 - .accentTextTheme!
219 - .headline3!
220 - .backgroundColor!));
212 + color: Theme.of(context).accentTextTheme.headline3!.backgroundColor!));
213 })
214 ],
215 ),
216 ));
217 }
218 +
219 + void _setEffects(BuildContext context) {
220 + if (effectsInstalled) {
221 + return;
222 + }
223 +
224 + reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
225 + Navigator.pop(context);
226 + switch (option) {
227 + case ReceivePageOption.anonPayInvoice:
228 + Navigator.pushReplacementNamed(
229 + context,
230 + Routes.anonPayInvoicePage,
231 + arguments: [addressListViewModel.address.address, option],
232 + );
233 + break;
234 + case ReceivePageOption.anonPayDonationLink:
235 + final sharedPreferences = getIt.get<SharedPreferences>();
236 + final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
237 + final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
238 +
239 + if (clearnetUrl != null && onionUrl != null) {
240 + Navigator.pushReplacementNamed(
241 + context,
242 + Routes.anonPayReceivePage,
243 + arguments: AnonpayDonationLinkInfo(
244 + clearnetUrl: clearnetUrl,
245 + onionUrl: onionUrl,
246 + address: addressListViewModel.address.address,
247 + ),
248 + );
249 + } else {
250 + Navigator.pushReplacementNamed(
251 + context,
252 + Routes.anonPayInvoicePage,
253 + arguments: [addressListViewModel.address.address, option],
254 + );
255 + }
256 + break;
257 + default:
258 + }
259 + });
260 +
261 + effectsInstalled = true;
262 + }
263 }
lib/src/screens/dashboard/widgets/anonpay_transaction_row.dart new
+64
@@ -0,0 +1,64 @@
1 +import 'package:flutter/material.dart';
2 +
3 +class AnonpayTransactionRow extends StatelessWidget {
4 + AnonpayTransactionRow({
5 + required this.provider,
6 + required this.createdAt,
7 + required this.currency,
8 + required this.onTap,
9 + required this.amount,
10 + });
11 +
12 + final VoidCallback? onTap;
13 + final String provider;
14 + final String createdAt;
15 + final String amount;
16 + final String currency;
17 +
18 + @override
19 + Widget build(BuildContext context) {
20 + return InkWell(
21 + onTap: onTap,
22 + child: Container(
23 + padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
24 + color: Colors.transparent,
25 + child: Row(
26 + mainAxisSize: MainAxisSize.max,
27 + crossAxisAlignment: CrossAxisAlignment.center,
28 + children: [
29 + _getImage(),
30 + SizedBox(width: 12),
31 + Expanded(
32 + child: Column(
33 + mainAxisSize: MainAxisSize.min,
34 + children: [
35 + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
36 + Text(provider,
37 + style: TextStyle(
38 + fontSize: 16,
39 + fontWeight: FontWeight.w500,
40 + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!)),
41 + Text(amount + ' ' + currency,
42 + style: TextStyle(
43 + fontSize: 16,
44 + fontWeight: FontWeight.w500,
45 + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!))
46 + ]),
47 + SizedBox(height: 5),
48 + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
49 + Text(createdAt,
50 + style: TextStyle(
51 + fontSize: 14,
52 + color: Theme.of(context).textTheme.overline!.backgroundColor!))
53 + ])
54 + ],
55 + ))
56 + ],
57 + ),
58 + ));
59 + }
60 +
61 + Widget _getImage() => ClipRRect(
62 + borderRadius: BorderRadius.circular(50),
63 + child: Image.asset('assets/images/trocador.png', width: 36, height: 36));
64 +}
lib/src/screens/dashboard/widgets/present_receive_option_picker.dart new
+140
@@ -0,0 +1,140 @@
1 +import 'package:cake_wallet/palette.dart';
2 +import 'package:cake_wallet/src/screens/ionia/widgets/rounded_checkbox.dart';
3 +import 'package:cake_wallet/src/widgets/alert_background.dart';
4 +import 'package:cake_wallet/typography.dart';
5 +import 'package:cake_wallet/utils/show_pop_up.dart';
6 +import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
7 +import 'package:flutter/material.dart';
8 +import 'package:flutter_mobx/flutter_mobx.dart';
9 +import 'package:cake_wallet/generated/i18n.dart';
10 +
11 +class PresentReceiveOptionPicker extends StatelessWidget {
12 + PresentReceiveOptionPicker({required this.receiveOptionViewModel});
13 +
14 + final ReceiveOptionViewModel receiveOptionViewModel;
15 +
16 + @override
17 + Widget build(BuildContext context) {
18 + final arrowBottom =
19 + Image.asset('assets/images/arrow_bottom_purple_icon.png', color: Colors.white, height: 6);
20 +
21 + return TextButton(
22 + onPressed: () => _showPicker(context),
23 + style: ButtonStyle(
24 + padding: MaterialStateProperty.all(EdgeInsets.zero),
25 + splashFactory: NoSplash.splashFactory,
26 + foregroundColor: MaterialStateProperty.all(Colors.transparent),
27 + overlayColor: MaterialStateProperty.all(Colors.transparent),
28 + ),
29 + child: Row(
30 + mainAxisSize: MainAxisSize.min,
31 + crossAxisAlignment: CrossAxisAlignment.start,
32 + children: <Widget>[
33 + Column(
34 + crossAxisAlignment: CrossAxisAlignment.center,
35 + mainAxisSize: MainAxisSize.min,
36 + children: <Widget>[
37 + Text(
38 + S.current.receive,
39 + style: TextStyle(
40 + fontSize: 18.0,
41 + fontWeight: FontWeight.bold,
42 + fontFamily: 'Lato',
43 + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!),
44 + ),
45 + Observer(
46 + builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(),
47 + style: TextStyle(
48 + fontSize: 10.0,
49 + fontWeight: FontWeight.w500,
50 + color: Theme.of(context).textTheme.headline5!.color!)))
51 + ],
52 + ),
53 + SizedBox(width: 5),
54 + Padding(
55 + padding: EdgeInsets.only(top: 12),
56 + child: arrowBottom,
57 + )
58 + ],
59 + ),
60 + );
61 + }
62 +
63 + void _showPicker(BuildContext context) async {
64 + await showPopUp<void>(
65 + builder: (BuildContext popUpContext) => Scaffold(
66 + resizeToAvoidBottomInset: false,
67 + backgroundColor: Colors.transparent,
68 + body: AlertBackground(
69 + child: Column(
70 + mainAxisSize: MainAxisSize.min,
71 + mainAxisAlignment: MainAxisAlignment.center,
72 + children: [
73 + Spacer(),
74 + Container(
75 + margin: EdgeInsets.symmetric(horizontal: 24),
76 + decoration: BoxDecoration(
77 + borderRadius: BorderRadius.circular(30),
78 + color: Theme.of(context).backgroundColor,
79 + ),
80 + child: Padding(
81 + padding: const EdgeInsets.only(top: 24, bottom: 24),
82 + child: (ListView.separated(
83 + padding: EdgeInsets.zero,
84 + shrinkWrap: true,
85 + itemCount: receiveOptionViewModel.options.length,
86 + itemBuilder: (_, index) {
87 + final option = receiveOptionViewModel.options[index];
88 + return InkWell(
89 + onTap: () => receiveOptionViewModel.selectReceiveOption(option),
90 + child: Padding(
91 + padding: const EdgeInsets.only(left: 24, right: 24),
92 + child: Observer(builder: (_) {
93 + final value = receiveOptionViewModel.selectedReceiveOption;
94 +
95 + return Row(
96 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
97 + children: [
98 + Text(option.toString(),
99 + textAlign: TextAlign.left,
100 + style: textSmall(
101 + color: Theme.of(context).primaryTextTheme.headline6!.color!,
102 + ).copyWith(
103 + fontWeight:
104 + value == option ? FontWeight.w800 : FontWeight.w500,
105 + )),
106 + RoundedCheckbox(
107 + value: value == option,
108 + )
109 + ],
110 + );
111 + }),
112 + ),
113 + );
114 + },
115 + separatorBuilder: (_, index) => SizedBox(height: 30),
116 + )),
117 + ),
118 + ),
119 + Spacer(),
120 + Container(
121 + margin: EdgeInsets.only(bottom: 40),
122 + child: InkWell(
123 + onTap: () => Navigator.pop(context),
124 + child: CircleAvatar(
125 + child: Icon(
126 + Icons.close,
127 + color: Palette.darkBlueCraiola,
128 + ),
129 + backgroundColor: Colors.white,
130 + ),
131 + ),
132 + )
133 + ],
134 + ),
135 + ),
136 + ),
137 + context: context,
138 + );
139 + }
140 +}
lib/src/screens/dashboard/widgets/transactions_page.dart
+81 -76
@@ -1,5 +1,8 @@
1 +import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
2 import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
3 +import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
4 import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
5 +import 'package:cw_core/crypto_currency.dart';
6 import 'package:flutter/material.dart';
7 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
8 import 'package:flutter_mobx/flutter_mobx.dart';
@@ -22,102 +25,104 @@ class TransactionsPage extends StatelessWidget {
25 @override
26 Widget build(BuildContext context) {
27 return Container(
25 - padding: EdgeInsets.only(
26 - top: 24,
27 - bottom: 24
28 - ),
28 + padding: EdgeInsets.only(top: 24, bottom: 24),
29 child: Column(
30 children: <Widget>[
31 HeaderRow(dashboardViewModel: dashboardViewModel),
32 - Expanded(
33 - child: Observer(
34 - builder: (_) {
35 - final items = dashboardViewModel.items;
32 + Expanded(child: Observer(builder: (_) {
33 + final items = dashboardViewModel.items;
34
37 - return items?.isNotEmpty ?? false
38 - ? ListView.builder(
39 - itemCount: items.length,
40 - itemBuilder: (context, index) {
35 + return items.isNotEmpty
36 + ? ListView.builder(
37 + itemCount: items.length,
38 + itemBuilder: (context, index) {
39 + final item = items[index];
40
42 - final item = items[index];
41 + if (item is DateSectionItem) {
42 + return DateSectionRaw(date: item.date);
43 + }
44
44 - if (item is DateSectionItem) {
45 - return DateSectionRaw(date: item.date);
46 - }
45 + if (item is TransactionListItem) {
46 + final transaction = item.transaction;
47
48 - if (item is TransactionListItem) {
49 - final transaction = item.transaction;
48 + return Observer(
49 + builder: (_) => TransactionRow(
50 + onTap: () => Navigator.of(context)
51 + .pushNamed(Routes.transactionDetails, arguments: transaction),
52 + direction: transaction.direction,
53 + formattedDate: DateFormat('HH:mm').format(transaction.date),
54 + formattedAmount: item.formattedCryptoAmount,
55 + formattedFiatAmount:
56 + dashboardViewModel.balanceViewModel.isFiatDisabled
57 + ? ''
58 + : item.formattedFiatAmount,
59 + isPending: transaction.isPending,
60 + title: item.formattedTitle + item.formattedStatus));
61 + }
62
51 - return Observer(
52 - builder: (_) => TransactionRow(
53 - onTap: () => Navigator.of(context).pushNamed(
54 - Routes.transactionDetails,
55 - arguments: transaction),
56 - direction: transaction.direction,
57 - formattedDate: DateFormat('HH:mm')
58 - .format(transaction.date),
59 - formattedAmount: item.formattedCryptoAmount,
60 - formattedFiatAmount:
61 - dashboardViewModel.balanceViewModel.isFiatDisabled
62 - ? '' : item.formattedFiatAmount,
63 - isPending: transaction.isPending,
64 - title: item.formattedTitle + item.formattedStatus));
65 - }
63 + if (item is AnonpayTransactionListItem) {
64 + final transactionInfo = item.transaction;
65
67 - if (item is TradeListItem) {
68 - final trade = item.trade;
66 + return AnonpayTransactionRow(
67 + onTap: () => Navigator.of(context)
68 + .pushNamed(Routes.anonPayDetailsPage, arguments: transactionInfo),
69 + currency: transactionInfo.fiatAmount != null
70 + ? transactionInfo.fiatEquiv ?? ''
71 + : CryptoCurrency.fromFullName(transactionInfo.coinTo)
72 + .name
73 + .toUpperCase(),
74 + provider: transactionInfo.provider,
75 + amount: transactionInfo.fiatAmount?.toString() ??
76 + (transactionInfo.amountTo?.toString() ?? ''),
77 + createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
78 + );
79 + }
80
70 - return Observer(builder: (_) => TradeRow(
71 - onTap: () => Navigator.of(context).pushNamed(
72 - Routes.tradeDetails,
73 - arguments: trade),
74 - provider: trade.provider,
75 - from: trade.from,
76 - to: trade.to,
77 - createdAtFormattedDate:
78 - trade.createdAt != null
79 - ? DateFormat('HH:mm').format(trade.createdAt!)
80 - : null,
81 - formattedAmount: item.tradeFormattedAmount
82 - ));
83 - }
81 + if (item is TradeListItem) {
82 + final trade = item.trade;
83
85 - if (item is OrderListItem) {
86 - final order = item.order;
84 + return Observer(
85 + builder: (_) => TradeRow(
86 + onTap: () => Navigator.of(context)
87 + .pushNamed(Routes.tradeDetails, arguments: trade),
88 + provider: trade.provider,
89 + from: trade.from,
90 + to: trade.to,
91 + createdAtFormattedDate: trade.createdAt != null
92 + ? DateFormat('HH:mm').format(trade.createdAt!)
93 + : null,
94 + formattedAmount: item.tradeFormattedAmount));
95 + }
96
88 - return Observer(builder: (_) => OrderRow(
89 - onTap: () => Navigator.of(context).pushNamed(
90 - Routes.orderDetails,
91 - arguments: order),
92 - provider: order.provider,
93 - from: order.from!,
94 - to: order.to!,
95 - createdAtFormattedDate:
96 - DateFormat('HH:mm').format(order.createdAt),
97 - formattedAmount: item.orderFormattedAmount,
98 - ));
99 - }
97 + if (item is OrderListItem) {
98 + final order = item.order;
99
101 - return Container(
102 - color: Colors.transparent,
103 - height: 1);
100 + return Observer(
101 + builder: (_) => OrderRow(
102 + onTap: () => Navigator.of(context)
103 + .pushNamed(Routes.orderDetails, arguments: order),
104 + provider: order.provider,
105 + from: order.from!,
106 + to: order.to!,
107 + createdAtFormattedDate:
108 + DateFormat('HH:mm').format(order.createdAt),
109 + formattedAmount: item.orderFormattedAmount,
110 + ));
111 }
105 - )
106 - : Center(
112 +
113 + return Container(color: Colors.transparent, height: 1);
114 + })
115 + : Center(
116 child: Text(
117 S.of(context).placeholder_transactions,
118 style: TextStyle(
110 - fontSize: 14,
111 - color: Theme.of(context).primaryTextTheme!
112 - .overline!.decorationColor!
113 - ),
119 + fontSize: 14,
120 + color: Theme.of(context).primaryTextTheme.overline!.decorationColor!),
121 ),
122 );
116 - }
117 - )
118 - )
123 + }))
124 ],
125 ),
126 );
127 }
123 -}
\ No newline at end of file
128 +}
lib/src/screens/exchange/widgets/currency_picker.dart
+9 -8
@@ -2,6 +2,7 @@ import 'dart:ui';
2 import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker_item_widget.dart';
3 import 'package:cake_wallet/src/screens/exchange/widgets/picker_item.dart';
4 import 'package:cake_wallet/src/widgets/alert_close_button.dart';
5 +import 'package:cw_core/currency.dart';
6 import 'package:flutter/cupertino.dart';
7 import 'package:flutter/material.dart';
8 import 'package:cw_core/crypto_currency.dart';
@@ -20,9 +21,9 @@ class CurrencyPicker extends StatefulWidget {
21 this.isConvertFrom = false});
22
23 int selectedAtIndex;
23 - final List<CryptoCurrency> items;
24 + final List<Currency> items;
25 final String? title;
25 - final Function(CryptoCurrency) onItemSelected;
26 + final Function(Currency) onItemSelected;
27 final bool isMoneroWallet;
28 final bool isConvertFrom;
29 final String? hintText;
@@ -38,13 +39,13 @@ class CurrencyPickerState extends State<CurrencyPicker> {
39 subPickerItemsList = items,
40 appBarTextStyle =
41 TextStyle(fontSize: 20, fontFamily: 'Lato', backgroundColor: Colors.transparent, color: Colors.white),
41 - pickerItemsList = <PickerItem<CryptoCurrency>>[];
42 + pickerItemsList = <PickerItem<Currency>>[];
43
43 - List<PickerItem<CryptoCurrency>> pickerItemsList;
44 - List<CryptoCurrency> items;
44 + List<PickerItem<Currency>> pickerItemsList;
45 + List<Currency> items;
46 bool isSearchBarActive;
47 String textFieldValue;
47 - List<CryptoCurrency> subPickerItemsList;
48 + List<Currency> subPickerItemsList;
49 TextStyle appBarTextStyle;
50
51 void cleanSubPickerItemsList() => subPickerItemsList = items;
@@ -54,7 +55,7 @@ class CurrencyPickerState extends State<CurrencyPicker> {
55 if (subString.isNotEmpty) {
56 subPickerItemsList = items
57 .where((element) =>
57 - (element.title != null ? element.title.toLowerCase().contains(subString.toLowerCase()) : false) ||
58 + element.name.toLowerCase().contains(subString.toLowerCase()) ||
59 (element.tag != null ? element.tag!.toLowerCase().contains(subString.toLowerCase()) : false) ||
60 (element.fullName != null ? element.fullName!.toLowerCase().contains(subString.toLowerCase()) : false))
61 .toList();
@@ -139,7 +140,7 @@ class CurrencyPickerState extends State<CurrencyPicker> {
140 AspectRatio(
141 aspectRatio: 6,
142 child: PickerItemWidget(
142 - title: items[widget.selectedAtIndex].title,
143 + title: items[widget.selectedAtIndex].name,
144 iconPath: items[widget.selectedAtIndex].iconPath,
145 isSelected: true,
146 tag: items[widget.selectedAtIndex].tag,
lib/src/screens/exchange/widgets/currency_picker_item_widget.dart
+2 -2
@@ -32,12 +32,12 @@ class PickerItemWidget extends StatelessWidget {
32 width: 20.0,
33 ),
34 ),
35 - const SizedBox(width: 6),
35 + const SizedBox(width: 12),
36 Expanded(
37 child: Row(
38 children: [
39 Text(
40 - title,
40 + title.toUpperCase(),
41 style: TextStyle(
42 color: isSelected ? Palette.blueCraiola : Theme.of(context).primaryTextTheme!.headline6!.color!,
43 fontSize: isSelected ? 16 : 14.0,
lib/src/screens/exchange/widgets/currency_picker_widget.dart
+4 -5
@@ -1,6 +1,5 @@
1 +import 'package:cw_core/currency.dart';
2 import 'package:flutter/material.dart';
2 -import 'package:cw_core/crypto_currency.dart';
3 -import 'picker_item.dart';
3 import 'currency_picker_item_widget.dart';
4
5 class CurrencyPickerWidget extends StatelessWidget {
@@ -14,7 +13,7 @@ class CurrencyPickerWidget extends StatelessWidget {
13 final int crossAxisCount;
14 final int selectedAtIndex;
15 final Function pickListItem;
17 - final List<CryptoCurrency> pickerItemsList;
16 + final List<Currency> pickerItemsList;
17
18 final ScrollController _scrollController = ScrollController();
19
@@ -39,8 +38,8 @@ class CurrencyPickerWidget extends StatelessWidget {
38 onTap: () {
39 pickListItem(index);
40 },
42 - title: pickerItemsList[index].title,
43 - iconPath: pickerItemsList[index].iconPath,
41 + title: pickerItemsList[index].name,
42 + iconPath: pickerItemsList[index].iconPath,
43 tag: pickerItemsList[index].tag,
44 );
45 }),
lib/src/screens/exchange/widgets/exchange_card.dart
+3 -2
@@ -4,6 +4,7 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
4 import 'package:cake_wallet/utils/show_bar.dart';
5 import 'package:cake_wallet/utils/show_pop_up.dart';
6 import 'package:cake_wallet/utils/payment_request.dart';
7 +import 'package:cw_core/currency.dart';
8 import 'package:flutter/services.dart';
9 import 'package:flutter/material.dart';
10 import 'package:cake_wallet/generated/i18n.dart';
@@ -501,9 +502,9 @@ class ExchangeCardState extends State<ExchangeCard> {
502 hintText: S.of(context).search_currency,
503 isMoneroWallet: _isMoneroWallet,
504 isConvertFrom: widget.hasRefundAddress,
504 - onItemSelected: (CryptoCurrency item) =>
505 + onItemSelected: (Currency item) =>
506 widget.onCurrencySelected != null
506 - ? widget.onCurrencySelected(item)
507 + ? widget.onCurrencySelected(item as CryptoCurrency)
508 : null),
509 context: context);
510 }
lib/src/screens/receive/anonpay_invoice_page.dart new
+222
@@ -0,0 +1,222 @@
1 +import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
2 +import 'package:cake_wallet/core/execution_state.dart';
3 +import 'package:cake_wallet/di.dart';
4 +import 'package:cake_wallet/entities/preferences_key.dart';
5 +import 'package:cake_wallet/entities/receive_page_option.dart';
6 +import 'package:cake_wallet/routes.dart';
7 +import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
8 +import 'package:cake_wallet/src/screens/receive/widgets/anonpay_input_form.dart';
9 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
10 +import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
11 +import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
12 +import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
13 +import 'package:flutter/material.dart';
14 +import 'package:flutter_mobx/flutter_mobx.dart';
15 +import 'package:keyboard_actions/keyboard_actions.dart';
16 +import 'package:cake_wallet/src/screens/base_page.dart';
17 +import 'package:cake_wallet/src/widgets/trail_button.dart';
18 +import 'package:cake_wallet/utils/show_pop_up.dart';
19 +import 'package:cake_wallet/generated/i18n.dart';
20 +import 'package:cake_wallet/src/widgets/primary_button.dart';
21 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
22 +import 'package:mobx/mobx.dart';
23 +import 'package:shared_preferences/shared_preferences.dart';
24 +
25 +class AnonPayInvoicePage extends BasePage {
26 + AnonPayInvoicePage(
27 + this.anonInvoicePageViewModel,
28 + this.receiveOptionViewModel,
29 + ) : _amountFocusNode = FocusNode() {
30 + _nameController.text = anonInvoicePageViewModel.receipientName;
31 + _descriptionController.text = anonInvoicePageViewModel.description;
32 + _emailController.text = anonInvoicePageViewModel.receipientEmail;
33 + }
34 +
35 + final _nameController = TextEditingController();
36 + final _emailController = TextEditingController();
37 + final _descriptionController = TextEditingController();
38 + final _amountController = TextEditingController();
39 + final FocusNode _amountFocusNode;
40 +
41 + final AnonInvoicePageViewModel anonInvoicePageViewModel;
42 + final ReceiveOptionViewModel receiveOptionViewModel;
43 + final _formKey = GlobalKey<FormState>();
44 +
45 + bool effectsInstalled = false;
46 + @override
47 + Color get titleColor => Colors.white;
48 +
49 + @override
50 + bool get resizeToAvoidBottomInset => false;
51 +
52 + @override
53 + bool get extendBodyBehindAppBar => true;
54 +
55 + @override
56 + AppBarStyle get appBarStyle => AppBarStyle.transparent;
57 +
58 + @override
59 + Widget middle(BuildContext context) =>
60 + PresentReceiveOptionPicker(receiveOptionViewModel: receiveOptionViewModel);
61 +
62 + @override
63 + Widget trailing(BuildContext context) => TrailButton(
64 + caption: S.of(context).clear,
65 + onPressed: () {
66 + _formKey.currentState?.reset();
67 + anonInvoicePageViewModel.reset();
68 + });
69 +
70 + @override
71 + Widget body(BuildContext context) {
72 + WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context));
73 +
74 + return KeyboardActions(
75 + disableScroll: true,
76 + config: KeyboardActionsConfig(
77 + keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
78 + keyboardBarColor: Theme.of(context).accentTextTheme.bodyText1!.backgroundColor!,
79 + nextFocus: false,
80 + actions: [
81 + KeyboardActionsItem(
82 + focusNode: _amountFocusNode,
83 + toolbarButtons: [(_) => KeyboardDoneButton()],
84 + ),
85 + ]),
86 + child: Container(
87 + color: Theme.of(context).backgroundColor,
88 + child: ScrollableWithBottomSection(
89 + contentPadding: EdgeInsets.only(bottom: 24),
90 + content: Container(
91 + decoration: BoxDecoration(
92 + borderRadius: BorderRadius.only(
93 + bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
94 + gradient: LinearGradient(
95 + colors: [
96 + Theme.of(context).primaryTextTheme.subtitle2!.color!,
97 + Theme.of(context).primaryTextTheme.subtitle2!.decorationColor!,
98 + ],
99 + begin: Alignment.topLeft,
100 + end: Alignment.bottomRight,
101 + ),
102 + ),
103 + child: Observer(builder: (_) {
104 + return Padding(
105 + padding: EdgeInsets.fromLTRB(24, 100, 24, 0),
106 + child: AnonInvoiceForm(
107 + nameController: _nameController,
108 + descriptionController: _descriptionController,
109 + amountController: _amountController,
110 + emailController: _emailController,
111 + depositAmountFocus: _amountFocusNode,
112 + formKey: _formKey,
113 + isInvoice: receiveOptionViewModel.selectedReceiveOption ==
114 + ReceivePageOption.anonPayInvoice,
115 + anonInvoicePageViewModel: anonInvoicePageViewModel,
116 + ),
117 + );
118 + }),
119 + ),
120 + bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
121 + bottomSection: Observer(builder: (_) {
122 + final isInvoice =
123 + receiveOptionViewModel.selectedReceiveOption == ReceivePageOption.anonPayInvoice;
124 + return Column(
125 + children: <Widget>[
126 + Padding(
127 + padding: EdgeInsets.only(bottom: 15),
128 + child: Center(
129 + child: Text(
130 + isInvoice
131 + ? S.of(context).anonpay_description("an invoice", "pay")
132 + : S.of(context).anonpay_description("a donation link", "donate"),
133 + textAlign: TextAlign.center,
134 + style: TextStyle(
135 + color: Theme.of(context).primaryTextTheme.headline1!.decorationColor!,
136 + fontWeight: FontWeight.w500,
137 + fontSize: 12),
138 + ),
139 + ),
140 + ),
141 + LoadingPrimaryButton(
142 + text:
143 + isInvoice ? S.of(context).create_invoice : S.of(context).create_donation_link,
144 + onPressed: () {
145 + anonInvoicePageViewModel.setRequestParams(
146 + inputAmount: _amountController.text,
147 + inputName: _nameController.text,
148 + inputEmail: _emailController.text,
149 + inputDescription: _descriptionController.text,
150 + );
151 + if (anonInvoicePageViewModel.receipientEmail.isNotEmpty &&
152 + _formKey.currentState != null &&
153 + !_formKey.currentState!.validate()) {
154 + return;
155 + }
156 + if (isInvoice) {
157 + anonInvoicePageViewModel.createInvoice();
158 + } else {
159 + anonInvoicePageViewModel.generateDonationLink();
160 + }
161 + },
162 + color: Theme.of(context).accentTextTheme.bodyText1!.color!,
163 + textColor: Colors.white,
164 + isLoading: anonInvoicePageViewModel.state is IsExecutingState,
165 + ),
166 + ],
167 + );
168 + }),
169 + ),
170 + ),
171 + );
172 + }
173 +
174 + void _setReactions(BuildContext context) {
175 + if (effectsInstalled) {
176 + return;
177 + }
178 +
179 + reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
180 + Navigator.pop(context);
181 + switch (option) {
182 + case ReceivePageOption.mainnet:
183 + Navigator.popAndPushNamed(context, Routes.addressPage);
184 + break;
185 + case ReceivePageOption.anonPayDonationLink:
186 + final sharedPreferences = getIt.get<SharedPreferences>();
187 + final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
188 + final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
189 +
190 + if (clearnetUrl != null && onionUrl != null) {
191 + Navigator.pushReplacementNamed(context, Routes.anonPayReceivePage,
192 + arguments: AnonpayDonationLinkInfo(
193 + clearnetUrl: clearnetUrl,
194 + onionUrl: onionUrl,
195 + address: anonInvoicePageViewModel.address,
196 + ));
197 + }
198 + break;
199 + default:
200 + }
201 + });
202 +
203 + reaction((_) => anonInvoicePageViewModel.state, (ExecutionState state) {
204 + if (state is ExecutedSuccessfullyState) {
205 + Navigator.pushNamed(context, Routes.anonPayReceivePage, arguments: state.payload);
206 + }
207 + if (state is FailureState) {
208 + showPopUp<void>(
209 + context: context,
210 + builder: (BuildContext context) {
211 + return AlertWithOneAction(
212 + alertTitle: S.of(context).error,
213 + alertContent: state.error.toString(),
214 + buttonText: S.of(context).ok,
215 + buttonAction: () => Navigator.of(context).pop());
216 + });
217 + }
218 + });
219 +
220 + effectsInstalled = true;
221 + }
222 +}
lib/src/screens/receive/anonpay_receive_page.dart new
+181
@@ -0,0 +1,181 @@
1 +import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
2 +import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
3 +import 'package:cake_wallet/entities/receive_page_option.dart';
4 +import 'package:cake_wallet/generated/i18n.dart';
5 +import 'package:cake_wallet/routes.dart';
6 +import 'package:cake_wallet/src/screens/base_page.dart';
7 +import 'package:cake_wallet/src/screens/receive/widgets/anonpay_status_section.dart';
8 +import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
9 +import 'package:cake_wallet/src/screens/receive/widgets/copy_link_item.dart';
10 +import 'package:cake_wallet/themes/theme_base.dart';
11 +import 'package:device_display_brightness/device_display_brightness.dart';
12 +import 'package:flutter/material.dart';
13 +import 'package:qr_flutter/qr_flutter.dart' as qr;
14 +
15 +class AnonPayReceivePage extends BasePage {
16 + final AnonpayInfoBase invoiceInfo;
17 +
18 + AnonPayReceivePage({required this.invoiceInfo});
19 +
20 + @override
21 + String get title => S.current.receive;
22 +
23 + @override
24 + Color get backgroundLightColor =>
25 + currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
26 +
27 + @override
28 + Color get backgroundDarkColor => Colors.transparent;
29 +
30 + @override
31 + bool get resizeToAvoidBottomInset => false;
32 +
33 + @override
34 + Widget leading(BuildContext context) {
35 + final _backButton = Icon(
36 + Icons.arrow_back_ios,
37 + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
38 + size: 16,
39 + );
40 +
41 + return SizedBox(
42 + height: 37,
43 + width: 37,
44 + child: ButtonTheme(
45 + minWidth: double.minPositive,
46 + child: TextButton(
47 + onPressed: () =>
48 + Navigator.pushNamedAndRemoveUntil(context, Routes.dashboard, (route) => false),
49 + child: _backButton),
50 + ),
51 + );
52 + }
53 +
54 + @override
55 + Widget middle(BuildContext context) {
56 + return Column(
57 + children: [
58 + Text(
59 + title,
60 + style: TextStyle(
61 + fontSize: 18.0,
62 + fontWeight: FontWeight.bold,
63 + fontFamily: 'Lato',
64 + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!),
65 + ),
66 + Text(
67 + invoiceInfo is AnonpayInvoiceInfo
68 + ? ReceivePageOption.anonPayInvoice.toString()
69 + : ReceivePageOption.anonPayDonationLink.toString(),
70 + style: TextStyle(
71 + fontSize: 10.0,
72 + fontWeight: FontWeight.w500,
73 + color: Theme.of(context).textTheme.headline5!.color!),
74 + )
75 + ],
76 + );
77 + }
78 +
79 + @override
80 + Widget? trailing(BuildContext context) {
81 + if (invoiceInfo is AnonpayInvoiceInfo) {
82 + return null;
83 + }
84 +
85 + return Material(
86 + color: Colors.transparent,
87 + child: IconButton(
88 + onPressed: () => Navigator.popAndPushNamed(
89 + context,
90 + Routes.anonPayInvoicePage,
91 + arguments: [invoiceInfo.address, ReceivePageOption.anonPayDonationLink],
92 + ),
93 + icon: Icon(
94 + Icons.edit,
95 + color: Theme.of(context).accentTextTheme.caption!.color!,
96 + size: 22.0,
97 + ),
98 + ),
99 + );
100 + }
101 +
102 + @override
103 + Widget Function(BuildContext, Widget) get rootWrapper =>
104 + (BuildContext context, Widget scaffold) => Container(
105 + decoration: BoxDecoration(
106 + gradient: LinearGradient(colors: [
107 + Theme.of(context).accentColor,
108 + Theme.of(context).scaffoldBackgroundColor,
109 + Theme.of(context).primaryColor,
110 + ], begin: Alignment.topRight, end: Alignment.bottomLeft)),
111 + child: scaffold);
112 +
113 + @override
114 + Widget body(BuildContext context) {
115 + return SingleChildScrollView(
116 + child: Column(
117 + children: <Widget>[
118 + SizedBox(height: 24),
119 + if (invoiceInfo is AnonpayInvoiceInfo)
120 + AnonInvoiceStatusSection(invoiceInfo: invoiceInfo as AnonpayInvoiceInfo),
121 + Padding(
122 + padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
123 + child: ConstrainedBox(
124 + constraints: BoxConstraints(
125 + maxWidth: MediaQuery.of(context).size.width * 0.5,
126 + ),
127 + child: GestureDetector(
128 + onTap: () async {
129 + final double brightness = await DeviceDisplayBrightness.getBrightness();
130 +
131 + // ignore: unawaited_futures
132 + DeviceDisplayBrightness.setBrightness(1.0);
133 + await Navigator.pushNamed(
134 + context,
135 + Routes.fullscreenQR,
136 + arguments: {
137 + 'qrData': invoiceInfo.clearnetUrl,
138 + 'version': qr.QrVersions.auto,
139 + },
140 + );
141 + // ignore: unawaited_futures
142 + DeviceDisplayBrightness.setBrightness(brightness);
143 + },
144 + child: Hero(
145 + tag: Key(invoiceInfo.clearnetUrl),
146 + child: Center(
147 + child: AspectRatio(
148 + aspectRatio: 1.0,
149 + child: Container(
150 + padding: EdgeInsets.all(5),
151 + decoration: BoxDecoration(
152 + border: Border.all(
153 + width: 3,
154 + color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
155 + ),
156 + ),
157 + child: QrImage(
158 + data: invoiceInfo.clearnetUrl,
159 + version: qr.QrVersions.auto,
160 + ),
161 + ),
162 + ),
163 + ),
164 + ),
165 + ),
166 + ),
167 + ),
168 + SizedBox(height: 24),
169 + Column(
170 + children: [
171 + CopyLinkItem(url: invoiceInfo.clearnetUrl, title: S.of(context).clearnet_link),
172 + SizedBox(height: 16),
173 + CopyLinkItem(url: invoiceInfo.onionUrl, title: S.of(context).onion_link),
174 + ],
175 + ),
176 + SizedBox(height: 100),
177 + ],
178 + ),
179 + );
180 + }
181 +}
lib/src/screens/receive/fullscreen_qr_page.dart
+3 -3
@@ -4,10 +4,10 @@ import 'package:flutter/material.dart';
4 import 'package:cake_wallet/src/screens/base_page.dart';
5
6 class FullscreenQRPage extends BasePage {
7 - FullscreenQRPage({required this.qrData, required this.isLight});
7 + FullscreenQRPage({required this.qrData, int? this.version});
8
9 - final bool isLight;
9 final String qrData;
10 + final int? version;
11
12 @override
13 Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
@@ -71,7 +71,7 @@ class FullscreenQRPage extends BasePage {
71 padding: EdgeInsets.all(10),
72 decoration: BoxDecoration(
73 border: Border.all(width: 3, color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!)),
74 - child: QrImage(data: qrData),
74 + child: QrImage(data: qrData, version: version),
75 ),
76 ),
77 ),
lib/src/screens/receive/widgets/anonpay_currency_input_field.dart new
+153
@@ -0,0 +1,153 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
3 +import 'package:cw_core/currency.dart';
4 +import 'package:flutter/material.dart';
5 +import 'package:flutter/services.dart';
6 +
7 +class AnonpayCurrencyInputField extends StatelessWidget {
8 + const AnonpayCurrencyInputField(
9 + {super.key,
10 + required this.onTapPicker,
11 + required this.selectedCurrency,
12 + required this.focusNode,
13 + required this.controller,
14 + required this.minAmount,
15 + required this.maxAmount});
16 + final Function() onTapPicker;
17 + final Currency selectedCurrency;
18 + final FocusNode focusNode;
19 + final TextEditingController controller;
20 + final String minAmount;
21 + final String maxAmount;
22 + @override
23 + Widget build(BuildContext context) {
24 + final arrowBottomPurple = Image.asset(
25 + 'assets/images/arrow_bottom_purple_icon.png',
26 + color: Colors.white,
27 + height: 8,
28 + );
29 + return Column(
30 + children: [
31 + Container(
32 + decoration: BoxDecoration(
33 + border: Border(
34 + bottom: BorderSide(
35 + color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!,
36 + width: 1)),
37 + ),
38 + child: Padding(
39 + padding: EdgeInsets.only(top: 20),
40 + child: Row(
41 + children: [
42 + Container(
43 + padding: EdgeInsets.only(right: 8),
44 + height: 32,
45 + child: InkWell(
46 + onTap: onTapPicker,
47 + child: Row(
48 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
49 + mainAxisSize: MainAxisSize.min,
50 + children: <Widget>[
51 + Padding(
52 + padding: EdgeInsets.only(right: 5),
53 + child: arrowBottomPurple,
54 + ),
55 + Text(selectedCurrency.name.toUpperCase(),
56 + style: TextStyle(
57 + fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white))
58 + ]),
59 + ),
60 + ),
61 + selectedCurrency.tag != null
62 + ? Padding(
63 + padding: const EdgeInsets.only(right: 3.0),
64 + child: Container(
65 + height: 32,
66 + decoration: BoxDecoration(
67 + color: Theme.of(context).primaryTextTheme.headline4!.color!,
68 + borderRadius: BorderRadius.all(Radius.circular(6))),
69 + child: Center(
70 + child: Padding(
71 + padding: const EdgeInsets.all(6.0),
72 + child: Text(
73 + selectedCurrency.tag!,
74 + style: TextStyle(
75 + fontSize: 12,
76 + fontWeight: FontWeight.bold,
77 + color: Theme.of(context)
78 + .primaryTextTheme
79 + .headline4!
80 + .decorationColor!,
81 + ),
82 + ),
83 + ),
84 + ),
85 + ),
86 + )
87 + : Container(),
88 + Padding(
89 + padding: const EdgeInsets.only(right: 4.0),
90 + child: Text(':',
91 + style: TextStyle(
92 + fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)),
93 + ),
94 + Expanded(
95 + child: Row(
96 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
97 + children: [
98 + Flexible(
99 + child: BaseTextFormField(
100 + focusNode: focusNode,
101 + controller: controller,
102 + textInputAction: TextInputAction.next,
103 + enabled: true,
104 + textAlign: TextAlign.left,
105 + keyboardType:
106 + TextInputType.numberWithOptions(signed: false, decimal: true),
107 + inputFormatters: [
108 + FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
109 + ],
110 + hintText: '0.0000',
111 + borderColor: Colors.transparent,
112 + //widget.borderColor,
113 + textStyle: TextStyle(
114 + fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
115 + placeholderTextStyle: TextStyle(
116 + fontSize: 16,
117 + fontWeight: FontWeight.w600,
118 + color: Theme.of(context).accentTextTheme.headline1!.decorationColor!,
119 + ),
120 + validator: null,
121 + ),
122 + ),
123 + ],
124 + ),
125 + ),
126 + ],
127 + ),
128 + )),
129 + Container(
130 + height: 15,
131 + child: Row(
132 + mainAxisAlignment: MainAxisAlignment.start,
133 + children: <Widget>[
134 + Text(
135 + S.of(context).min_value(minAmount, selectedCurrency.toString()),
136 + style: TextStyle(
137 + fontSize: 10,
138 + height: 1.2,
139 + color: Theme.of(context).accentTextTheme.headline1!.decorationColor!),
140 + ),
141 + SizedBox(width: 10),
142 + Text(S.of(context).max_value(maxAmount, selectedCurrency.toString()),
143 + style: TextStyle(
144 + fontSize: 10,
145 + height: 1.2,
146 + color: Theme.of(context).accentTextTheme.headline1!.decorationColor!)),
147 + ],
148 + ),
149 + )
150 + ],
151 + );
152 + }
153 +}
lib/src/screens/receive/widgets/anonpay_input_form.dart new
+132
@@ -0,0 +1,132 @@
1 +import 'package:cake_wallet/core/email_validator.dart';
2 +import 'package:cake_wallet/generated/i18n.dart';
3 +import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
4 +import 'package:cake_wallet/src/screens/receive/widgets/anonpay_currency_input_field.dart';
5 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
6 +import 'package:cake_wallet/typography.dart';
7 +import 'package:cake_wallet/utils/show_pop_up.dart';
8 +import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
9 +import 'package:flutter/material.dart';
10 +import 'package:flutter_mobx/flutter_mobx.dart';
11 +
12 +class AnonInvoiceForm extends StatelessWidget {
13 + AnonInvoiceForm({
14 + super.key,
15 + required this.formKey,
16 + required this.anonInvoicePageViewModel,
17 + required this.isInvoice,
18 + required this.amountController,
19 + required this.nameController,
20 + required this.emailController,
21 + required this.descriptionController,
22 + required this.depositAmountFocus,
23 + }) : _nameFocusNode = FocusNode(),
24 + _emailFocusNode = FocusNode(),
25 + _descriptionFocusNode = FocusNode();
26 +
27 + final TextEditingController amountController;
28 + final TextEditingController nameController;
29 + final TextEditingController emailController;
30 + final TextEditingController descriptionController;
31 + final AnonInvoicePageViewModel anonInvoicePageViewModel;
32 + final FocusNode depositAmountFocus;
33 + final FocusNode _nameFocusNode;
34 + final FocusNode _emailFocusNode;
35 + final FocusNode _descriptionFocusNode;
36 + final GlobalKey<FormState> formKey;
37 + final bool isInvoice;
38 +
39 + @override
40 + Widget build(BuildContext context) {
41 + return Form(
42 + key: formKey,
43 + child: Column(
44 + crossAxisAlignment: CrossAxisAlignment.start,
45 + children: <Widget>[
46 + Text(
47 + isInvoice ? S.of(context).invoice_details : S.of(context).donation_link_details,
48 + style: textMediumSemiBold(),
49 + ),
50 + if (isInvoice)
51 + Observer(builder: (_) {
52 + return AnonpayCurrencyInputField(
53 + onTapPicker: () => _presentPicker(context),
54 + controller: amountController,
55 + focusNode: depositAmountFocus,
56 + maxAmount: anonInvoicePageViewModel.maximum?.toString() ?? '...',
57 + minAmount: anonInvoicePageViewModel.minimum?.toString() ?? '...',
58 + selectedCurrency: anonInvoicePageViewModel.selectedCurrency,
59 + );
60 + }),
61 + SizedBox(
62 + height: 24,
63 + ),
64 + BaseTextFormField(
65 + controller: nameController,
66 + focusNode: _nameFocusNode,
67 + borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor,
68 + suffixIcon: SizedBox(width: 36),
69 + hintText: S.of(context).optional_name,
70 + textInputAction: TextInputAction.next,
71 + placeholderTextStyle: TextStyle(
72 + fontSize: 16,
73 + fontWeight: FontWeight.w600,
74 + color: Theme.of(context).accentTextTheme.headline1!.decorationColor!,
75 + ),
76 + textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
77 + validator: null,
78 + ),
79 + SizedBox(
80 + height: 24,
81 + ),
82 + BaseTextFormField(
83 + controller: descriptionController,
84 + focusNode: _descriptionFocusNode,
85 + textInputAction: TextInputAction.next,
86 + borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor,
87 + suffixIcon: SizedBox(width: 36),
88 + hintText: S.of(context).optional_description,
89 + placeholderTextStyle: TextStyle(
90 + fontSize: 16,
91 + fontWeight: FontWeight.w600,
92 + color: Theme.of(context).accentTextTheme.headline1!.decorationColor!,
93 + ),
94 + textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
95 + validator: null,
96 + ),
97 + SizedBox(height: 24),
98 + BaseTextFormField(
99 + controller: emailController,
100 + textInputAction: TextInputAction.next,
101 + focusNode: _emailFocusNode,
102 + borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor,
103 + suffixIcon: SizedBox(width: 36),
104 + keyboardType: TextInputType.emailAddress,
105 + hintText: S.of(context).optional_email_hint,
106 + placeholderTextStyle: TextStyle(
107 + fontSize: 16,
108 + fontWeight: FontWeight.w600,
109 + color: Theme.of(context).accentTextTheme.headline1!.decorationColor!,
110 + ),
111 + textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
112 + validator: EmailValidator(),
113 + ),
114 + SizedBox(
115 + height: 52,
116 + ),
117 + ],
118 + ));
119 + }
120 +
121 + void _presentPicker(BuildContext context) {
122 + showPopUp<void>(
123 + builder: (_) => CurrencyPicker(
124 + selectedAtIndex: anonInvoicePageViewModel.selectedCurrencyIndex,
125 + items: anonInvoicePageViewModel.currencies,
126 + hintText: S.of(context).search_currency,
127 + onItemSelected: anonInvoicePageViewModel.selectCurrency,
128 + ),
129 + context: context,
130 + );
131 + }
132 +}
lib/src/screens/receive/widgets/anonpay_status_section.dart new
+87
@@ -0,0 +1,87 @@
1 +import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
2 +import 'package:cake_wallet/generated/i18n.dart';
3 +import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
4 +import 'package:cake_wallet/typography.dart';
5 +import 'package:flutter/material.dart';
6 +
7 +class AnonInvoiceStatusSection extends StatelessWidget {
8 + const AnonInvoiceStatusSection({
9 + super.key,
10 + required this.invoiceInfo,
11 + });
12 +
13 + final AnonpayInvoiceInfo invoiceInfo;
14 +
15 + @override
16 + Widget build(BuildContext context) {
17 + return Container(
18 + width: 200,
19 + padding: EdgeInsets.all(19),
20 + decoration: BoxDecoration(
21 + color: Theme.of(context).backgroundColor,
22 + borderRadius: BorderRadius.circular(30),
23 + ),
24 + child: Column(
25 + children: [
26 + Row(
27 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
28 + children: [
29 + Text(
30 + S.current.status,
31 + style: TextStyle(
32 + fontSize: 14,
33 + fontWeight: FontWeight.w500,
34 + color: Theme.of(context).primaryTextTheme.headline1!.decorationColor!,
35 + ),
36 + ),
37 + Container(
38 + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
39 + decoration: BoxDecoration(
40 + color: Theme.of(context).accentTextTheme.headline3!.color!,
41 + borderRadius: BorderRadius.circular(10),
42 + ),
43 + child: Row(
44 + mainAxisSize: MainAxisSize.min,
45 + children: [
46 + SyncIndicatorIcon(
47 + boolMode: false,
48 + value: invoiceInfo.status ?? '',
49 + size: 6,
50 + ),
51 + SizedBox(width: 5),
52 + Text(
53 + invoiceInfo.status ?? '',
54 + style: textSmallSemiBold(
55 + color: Theme.of(context).primaryTextTheme.headline6!.color,
56 + ),
57 + )
58 + ],
59 + ),
60 + )
61 + ],
62 + ),
63 + SizedBox(height: 27),
64 + Row(
65 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
66 + children: [
67 + Text(
68 + 'ID',
69 + style: TextStyle(
70 + fontSize: 14,
71 + fontWeight: FontWeight.w500,
72 + color: Theme.of(context).primaryTextTheme.headline1!.decorationColor!,
73 + ),
74 + ),
75 + Text(
76 + invoiceInfo.invoiceId ?? '',
77 + style: textSmallSemiBold(
78 + color: Theme.of(context).primaryTextTheme.headline6!.color,
79 + ),
80 + ),
81 + ],
82 + ),
83 + ],
84 + ),
85 + );
86 + }
87 +}
lib/src/screens/receive/widgets/copy_link_item.dart new
+56
@@ -0,0 +1,56 @@
1 +
2 +import 'package:cake_wallet/generated/i18n.dart';
3 +import 'package:cake_wallet/typography.dart';
4 +import 'package:cake_wallet/utils/show_bar.dart';
5 +import 'package:flutter/material.dart';
6 +import 'package:flutter/services.dart';
7 +import 'package:share_plus/share_plus.dart';
8 +
9 +class CopyLinkItem extends StatelessWidget {
10 + const CopyLinkItem({super.key, required this.url, required this.title});
11 + final String url;
12 + final String title;
13 +
14 + @override
15 + Widget build(BuildContext context) {
16 + final copyImage = Image.asset('assets/images/copy_address.png',
17 + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!);
18 +
19 + return Row(
20 + mainAxisAlignment: MainAxisAlignment.center,
21 + children: [
22 + Text(
23 + title,
24 + style: textMedium(
25 + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
26 + ),
27 + ),
28 + SizedBox(width: 50),
29 + Row(
30 + children: [
31 + InkWell(
32 + onTap: () {
33 + Clipboard.setData(ClipboardData(text: url));
34 + showBar<void>(context, S.of(context).copied_to_clipboard);
35 + },
36 + child: copyImage,
37 + ),
38 + SizedBox(width: 20),
39 + IconButton(
40 + padding: EdgeInsets.zero,
41 + constraints: BoxConstraints(),
42 + highlightColor: Colors.transparent,
43 + splashColor: Colors.transparent,
44 + iconSize: 25,
45 + onPressed: () => Share.share(url),
46 + icon: Icon(
47 + Icons.share,
48 + color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
49 + ),
50 + )
51 + ],
52 + )
53 + ],
54 + );
55 + }
56 +}
lib/src/screens/receive/widgets/qr_image.dart
+3 -3
@@ -5,13 +5,13 @@ class QrImage extends StatelessWidget {
5 QrImage({
6 required this.data,
7 this.size = 100.0,
8 - this.version = 9, // Previous value: 7 something happened after flutter upgrade monero wallets addresses are longer than ver. 7 ???
8 + this.version,
9 this.errorCorrectionLevel = qr.QrErrorCorrectLevel.L,
10 });
11
12 final double size;
13 final String data;
14 - final int version;
14 + final int? version;
15 final int errorCorrectionLevel;
16
17 @override
@@ -19,7 +19,7 @@ class QrImage extends StatelessWidget {
19 return qr.QrImage(
20 data: data,
21 errorCorrectionLevel: errorCorrectionLevel,
22 - version: version,
22 + version: version ?? 9, // Previous value: 7 something happened after flutter upgrade monero wallets addresses are longer than ver. 7 ???
23 size: size,
24 foregroundColor: Colors.black,
25 backgroundColor: Colors.white,
lib/src/screens/receive/widgets/qr_widget.dart
+71 -66
@@ -3,7 +3,6 @@ import 'package:cake_wallet/utils/show_bar.dart';
3 import 'package:cw_core/wallet_type.dart';
4 import 'package:device_display_brightness/device_display_brightness.dart';
5 import 'package:flutter/material.dart';
6 -import 'package:flutter/cupertino.dart';
6 import 'package:flutter/services.dart';
7 import 'package:flutter_mobx/flutter_mobx.dart';
8 import 'package:cake_wallet/generated/i18n.dart';
@@ -16,11 +15,12 @@ class QRWidget extends StatelessWidget {
15 QRWidget(
16 {required this.addressListViewModel,
17 required this.isLight,
18 + this.qrVersion,
19 this.isAmountFieldShow = false,
20 this.amountTextFieldFocusNode})
21 : amountController = TextEditingController(),
22 _formKey = GlobalKey<FormState>() {
23 - amountController.addListener(() => addressListViewModel.amount =
23 + amountController.addListener(() => addressListViewModel?.amount =
24 _formKey.currentState!.validate() ? amountController.text : '');
25 }
26
@@ -30,6 +30,7 @@ class QRWidget extends StatelessWidget {
30 final FocusNode? amountTextFieldFocusNode;
31 final GlobalKey<FormState> _formKey;
32 final bool isLight;
33 + final int? qrVersion;
34
35 @override
36 Widget build(BuildContext context) {
@@ -57,46 +58,48 @@ class QRWidget extends StatelessWidget {
58 children: <Widget>[
59 Spacer(flex: 3),
60 Observer(
60 - builder: (_) => Flexible(
61 - flex: 5,
62 - child: GestureDetector(
63 - onTap: () async {
64 - // Get the current brightness:
65 - final double brightness = await DeviceDisplayBrightness.getBrightness();
61 + builder: (_) {
62 + return Flexible(
63 + flex: 5,
64 + child: GestureDetector(
65 + onTap: () async {
66 + // Get the current brightness:
67 + final double brightness = await DeviceDisplayBrightness.getBrightness();
68
67 - // ignore: unawaited_futures
68 - DeviceDisplayBrightness.setBrightness(1.0);
69 - await Navigator.pushNamed(
70 - context,
71 - Routes.fullscreenQR,
72 - arguments: {
73 - 'qrData': addressListViewModel.uri.toString(),
74 - 'isLight': isLight,
75 - },
76 - );
77 - // ignore: unawaited_futures
78 - DeviceDisplayBrightness.setBrightness(brightness);
79 - },
80 - child: Hero(
81 - tag: Key(addressListViewModel.uri.toString()),
82 - child: Center(
83 - child: AspectRatio(
84 - aspectRatio: 1.0,
85 - child: Container(
86 - padding: EdgeInsets.all(5),
87 - decoration: BoxDecoration(
88 - border: Border.all(
89 - width: 3,
90 - color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
69 + // ignore: unawaited_futures
70 + DeviceDisplayBrightness.setBrightness(1.0);
71 + await Navigator.pushNamed(
72 + context,
73 + Routes.fullscreenQR,
74 + arguments: {
75 + 'qrData': addressListViewModel.uri.toString(),
76 + },
77 + );
78 + // ignore: unawaited_futures
79 + DeviceDisplayBrightness.setBrightness(brightness);
80 + },
81 + child: Hero(
82 + tag: Key(addressListViewModel.uri.toString()),
83 + child: Center(
84 + child: AspectRatio(
85 + aspectRatio: 1.0,
86 + child: Container(
87 + padding: EdgeInsets.all(5),
88 + decoration: BoxDecoration(
89 + border: Border.all(
90 + width: 3,
91 + color:
92 + Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
93 + ),
94 ),
95 + child: QrImage(data: addressListViewModel.uri.toString(), version: qrVersion),
96 ),
93 - child: QrImage(data: addressListViewModel.uri.toString()),
97 ),
98 ),
99 ),
100 ),
98 - ),
99 - ),
101 + );
102 + }
103 ),
104 Spacer(flex: 3)
105 ],
@@ -120,8 +123,9 @@ class QRWidget extends StatelessWidget {
123 hintText: S.of(context).receive_amount,
124 textColor: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
125 borderColor: Theme.of(context).textTheme!.headline5!.decorationColor!,
123 - validator: AmountValidator(currency:
124 - walletTypeToCryptoCurrency(addressListViewModel.type), isAutovalidate: true),
126 + validator: AmountValidator(
127 + currency: walletTypeToCryptoCurrency(addressListViewModel!.type),
128 + isAutovalidate: true),
129 // FIX-ME: Check does it equal to autovalidate: true,
130 autovalidateMode: AutovalidateMode.always,
131 placeholderTextStyle: TextStyle(
@@ -135,39 +139,40 @@ class QRWidget extends StatelessWidget {
139 ],
140 ),
141 ),
138 - Padding(
139 - padding: EdgeInsets.only(top: 8, bottom: 8),
140 - child: Builder(
141 - builder: (context) => Observer(
142 - builder: (context) => GestureDetector(
143 - onTap: () {
144 - Clipboard.setData(ClipboardData(text: addressListViewModel.address.address));
145 - showBar<void>(context, S.of(context).copied_to_clipboard);
146 - },
147 - child: Row(
148 - mainAxisSize: MainAxisSize.max,
149 - crossAxisAlignment: CrossAxisAlignment.start,
150 - children: <Widget>[
151 - Expanded(
152 - child: Text(
153 - addressListViewModel.address.address,
154 - textAlign: TextAlign.center,
155 - style: TextStyle(
156 - fontSize: 15,
157 - fontWeight: FontWeight.w500,
158 - color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!),
142 + Padding(
143 + padding: EdgeInsets.only(top: 8, bottom: 8),
144 + child: Builder(
145 + builder: (context) => Observer(
146 + builder: (context) => GestureDetector(
147 + onTap: () {
148 + Clipboard.setData(ClipboardData(text: addressListViewModel!.address.address));
149 + showBar<void>(context, S.of(context).copied_to_clipboard);
150 + },
151 + child: Row(
152 + mainAxisSize: MainAxisSize.max,
153 + crossAxisAlignment: CrossAxisAlignment.start,
154 + children: <Widget>[
155 + Expanded(
156 + child: Text(
157 + addressListViewModel!.address.address,
158 + textAlign: TextAlign.center,
159 + style: TextStyle(
160 + fontSize: 15,
161 + fontWeight: FontWeight.w500,
162 + color:
163 + Theme.of(context).accentTextTheme!.headline2!.backgroundColor!),
164 + ),
165 ),
160 - ),
161 - Padding(
162 - padding: EdgeInsets.only(left: 12),
163 - child: copyImage,
164 - )
165 - ],
166 + Padding(
167 + padding: EdgeInsets.only(left: 12),
168 + child: copyImage,
169 + )
170 + ],
171 + ),
172 ),
173 ),
174 ),
169 - ),
170 - )
175 + )
176 ],
177 );
178 }
lib/src/screens/wallet_keys/wallet_keys_page.dart
-1
@@ -33,7 +33,6 @@ class WalletKeysPage extends BasePage {
33 Routes.fullscreenQR,
34 arguments: {
35 'qrData': (await walletKeysViewModel.url).toString(),
36 - 'isLight': true,
36 },
37 );
38 // ignore: unawaited_futures
lib/store/anonpay/anonpay_transactions_store.dart new
+34
@@ -0,0 +1,34 @@
1 +import 'dart:async';
2 +import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
3 +import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
4 +import 'package:hive/hive.dart';
5 +import 'package:mobx/mobx.dart';
6 +
7 +part 'anonpay_transactions_store.g.dart';
8 +
9 +class AnonpayTransactionsStore = AnonpayTransactionsStoreBase with _$AnonpayTransactionsStore;
10 +
11 +abstract class AnonpayTransactionsStoreBase with Store {
12 + AnonpayTransactionsStoreBase({
13 + required this.anonpayInvoiceInfoSource,
14 + }) : transactions = <AnonpayTransactionListItem>[] {
15 + anonpayInvoiceInfoSource.watch().listen(
16 + (_) async => await updateTransactionList(),
17 + );
18 + updateTransactionList();
19 + }
20 +
21 + Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource;
22 +
23 + @observable
24 + List<AnonpayTransactionListItem> transactions;
25 +
26 + @action
27 + Future<void> updateTransactionList() async {
28 + transactions = anonpayInvoiceInfoSource.values
29 + .map(
30 + (transaction) => AnonpayTransactionListItem(transaction: transaction),
31 + )
32 + .toList();
33 + }
34 +}
lib/store/dashboard/transaction_filter_store.dart
+14 -4
@@ -1,8 +1,8 @@
1 +import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
2 +import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
3 import 'package:mobx/mobx.dart';
4 import 'package:cw_core/transaction_direction.dart';
5 import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
4 -import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
5 -import 'package:cake_wallet/generated/i18n.dart';
6
7 part 'transaction_filter_store.g.dart';
8
@@ -57,8 +57,8 @@ abstract class TransactionFilterStoreBase with Store {
57 @action
58 void changeEndDate(DateTime date) => endDate = date;
59
60 - List<TransactionListItem> filtered({required List<TransactionListItem> transactions}) {
61 - var _transactions = <TransactionListItem>[];
60 + List<ActionListItem> filtered({required List<ActionListItem> transactions}) {
61 + var _transactions = <ActionListItem>[];
62 final needToFilter = !displayAll ||
63 (startDate != null && endDate != null);
64
@@ -67,16 +67,26 @@ abstract class TransactionFilterStoreBase with Store {
67 var allowed = true;
68
69 if (allowed && startDate != null && endDate != null) {
70 + if(item is TransactionListItem){
71 allowed = (startDate?.isBefore(item.transaction.date) ?? false)
72 && (endDate?.isAfter(item.transaction.date) ?? false);
73 + }else if(item is AnonpayTransactionListItem){
74 + allowed = (startDate?.isBefore(item.transaction.createdAt) ?? false)
75 + && (endDate?.isAfter(item.transaction.createdAt) ?? false);
76 + }
77 }
78
79 if (allowed && (!displayAll)) {
80 + if(item is TransactionListItem){
81 allowed = (displayOutgoing &&
82 item.transaction.direction ==
83 TransactionDirection.outgoing) ||
84 (displayIncoming &&
85 item.transaction.direction == TransactionDirection.incoming);
86 + } else if(item is AnonpayTransactionListItem){
87 + allowed = displayIncoming;
88 + }
89 +
90 }
91
92 return allowed;
lib/view_model/anon_invoice_page_view_model.dart new
+186
@@ -0,0 +1,186 @@
1 +import 'package:cake_wallet/anonpay/anonpay_api.dart';
2 +import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
3 +import 'package:cake_wallet/anonpay/anonpay_request.dart';
4 +import 'package:cake_wallet/core/execution_state.dart';
5 +import 'package:cake_wallet/entities/fiat_currency.dart';
6 +import 'package:cake_wallet/entities/preferences_key.dart';
7 +import 'package:cake_wallet/entities/receive_page_option.dart';
8 +import 'package:cake_wallet/store/settings_store.dart';
9 +import 'package:cw_core/crypto_currency.dart';
10 +import 'package:cw_core/currency.dart';
11 +import 'package:cw_core/wallet_base.dart';
12 +import 'package:cw_core/wallet_type.dart';
13 +import 'package:hive/hive.dart';
14 +import 'package:mobx/mobx.dart';
15 +import 'package:shared_preferences/shared_preferences.dart';
16 +
17 +part 'anon_invoice_page_view_model.g.dart';
18 +
19 +class AnonInvoicePageViewModel = AnonInvoicePageViewModelBase with _$AnonInvoicePageViewModel;
20 +
21 +abstract class AnonInvoicePageViewModelBase with Store {
22 + AnonInvoicePageViewModelBase(
23 + this.anonPayApi,
24 + this.address,
25 + this.settingsStore,
26 + this._wallet,
27 + this._anonpayInvoiceInfoSource,
28 + this.sharedPreferences,
29 + this.pageOption,
30 + ) : receipientEmail = '',
31 + receipientName = '',
32 + description = '',
33 + amount = '',
34 + state = InitialExecutionState(),
35 + selectedCurrency = walletTypeToCryptoCurrency(_wallet.type),
36 + cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type) {
37 + _getPreviousDonationLink();
38 + _fetchLimits();
39 + }
40 +
41 + List<Currency> get currencies => [walletTypeToCryptoCurrency(_wallet.type), ...FiatCurrency.all];
42 + final AnonPayApi anonPayApi;
43 + final String address;
44 + final SettingsStore settingsStore;
45 + final WalletBase _wallet;
46 + final Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
47 + final SharedPreferences sharedPreferences;
48 + final ReceivePageOption pageOption;
49 +
50 + @observable
51 + Currency selectedCurrency;
52 +
53 + CryptoCurrency cryptoCurrency;
54 +
55 + @observable
56 + String receipientEmail;
57 +
58 + @observable
59 + String receipientName;
60 +
61 + @observable
62 + String description;
63 +
64 + @observable
65 + String amount;
66 +
67 + @observable
68 + ExecutionState state;
69 +
70 + @computed
71 + int get selectedCurrencyIndex => currencies.indexOf(selectedCurrency);
72 +
73 + @observable
74 + double? minimum;
75 +
76 + @observable
77 + double? maximum;
78 +
79 + @action
80 + void selectCurrency(Currency currency) {
81 + selectedCurrency = currency;
82 + maximum = minimum = null;
83 + if (currency is CryptoCurrency) {
84 + cryptoCurrency = currency;
85 + } else {
86 + cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type);
87 + }
88 +
89 + _fetchLimits();
90 + }
91 +
92 + @action
93 + Future<void> createInvoice() async {
94 + state = IsExecutingState();
95 + if (amount.isNotEmpty) {
96 + final amountInCrypto = double.parse(amount);
97 + if (minimum != null && amountInCrypto < minimum!) {
98 + state = FailureState('Amount is too small');
99 + return;
100 + }
101 + if (maximum != null && amountInCrypto > maximum!) {
102 + state = FailureState('Amount is too big');
103 + return;
104 + }
105 + }
106 + final result = await anonPayApi.createInvoice(AnonPayRequest(
107 + cryptoCurrency: cryptoCurrency,
108 + address: address,
109 + amount: amount.isEmpty ? null : amount,
110 + description: description,
111 + email: receipientEmail,
112 + name: receipientName,
113 + fiatEquivalent:
114 + selectedCurrency is FiatCurrency ? (selectedCurrency as FiatCurrency).raw : null,
115 + ));
116 +
117 + _anonpayInvoiceInfoSource.add(result);
118 +
119 + state = ExecutedSuccessfullyState(payload: result);
120 + }
121 +
122 + @action
123 + void setRequestParams({
124 + required String inputAmount,
125 + required String inputName,
126 + required String inputEmail,
127 + required String inputDescription,
128 + }) {
129 + receipientName = inputName;
130 + receipientEmail = inputEmail;
131 + description = inputDescription;
132 + amount = inputAmount;
133 + }
134 +
135 + @action
136 + Future<void> generateDonationLink() async {
137 + state = IsExecutingState();
138 +
139 + final result = await anonPayApi.generateDonationLink(AnonPayRequest(
140 + cryptoCurrency: cryptoCurrency,
141 + address: address,
142 + description: description,
143 + email: receipientEmail,
144 + name: receipientName,
145 + ));
146 +
147 + await sharedPreferences.setString(PreferencesKey.clearnetDonationLink, result.clearnetUrl);
148 + await sharedPreferences.setString(PreferencesKey.onionDonationLink, result.onionUrl);
149 +
150 + state = ExecutedSuccessfullyState(payload: result);
151 + }
152 +
153 + Future<void> _fetchLimits() async {
154 + final limit = await anonPayApi.fetchLimits(
155 + cryptoCurrency: cryptoCurrency,
156 + fiatCurrency: selectedCurrency is FiatCurrency ? selectedCurrency as FiatCurrency : null,
157 + );
158 + minimum = limit.min;
159 + maximum = limit.max != null ? limit.max! / 4 : null;
160 + }
161 +
162 + @action
163 + void reset() {
164 + selectedCurrency = walletTypeToCryptoCurrency(_wallet.type);
165 + cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type);
166 + receipientEmail = '';
167 + receipientName = '';
168 + description = '';
169 + amount = '';
170 + _fetchLimits();
171 + }
172 +
173 + Future<void> _getPreviousDonationLink() async {
174 + if (pageOption == ReceivePageOption.anonPayDonationLink) {
175 + final donationLink = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
176 + if (donationLink != null) {
177 + final url = Uri.parse(donationLink);
178 + url.queryParameters.forEach((key, value) {
179 + if (key == 'name') receipientName = value;
180 + if (key == 'email') receipientEmail = value;
181 + if (key == 'description') description = Uri.decodeComponent(value);
182 + });
183 + }
184 + }
185 + }
186 +}
lib/view_model/anonpay_details_view_model.dart new
+78
@@ -0,0 +1,78 @@
1 +import 'dart:async';
2 +
3 +import 'package:cake_wallet/anonpay/anonpay_api.dart';
4 +import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
5 +import 'package:cake_wallet/generated/i18n.dart';
6 +import 'package:cake_wallet/src/screens/trade_details/track_trade_list_item.dart';
7 +import 'package:cake_wallet/src/screens/trade_details/trade_details_list_card.dart';
8 +import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.dart';
9 +import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
10 +import 'package:cake_wallet/store/settings_store.dart';
11 +import 'package:cake_wallet/utils/date_formatter.dart';
12 +import 'package:cake_wallet/utils/show_bar.dart';
13 +import 'package:cw_core/crypto_currency.dart';
14 +import 'package:flutter/material.dart';
15 +import 'package:flutter/services.dart';
16 +import 'package:mobx/mobx.dart';
17 +import 'package:url_launcher/url_launcher_string.dart';
18 +
19 +part 'anonpay_details_view_model.g.dart';
20 +
21 +class AnonpayDetailsViewModel = AnonpayDetailsViewModelBase with _$AnonpayDetailsViewModel;
22 +
23 +abstract class AnonpayDetailsViewModelBase with Store {
24 + AnonpayDetailsViewModelBase(
25 + {required this.anonPayApi,
26 + required AnonpayInvoiceInfo anonpayInvoiceInfo,
27 + required this.settingsStore})
28 + : items = ObservableList<StandartListItem>(),
29 + invoiceDetail = anonpayInvoiceInfo {
30 + _updateItems();
31 + _updateInvoiceDetail();
32 + timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateInvoiceDetail());
33 + }
34 +
35 + final AnonPayApi anonPayApi;
36 + final SettingsStore settingsStore;
37 + final AnonpayInvoiceInfo invoiceDetail;
38 +
39 + final ObservableList<StandartListItem> items;
40 +
41 + Timer? timer;
42 +
43 + @action
44 + Future<void> _updateInvoiceDetail() async {
45 + try {
46 + final data = await anonPayApi.paymentStatus(invoiceDetail.invoiceId);
47 + invoiceDetail.status = data.status;
48 + _updateItems();
49 + } catch (e) {
50 + print(e.toString());
51 + }
52 + }
53 +
54 + void _updateItems() {
55 + final dateFormat = DateFormatter.withCurrentLocal();
56 + items.clear();
57 + items.addAll([
58 + DetailsListStatusItem(title: S.current.status, value: invoiceDetail.status),
59 + TradeDetailsListCardItem(
60 + id: invoiceDetail.invoiceId,
61 + createdAt: dateFormat.format(invoiceDetail.createdAt).toString(),
62 + pair: (invoiceDetail.fiatAmount != null)
63 + ? "→ ${invoiceDetail.fiatAmount} ${invoiceDetail.fiatEquiv ?? ''}"
64 + : '→ ${invoiceDetail.amountTo ?? ''} ${CryptoCurrency.fromFullName(invoiceDetail.coinTo).name.toUpperCase()}',
65 + onTap: (BuildContext context) {
66 + Clipboard.setData(ClipboardData(text: '${invoiceDetail.invoiceId}'));
67 + showBar<void>(context, S.of(context).copied_to_clipboard);
68 + },
69 + ),
70 + StandartListItem(title: S.current.trade_details_provider, value: invoiceDetail.provider)
71 + ]);
72 +
73 + items.add(TrackTradeListItem(
74 + title: 'Track',
75 + value: invoiceDetail.clearnetStatusUrl,
76 + onTap: () => launchUrlString(invoiceDetail.clearnetStatusUrl)));
77 + }
78 +}
lib/view_model/dashboard/anonpay_transaction_list_item.dart new
+11
@@ -0,0 +1,11 @@
1 +import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
2 +import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
3 +
4 +class AnonpayTransactionListItem extends ActionListItem {
5 + AnonpayTransactionListItem({required this.transaction});
6 +
7 + final AnonpayInvoiceInfo transaction;
8 +
9 + @override
10 + DateTime get date => transaction.createdAt;
11 +}
lib/view_model/dashboard/dashboard_view_model.dart
+13 -2
@@ -1,5 +1,7 @@
1 import 'package:cake_wallet/entities/exchange_api_mode.dart';
2 import 'package:cake_wallet/entities/fiat_api_mode.dart';
3 +import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
4 +import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
5 import 'package:cake_wallet/wallet_type_utils.dart';
6 import 'package:cw_core/transaction_history.dart';
7 import 'package:cw_core/balance.dart';
@@ -48,7 +50,9 @@ abstract class DashboardViewModelBase with Store {
50 required this.transactionFilterStore,
51 required this.settingsStore,
52 required this.yatStore,
51 - required this.ordersStore})
53 + required this.ordersStore,
54 + required this.anonpayTransactionsStore,
55 + })
56 : isOutdatedElectrumWallet = false,
57 hasSellAction = false,
58 isEnabledSellAction = false,
@@ -227,6 +231,11 @@ abstract class DashboardViewModelBase with Store {
231 List<OrderListItem> get orders => ordersStore.orders
232 .where((item) => item.order.walletId == wallet.id)
233 .toList();
234 +
235 + @computed
236 + List<AnonpayTransactionListItem> get anonpayTransactons => anonpayTransactionsStore.transactions
237 + .where((item) => item.transaction.walletId == wallet.id)
238 + .toList();
239
240 @computed
241 double get price => balanceViewModel.price;
@@ -235,7 +244,7 @@ abstract class DashboardViewModelBase with Store {
244 List<ActionListItem> get items {
245 final _items = <ActionListItem>[];
246
238 - _items.addAll(transactionFilterStore.filtered(transactions: transactions));
247 + _items.addAll(transactionFilterStore.filtered(transactions: [...transactions, ...anonpayTransactons]));
248 _items.addAll(tradeFilterStore.filtered(trades: trades, wallet: wallet));
249 _items.addAll(orders);
250
@@ -262,6 +271,8 @@ abstract class DashboardViewModelBase with Store {
271
272 TradeFilterStore tradeFilterStore;
273
274 + AnonpayTransactionsStore anonpayTransactionsStore;
275 +
276 TransactionFilterStore transactionFilterStore;
277
278 Map<String, List<FilterItem>> filterItems;
lib/view_model/dashboard/receive_option_view_model.dart new
+34
@@ -0,0 +1,34 @@
1 +import 'package:cake_wallet/entities/receive_page_option.dart';
2 +import 'package:cw_core/wallet_base.dart';
3 +import 'package:cw_core/wallet_type.dart';
4 +import 'package:mobx/mobx.dart';
5 +
6 +part 'receive_option_view_model.g.dart';
7 +
8 +class ReceiveOptionViewModel = ReceiveOptionViewModelBase with _$ReceiveOptionViewModel;
9 +
10 +abstract class ReceiveOptionViewModelBase with Store {
11 + ReceiveOptionViewModelBase(this._wallet, this.initialPageOption)
12 + : selectedReceiveOption = initialPageOption ?? ReceivePageOption.mainnet,
13 + _options = [] {
14 + final walletType = _wallet.type;
15 + _options =
16 + walletType == WalletType.haven ? [ReceivePageOption.mainnet] : ReceivePageOption.values;
17 + }
18 +
19 + final WalletBase _wallet;
20 +
21 + final ReceivePageOption? initialPageOption;
22 +
23 + List<ReceivePageOption> _options;
24 +
25 + @observable
26 + ReceivePageOption selectedReceiveOption;
27 +
28 + List<ReceivePageOption> get options => _options;
29 +
30 + @action
31 + void selectReceiveOption(ReceivePageOption option) {
32 + selectedReceiveOption = option;
33 + }
34 +}
res/values/strings_ar.arb
+11 -1
@@ -684,5 +684,15 @@
684 "do_not_send": "لا ترسل",
685 "error_dialog_content": "عفوًا ، لقد حصلنا على بعض الخطأ.\n\nيرجى إرسال تقرير التعطل إلى فريق الدعم لدينا لتحسين التطبيق.",
686 "decimal_places_error": "عدد كبير جدًا من المنازل العشرية",
687 - "edit_node": "تحرير العقدة"
687 + "edit_node": "تحرير العقدة",
688 + "invoice_details": "تفاصيل الفاتورة",
689 + "donation_link_details": "تفاصيل رابط التبرع",
690 + "anonpay_description": "توليد ${type}. يمكن للمستلم ${method} بأي عملة مشفرة مدعومة ، وستتلقى أموالاً في هذه",
691 + "create_invoice": "إنشاء فاتورة",
692 + "create_donation_link": "إنشاء رابط التبرع",
693 + "optional_email_hint": "البريد الإلكتروني إخطار المدفوع لأمره الاختياري",
694 + "optional_description": "وصف اختياري",
695 + "optional_name": "اسم المستلم الاختياري",
696 + "clearnet_link": "رابط Clearnet",
697 + "onion_link": "رابط البصل"
698 }
res/values/strings_bg.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "Не изпращай",
687 "error_dialog_content": "Получихме грешка.\n\nМоля, изпратете доклада до нашия отдел поддръжка, за да подобрим приложението.",
688 "decimal_places_error": "Твърде много знаци след десетичната запетая",
689 - "edit_node": "Редактиране на възел"
689 + "edit_node": "Редактиране на възел",
690 + "invoice_details": "IДанни за фактура",
691 + "donation_link_details": "Подробности за връзката за дарение",
692 + "anonpay_description": "Генерирайте ${type}. Получателят може да ${method} с всяка поддържана криптовалута и вие ще получите средства в този портфейл.",
693 + "create_invoice": "Създайте фактура",
694 + "create_donation_link": "Създайте връзка за дарение",
695 + "optional_email_hint": "Незадължителен имейл за уведомяване на получателя",
696 + "optional_description": "Описание по избор",
697 + "optional_name": "Незадължително име на получател",
698 + "clearnet_link": "Clearnet връзка",
699 + "onion_link": "Лукова връзка"
700 }
res/values/strings_cs.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "Neodesílat",
687 "error_dialog_content": "Nastala chyba.\n\nProsím odešlete zprávu o chybě naší podpoře, aby mohli zajistit opravu.",
688 "decimal_places_error": "Příliš mnoho desetinných míst",
689 - "edit_node": "Upravit uzel"
689 + "edit_node": "Upravit uzel",
690 + "invoice_details": "detaily faktury",
691 + "donation_link_details": "Podrobnosti odkazu na darování",
692 + "anonpay_description": "Vygenerujte ${type}. Příjemce může ${method} s jakoukoli podporovanou kryptoměnou a vy obdržíte prostředky v této peněžence.",
693 + "create_invoice": "Vytvořit fakturu",
694 + "create_donation_link": "Vytvořit odkaz na darování",
695 + "optional_email_hint": "Volitelný e-mail s upozorněním na příjemce platby",
696 + "optional_description": "Volitelný popis",
697 + "optional_name": "Volitelné jméno příjemce",
698 + "clearnet_link": "Odkaz na Clearnet",
699 + "onion_link": "Cibulový odkaz"
700 }
res/values/strings_de.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "Nicht senden",
687 "error_dialog_content": "Hoppla, wir haben einen Fehler.\n\nBitte senden Sie den Absturzbericht an unser Support-Team, um die Anwendung zu verbessern.",
688 "decimal_places_error": "Zu viele Nachkommastellen",
689 - "edit_node": "Knoten bearbeiten"
689 + "edit_node": "Knoten bearbeiten",
690 + "invoice_details": "Rechnungs-Details",
691 + "donation_link_details": "Details zum Spendenlink",
692 + "anonpay_description": "Generieren Sie ${type}. Der Empfänger kann ${method} mit jeder unterstützten Kryptowährung verwenden, und Sie erhalten Geld in dieser Brieftasche.",
693 + "create_invoice": "Rechnung erstellen",
694 + "create_donation_link": "Spendenlink erstellen",
695 + "optional_email_hint": "Optionale Benachrichtigungs-E-Mail für den Zahlungsempfänger",
696 + "optional_description": "Optionale Beschreibung",
697 + "optional_name": "Optionaler Empfängername",
698 + "clearnet_link": "Clearnet-Link",
699 + "onion_link": "Zwiebel-Link"
700 }
res/values/strings_en.arb
+10
@@ -685,6 +685,16 @@
685 "arrive_in_this_address" : "${currency} ${tag}will arrive in this address",
686 "do_not_send": "Don't send",
687 "error_dialog_content": "Oops, we got some error.\n\nPlease send the crash report to our support team to make the application better.",
688 + "invoice_details": "Invoice details",
689 + "donation_link_details": "Donation link details",
690 + "anonpay_description": "Generate ${type}. The recipient can ${method} with any supported cryptocurrency, and you will receive funds in this wallet.",
691 + "create_invoice": "Create invoice",
692 + "create_donation_link": "Create donation link",
693 + "optional_email_hint": "Optional payee notification email",
694 + "optional_description": "Optional description",
695 + "optional_name": "Optional recipient name",
696 + "clearnet_link": "Clearnet link",
697 + "onion_link": "Onion link",
698 "decimal_places_error": "Too many decimal places",
699 "edit_node": "Edit Node"
700 }
res/values/strings_es.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "no enviar",
687 "error_dialog_content": "Vaya, tenemos un error.\n\nEnvíe el informe de bloqueo a nuestro equipo de soporte para mejorar la aplicación.",
688 "decimal_places_error": "Demasiados lugares decimales",
689 - "edit_node": "Edit Node"
689 + "edit_node": "Edit Node",
690 + "invoice_details": "Detalles de la factura",
691 + "donation_link_details": "Detalles del enlace de donación",
692 + "anonpay_description": "Genera ${type}. El destinatario puede ${method} con cualquier criptomoneda admitida, y recibirá fondos en esta billetera.",
693 + "create_invoice": "Crear factura",
694 + "create_donation_link": "Crear enlace de donación",
695 + "optional_email_hint": "Correo electrónico de notificación del beneficiario opcional",
696 + "optional_description": "Descripción opcional",
697 + "optional_name": "Nombre del destinatario opcional",
698 + "clearnet_link": "enlace Clearnet",
699 + "onion_link": "Enlace de cebolla"
700 }
res/values/strings_fr.arb
+11 -1
@@ -684,5 +684,15 @@
684 "do_not_send": "N'envoyez pas",
685 "error_dialog_content": "Oups, nous avons eu une erreur.\n\nVeuillez envoyer le rapport de plantage à notre équipe d'assistance pour améliorer l'application.",
686 "decimal_places_error": "Trop de décimales",
687 - "edit_node": "Modifier le nœud"
687 + "edit_node": "Modifier le nœud",
688 + "invoice_details": "Détails de la facture",
689 + "donation_link_details": "Détails du lien de don",
690 + "anonpay_description": "Générez ${type}. Le destinataire peut ${method} avec n'importe quelle crypto-monnaie prise en charge, et vous recevrez des fonds dans ce portefeuille.",
691 + "create_invoice": "Créer une facture",
692 + "create_donation_link": "Créer un lien de don",
693 + "optional_email_hint": "E-mail de notification du bénéficiaire facultatif",
694 + "optional_description": "Descriptif facultatif",
695 + "optional_name": "Nom du destinataire facultatif",
696 + "clearnet_link": "Lien Clearnet",
697 + "onion_link": "Lien d'oignon"
698 }
res/values/strings_hi.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "मत भेजो",
687 "error_dialog_content": "ओह, हमसे कुछ गड़बड़ी हुई है.\n\nएप्लिकेशन को बेहतर बनाने के लिए कृपया क्रैश रिपोर्ट हमारी सहायता टीम को भेजें।",
688 "decimal_places_error": "बहुत अधिक दशमलव स्थान",
689 - "edit_node": "नोड संपादित करें"
689 + "edit_node": "नोड संपादित करें",
690 + "invoice_details": "चालान विवरण",
691 + "donation_link_details": "दान लिंक विवरण",
692 + "anonpay_description": "${type} उत्पन्न करें। प्राप्तकर्ता किसी भी समर्थित क्रिप्टोकरेंसी के साथ ${method} कर सकता है, और आपको इस वॉलेट में धन प्राप्त होगा।",
693 + "create_invoice": "इनवॉयस बनाएँ",
694 + "create_donation_link": "दान लिंक बनाएं",
695 + "optional_email_hint": "वैकल्पिक प्राप्तकर्ता सूचना ईमेल",
696 + "optional_description": "वैकल्पिक विवरण",
697 + "optional_name": "वैकल्पिक प्राप्तकर्ता नाम",
698 + "clearnet_link": "क्लियरनेट लिंक",
699 + "onion_link": "प्याज का लिंक"
700 }
res/values/strings_hr.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "Ne šalji",
687 "error_dialog_content": "Ups, imamo grešku.\n\nPošaljite izvješće o padu našem timu za podršku kako bismo poboljšali aplikaciju.",
688 "decimal_places_error": "Previše decimalnih mjesta",
689 - "edit_node": "Uredi čvor"
689 + "edit_node": "Uredi čvor",
690 + "invoice_details": "Podaci o fakturi",
691 + "donation_link_details": "Detalji veza za donacije",
692 + "anonpay_description": "Generiraj ${type}. Primatelj može ${method} s bilo kojom podržanom kriptovalutom, a vi ćete primiti sredstva u ovaj novčanik.",
693 + "create_invoice": "Izradite fakturu",
694 + "create_donation_link": "Izradi poveznicu za donaciju",
695 + "optional_email_hint": "Neobavezna e-pošta za obavijest primatelja",
696 + "optional_description": "Opcijski opis",
697 + "optional_name": "Izborno ime primatelja",
698 + "clearnet_link": "Clearnet veza",
699 + "onion_link": "Poveznica luka"
700 }
res/values/strings_id.arb
+11 -1
@@ -668,5 +668,15 @@
668 "contact_list_contacts": "Kontak",
669 "contact_list_wallets": "Dompet Saya",
670 "decimal_places_error": "Terlalu banyak tempat desimal",
671 - "edit_node": "Sunting Node"
671 + "edit_node": "Sunting Node",
672 + "invoice_details": "Detail faktur",
673 + "donation_link_details": "Detail tautan donasi",
674 + "anonpay_description": "Hasilkan ${type}. Penerima dapat ${method} dengan cryptocurrency apa pun yang didukung, dan Anda akan menerima dana di dompet ini.",
675 + "create_invoice": "Buat faktur",
676 + "create_donation_link": "Buat tautan donasi",
677 + "optional_email_hint": "Email pemberitahuan penerima pembayaran opsional",
678 + "optional_description": "Deskripsi opsional",
679 + "optional_name": "Nama penerima opsional",
680 + "clearnet_link": "Tautan clearnet",
681 + "onion_link": "Tautan bawang"
682 }
res/values/strings_it.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "Non inviare",
687 "error_dialog_content": "Spiacenti, abbiamo riscontrato un errore.\n\nSi prega di inviare il rapporto sull'arresto anomalo al nostro team di supporto per migliorare l'applicazione.",
688 "decimal_places_error": "Troppe cifre decimali",
689 - "edit_node": "Modifica nodo"
689 + "edit_node": "Modifica nodo",
690 + "invoice_details": "Dettagli della fattura",
691 + "donation_link_details": "Dettagli del collegamento alla donazione",
692 + "anonpay_description": "Genera ${type}. Il destinatario può ${method} con qualsiasi criptovaluta supportata e riceverai fondi in questo portafoglio.",
693 + "create_invoice": "Crea fattura",
694 + "create_donation_link": "Crea un link per la donazione",
695 + "optional_email_hint": "Email di notifica del beneficiario facoltativa",
696 + "optional_description": "Descrizione facoltativa",
697 + "optional_name": "Nome del destinatario facoltativo",
698 + "clearnet_link": "Collegamento Clearnet",
699 + "onion_link": "Collegamento a cipolla"
700 }
res/values/strings_ja.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "送信しない",
687 "error_dialog_content": "エラーが発生しました。\n\nアプリケーションを改善するために、クラッシュ レポートをサポート チームに送信してください。",
688 "decimal_places_error": "小数点以下の桁数が多すぎる",
689 - "edit_node": "ノードを編集"
689 + "edit_node": "ノードを編集",
690 + "invoice_details": "請求の詳細",
691 + "donation_link_details": "寄付リンクの詳細",
692 + "anonpay_description": "${type} を生成します。受取人はサポートされている任意の暗号通貨で ${method} でき、あなたはこのウォレットで資金を受け取ります。",
693 + "create_invoice": "請求書の作成",
694 + "create_donation_link": "寄付リンクを作成",
695 + "optional_email_hint": "オプションの受取人通知メール",
696 + "optional_description": "オプションの説明",
697 + "optional_name": "オプションの受信者名",
698 + "clearnet_link": "クリアネット リンク",
699 + "onion_link": "オニオンリンク"
700 }
res/values/strings_ko.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "보내지 마세요",
687 "error_dialog_content": "죄송합니다. 오류가 발생했습니다.\n\n응용 프로그램을 개선하려면 지원 팀에 충돌 보고서를 보내주십시오.",
688 "decimal_places_error": "소수점 이하 자릿수가 너무 많습니다.",
689 - "edit_node": "노드 편집"
689 + "edit_node": "노드 편집",
690 + "invoice_details": "인보이스 세부정보",
691 + "donation_link_details": "기부 링크 세부정보",
692 + "anonpay_description": "${type} 생성. 수신자는 지원되는 모든 암호화폐로 ${method}할 수 있으며 이 지갑에서 자금을 받게 됩니다.",
693 + "create_invoice": "인보이스 생성",
694 + "create_donation_link": "기부 링크 만들기",
695 + "optional_email_hint": "선택적 수취인 알림 이메일",
696 + "optional_description": "선택적 설명",
697 + "optional_name": "선택적 수신자 이름",
698 + "clearnet_link": "클리어넷 링크",
699 + "onion_link": "양파 링크"
700 }
res/values/strings_my.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "မပို့ပါနှင့်",
687 "error_dialog_content": "အိုး၊ ကျွန်ုပ်တို့တွင် အမှားအယွင်းအချို့ရှိသည်။\n\nအပလီကေးရှင်းကို ပိုမိုကောင်းမွန်စေရန်အတွက် ပျက်စီးမှုအစီရင်ခံစာကို ကျွန်ုပ်တို့၏ပံ့ပိုးကူညီရေးအဖွဲ့ထံ ပေးပို့ပါ။",
688 "decimal_places_error": "ဒဿမနေရာများ များလွန်းသည်။",
689 - "edit_node": "Node ကို တည်းဖြတ်ပါ။"
689 + "edit_node": "Node ကို တည်းဖြတ်ပါ။",
690 + "invoice_details": "ပြေစာအသေးစိတ်",
691 + "donation_link_details": "လှူဒါန်းရန်လင့်ခ်အသေးစိတ်",
692 + "anonpay_description": "${type} ကို ဖန်တီးပါ။ လက်ခံသူက ${method} ကို ပံ့ပိုးပေးထားသည့် cryptocurrency တစ်ခုခုဖြင့် လုပ်ဆောင်နိုင်ပြီး၊ သင်သည် ဤပိုက်ဆံအိတ်တွင် ရံပုံငွေများ ရရှိမည်ဖြစ်သည်။",
693 + "create_invoice": "ပြေစာဖန်တီးပါ။",
694 + "create_donation_link": "လှူဒါန်းမှုလင့်ခ်ကို ဖန်တီးပါ။",
695 + "optional_email_hint": "ရွေးချယ်နိုင်သော ငွေလက်ခံသူ အကြောင်းကြားချက် အီးမေးလ်",
696 + "optional_description": "ရွေးချယ်နိုင်သော ဖော်ပြချက်",
697 + "optional_name": "ရွေးချယ်နိုင်သော လက်ခံသူအမည်",
698 + "clearnet_link": "Clearnet လင့်ခ်",
699 + "onion_link": "ကြက်သွန်လင့်"
700 }
res/values/strings_nl.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "Niet sturen",
687 "error_dialog_content": "Oeps, er is een fout opgetreden.\n\nStuur het crashrapport naar ons ondersteuningsteam om de applicatie te verbeteren.",
688 "decimal_places_error": "Te veel decimalen",
689 - "edit_node": "Knooppunt bewerken"
689 + "edit_node": "Knooppunt bewerken",
690 + "invoice_details": "Factuurgegevens",
691 + "donation_link_details": "Details van de donatielink",
692 + "anonpay_description": "Genereer ${type}. De ontvanger kan ${method} gebruiken met elke ondersteunde cryptocurrency en u ontvangt geld in deze portemonnee",
693 + "create_invoice": "Factuur maken",
694 + "create_donation_link": "Maak een donatielink aan",
695 + "optional_email_hint": "Optionele kennisgeving per e-mail aan de begunstigde",
696 + "optional_description": "Optionele beschrijving",
697 + "optional_name": "Optionele naam ontvanger",
698 + "clearnet_link": "Clearnet-link",
699 + "onion_link": "Ui koppeling"
700 }
res/values/strings_pl.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "Nie wysyłaj",
687 "error_dialog_content": "Ups, wystąpił błąd.\n\nPrześlij raport o awarii do naszego zespołu wsparcia, aby ulepszyć aplikację.",
688 "decimal_places_error": "Za dużo miejsc dziesiętnych",
689 - "edit_node": "Edytuj węzeł"
689 + "edit_node": "Edytuj węzeł",
690 + "invoice_details": "Dane do faktury",
691 + "donation_link_details": "Szczegóły linku darowizny",
692 + "anonpay_description": "Wygeneruj ${type}. Odbiorca może ${method} z dowolną obsługiwaną kryptowalutą, a Ty otrzymasz środki w tym portfelu.",
693 + "create_invoice": "Wystaw fakturę",
694 + "create_donation_link": "Utwórz link do darowizny",
695 + "optional_email_hint": "Opcjonalny e-mail z powiadomieniem odbiorcy płatności",
696 + "optional_description": "Opcjonalny opis",
697 + "optional_name": "Opcjonalna nazwa odbiorcy",
698 + "clearnet_link": "łącze Clearnet",
699 + "onion_link": "Łącznik cebulowy"
700 }
res/values/strings_pt.arb
+11 -1
@@ -685,5 +685,15 @@
685 "do_not_send": "não envie",
686 "error_dialog_content": "Ops, houve algum erro.\n\nPor favor, envie o relatório de falha para nossa equipe de suporte para melhorar o aplicativo.",
687 "decimal_places_error": "Muitas casas decimais",
688 - "edit_node": "Editar nó"
688 + "edit_node": "Editar nó",
689 + "invoice_details": "Detalhes da fatura",
690 + "donation_link_details": "Detalhes do link de doação",
691 + "anonpay_description": "Gere ${type}. O destinatário pode ${method} com qualquer criptomoeda suportada e você receberá fundos nesta carteira.",
692 + "create_invoice": "Criar recibo",
693 + "create_donation_link": "Criar link de doação",
694 + "optional_email_hint": "E-mail opcional de notificação do beneficiário",
695 + "optional_description": "Descrição opcional",
696 + "optional_name": "Nome do destinatário opcional",
697 + "clearnet_link": "link clear net",
698 + "onion_link": "ligação de cebola"
699 }
res/values/strings_ru.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "Не отправлять",
687 "error_dialog_content": "Ой, у нас какая-то ошибка.\n\nПожалуйста, отправьте отчет о сбое в нашу службу поддержки, чтобы сделать приложение лучше.",
688 "decimal_places_error": "Слишком много десятичных знаков",
689 - "edit_node": "Редактировать узел"
689 + "edit_node": "Редактировать узел",
690 + "invoice_details": "Детали счета",
691 + "donation_link_details": "Информация о ссылке для пожертвований",
692 + "anonpay_description": "Создайте ${type}. Получатель может использовать ${method} с любой поддерживаемой криптовалютой, и вы получите средства на этот кошелек.",
693 + "create_invoice": "Создать счет",
694 + "create_donation_link": "Создать ссылку для пожертвований",
695 + "optional_email_hint": "Необязательное электронное письмо с уведомлением получателя платежа",
696 + "optional_description": "Дополнительное описание",
697 + "optional_name": "Необязательное имя получателя",
698 + "clearnet_link": "Клирнет ссылка",
699 + "onion_link": "Луковая ссылка"
700 }
res/values/strings_th.arb
+11 -1
@@ -684,5 +684,15 @@
684 "do_not_send": "อย่าส่ง",
685 "error_dialog_content": "อ๊ะ เราพบข้อผิดพลาดบางอย่าง\n\nโปรดส่งรายงานข้อขัดข้องไปยังทีมสนับสนุนของเราเพื่อปรับปรุงแอปพลิเคชันให้ดียิ่งขึ้น",
686 "decimal_places_error": "ทศนิยมมากเกินไป",
687 - "edit_node": "แก้ไขโหนด"
687 + "edit_node": "แก้ไขโหนด",
688 + "invoice_details": "รายละเอียดใบแจ้งหนี้",
689 + "donation_link_details": "รายละเอียดลิงค์บริจาค",
690 + "anonpay_description": "สร้าง ${type} ผู้รับสามารถ ${method} ด้วยสกุลเงินดิจิทัลที่รองรับ และคุณจะได้รับเงินในกระเป๋าสตางค์นี้",
691 + "create_invoice": "สร้างใบแจ้งหนี้",
692 + "create_donation_link": "สร้างลิงค์บริจาค",
693 + "optional_email_hint": "อีเมลแจ้งผู้รับเงินเพิ่มเติม",
694 + "optional_description": "คำอธิบายเพิ่มเติม",
695 + "optional_name": "ชื่อผู้รับเพิ่มเติม",
696 + "clearnet_link": "ลิงค์เคลียร์เน็ต",
697 + "onion_link": "ลิงค์หัวหอม"
698 }
res/values/strings_tr.arb
+11 -1
@@ -686,5 +686,15 @@
686 "do_not_send": "Gönderme",
687 "error_dialog_content": "Hay aksi, bir hatamız var.\n\nUygulamayı daha iyi hale getirmek için lütfen kilitlenme raporunu destek ekibimize gönderin.",
688 "decimal_places_error": "Çok fazla ondalık basamak",
689 - "edit_node": "Düğümü Düzenle"
689 + "edit_node": "Düğümü Düzenle",
690 + "invoice_details": "fatura detayları",
691 + "donation_link_details": "Bağış bağlantısı ayrıntıları",
692 + "anonpay_description": "${type} oluşturun. Alıcı, desteklenen herhangi bir kripto para birimi ile ${method} yapabilir ve bu cüzdanda para alırsınız.",
693 + "create_invoice": "Fatura oluşturmak",
694 + "create_donation_link": "Bağış bağlantısı oluştur",
695 + "optional_email_hint": "İsteğe bağlı alacaklı bildirim e-postası",
696 + "optional_description": "İsteğe bağlı açıklama",
697 + "optional_name": "İsteğe bağlı alıcı adı",
698 + "clearnet_link": "Net bağlantı",
699 + "onion_link": "soğan bağlantısı"
700 }
res/values/strings_uk.arb
+11 -1
@@ -685,5 +685,15 @@
685 "do_not_send": "Не надсилайте",
686 "error_dialog_content": "На жаль, ми отримали помилку.\n\nБудь ласка, надішліть звіт про збій нашій команді підтримки, щоб покращити додаток.",
687 "decimal_places_error": "Забагато знаків після коми",
688 - "edit_node": "Редагувати вузол"
688 + "edit_node": "Редагувати вузол",
689 + "invoice_details": "Реквізити рахунку-фактури",
690 + "donation_link_details": "Деталі посилання для пожертв",
691 + "anonpay_description": "Згенерувати ${type}. Одержувач може ${method} будь-якою підтримуваною криптовалютою, і ви отримаєте кошти на цей гаманець.",
692 + "create_invoice": "Створити рахунок-фактуру",
693 + "create_donation_link": "Створити посилання для пожертв",
694 + "optional_email_hint": "Додаткова електронна адреса для сповіщення одержувача",
695 + "optional_description": "Додатковий опис",
696 + "optional_name": "Додаткове ім'я одержувача",
697 + "clearnet_link": "Посилання Clearnet",
698 + "onion_link": "Посилання на цибулю"
699 }
res/values/strings_ur.arb
+11 -1
@@ -687,5 +687,15 @@
687 "do_not_send" : "مت بھیجیں۔",
688 "error_dialog_content" : "افوہ، ہمیں کچھ خرابی ملی۔\n\nایپلی کیشن کو بہتر بنانے کے لیے براہ کرم کریش رپورٹ ہماری سپورٹ ٹیم کو بھیجیں۔",
689 "decimal_places_error": "بہت زیادہ اعشاریہ جگہیں۔",
690 - "edit_node": "نوڈ میں ترمیم کریں۔"
690 + "edit_node": "نوڈ میں ترمیم کریں۔",
691 + "invoice_details": "رسید کی تفصیلات",
692 + "donation_link_details": "عطیہ کے لنک کی تفصیلات",
693 + "anonpay_description": "${type} بنائیں۔ وصول کنندہ کسی بھی تعاون یافتہ کرپٹو کرنسی کے ساتھ ${method} کرسکتا ہے، اور آپ کو اس بٹوے میں فنڈز موصول ہوں گے۔",
694 + "create_invoice": "انوائس بنائیں",
695 + "create_donation_link": "عطیہ کا لنک بنائیں",
696 + "optional_email_hint": "اختیاری وصول کنندہ کی اطلاع کا ای میل",
697 + "optional_description": "اختیاری تفصیل",
698 + "optional_name": "اختیاری وصول کنندہ کا نام",
699 + "clearnet_link": "کلیرنیٹ لنک",
700 + "onion_link": "پیاز کا لنک"
701 }
res/values/strings_zh.arb
+11 -1
@@ -684,5 +684,15 @@
684 "do_not_send": "不要发送",
685 "error_dialog_content": "糟糕,我们遇到了一些错误。\n\n请将崩溃报告发送给我们的支持团队,以改进应用程序。",
686 "decimal_places_error": "小数位太多",
687 - "edit_node": "编辑节点"
687 + "edit_node": "编辑节点",
688 + "invoice_details": "发票明细",
689 + "donation_link_details": "捐赠链接详情",
690 + "anonpay_description": "生成 ${type}。收款人可以使用任何受支持的加密货币 ${method},您将在此钱包中收到资金。",
691 + "create_invoice": "创建发票",
692 + "create_donation_link": "创建捐赠链接",
693 + "optional_email_hint": "可选的收款人通知电子邮件",
694 + "optional_description": "可选说明",
695 + "optional_name": "可选收件人姓名",
696 + "clearnet_link": "明网链接",
697 + "onion_link": "洋葱链接"
698 }
tool/utils/secret_key.dart
+1
@@ -27,6 +27,7 @@ class SecretKey {
27 SecretKey('trocadorApiKey', () => ''),
28 SecretKey('trocadorExchangeMarkup', () => ''),
29 SecretKey('twitterBearerToken', () => ''),
30 + SecretKey('anonPayReferralCode', () => '')
31 ];
32
33 final String name;