Generic fixes (#1320)

* New price API * Fix test app package id * Fix workflow * change environment variable to use pr number [skip ci] * Fix un-needed padding * Fix raw value for usdtSol * Remove duplicate fetching for balance and transactions at start [skip ci] * Fix address validation of spl tokens * Add Service Status * Update lib/src/widgets/service_status_tile.dart Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> * Update lib/src/widgets/services_updates_widget.dart Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> * Update monero version * update sodium script * Change automatic priority fee rate --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>

Omar Hatem committed Mar 10, 2024 at 04:02 UTC 64143646026576b05f57795cb92cb99b95438644
20 files changed +285 -33
.github/workflows/pr_test_build.yml
+2 -4
@@ -16,6 +16,7 @@ jobs:
16 env:
17 STORE_PASS: test@cake_wallet
18 KEY_PASS: test@cake_wallet
19 + PR_NUMBER: ${{ github.event.number }}
20
21 steps:
22 - name: is pr
@@ -150,10 +151,7 @@ jobs:
151
152 - name: Rename app
153 run: |
153 - hash=`sha512sum <<<"${{ env.BRANCH_NAME }}"`
154 - substring=${hash:0:15}
155 - echo substring
156 - echo -e "id=com.cakewallet.test_$(substring)\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
154 + echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
155
156 - name: Build
157 run: |
assets/images/notification_icon.png
Binary files /dev/null and b/assets/images/notification_icon.png differ
assets/images/status_website_image.png
Binary files /dev/null and b/assets/images/status_website_image.png differ
cw_core/lib/crypto_currency.dart
+1 -1
@@ -216,7 +216,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
216 static const usdcEPoly = CryptoCurrency(title: 'USDC.E', tag: 'POLY', fullName: 'USD Coin (PoS)', raw: 88, name: 'usdcepoly', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
217 static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kas', iconPath: 'assets/images/kaspa_icon.png', decimals: 8);
218 static const digibyte = CryptoCurrency(title: 'DGB', fullName: 'DigiByte', raw: 90, name: 'dgb', iconPath: 'assets/images/digibyte.png', decimals: 8);
219 - static const usdtSol = CryptoCurrency(title: 'USDT', tag: 'SOL', fullName: 'USDT Tether', raw: 90, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
219 + static const usdtSol = CryptoCurrency(title: 'USDT', tag: 'SOL', fullName: 'USDT Tether', raw: 91, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
220
221
222 static final Map<int, CryptoCurrency> _rawCurrencyMap =
cw_monero/lib/monero_wallet.dart
+4 -7
@@ -183,13 +183,8 @@ abstract class MoneroWalletBase
183 // try to use the date instead:
184 try {
185 _setHeightFromDate();
186 - } catch (e, s) {
186 + } catch (_) {
187 // we still couldn't get a valid sync height :/
188 - onError?.call(FlutterErrorDetails(
189 - exception: e,
190 - stack: s,
191 - library: this.runtimeType.toString(),
192 - ));
188 }
189 }
190 }
@@ -287,7 +282,9 @@ abstract class MoneroWalletBase
282 pendingTransactionDescription = await transaction_history.createTransaction(
283 address: address!,
284 amount: amount,
290 - priorityRaw: _credentials.priority.serialize(),
285 + priorityRaw: _credentials.priority == MoneroTransactionPriority.automatic
286 + ? MoneroTransactionPriority.medium.serialize()
287 + : _credentials.priority.serialize(),
288 accountIndex: walletAddresses.account!.id,
289 preferredInputs: inputs);
290 }
cw_solana/lib/solana_wallet.dart
-10
@@ -165,16 +165,6 @@ abstract class SolanaWalletBase
165 throw Exception("Solana Node connection failed");
166 }
167
168 - try {
169 - await Future.wait([
170 - _updateBalance(),
171 - _updateNativeSOLTransactions(),
172 - _updateSPLTokenTransactions(),
173 - ]);
174 - } catch (e) {
175 - log(e.toString());
176 - }
177 -
168 _setTransactionUpdateTimer();
169
170 syncStatus = ConnectedSyncStatus();
lib/di.dart
+1
@@ -384,6 +384,7 @@ Future<void> setup({
384 yatStore: getIt.get<YatStore>(),
385 ordersStore: getIt.get<OrdersStore>(),
386 anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>(),
387 + sharedPreferences: getIt.get<SharedPreferences>(),
388 keyService: getIt.get<KeyService>()));
389
390 getIt.registerFactory<AuthService>(
lib/entities/preferences_key.dart
+1
@@ -72,4 +72,5 @@ class PreferencesKey {
72 static const lastSeenAppVersion = 'last_seen_app_version';
73 static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard';
74 static const isNewInstall = 'is_new_install';
75 + static const serviceStatusShaKey = 'service_status_sha_key';
76 }
lib/entities/service_status.dart new
+41
@@ -0,0 +1,41 @@
1 +class ServiceStatus {
2 + final String title;
3 + final String description;
4 + final String? image;
5 + final String? status;
6 + final DateTime date;
7 +
8 + ServiceStatus(
9 + {required this.title,
10 + required this.description,
11 + required this.date,
12 + this.image,
13 + this.status});
14 +
15 + factory ServiceStatus.fromJson(Map<String, dynamic> json) => ServiceStatus(
16 + title: json['title'] as String? ?? '',
17 + description: json['description'] as String? ?? '',
18 + date: DateTime.tryParse(json['date'] as String? ?? '') ?? DateTime.now(),
19 + image: json['image'] as String?,
20 + status: json['status'] as String?,
21 + );
22 +}
23 +
24 +class ServicesResponse {
25 + final List<ServiceStatus> servicesStatus;
26 + final bool hasUpdates;
27 + final String currentSha;
28 +
29 + ServicesResponse(this.servicesStatus, this.hasUpdates, this.currentSha);
30 +
31 + factory ServicesResponse.fromJson(
32 + Map<String, dynamic> json, bool hasUpdates, String currentSha) {
33 + return ServicesResponse(
34 + (json['notices'] as List? ?? [])
35 + .map((e) => ServiceStatus.fromJson(e as Map<String, dynamic>))
36 + .toList(),
37 + hasUpdates,
38 + currentSha,
39 + );
40 + }
41 +}
lib/solana/cw_solana.dart
+1 -1
@@ -110,7 +110,7 @@ class CWSolana extends Solana {
110 @override
111 List<int>? getValidationLength(CryptoCurrency type) {
112 if (type is SPLToken) {
113 - return [44];
113 + return [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44];
114 }
115
116 return null;
lib/src/screens/dashboard/dashboard_page.dart
+4
@@ -7,6 +7,7 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sideba
7 import 'package:cake_wallet/src/screens/dashboard/pages/market_place_page.dart';
8 import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
9 import 'package:cake_wallet/src/widgets/gradient_background.dart';
10 +import 'package:cake_wallet/src/widgets/services_updates_widget.dart';
11 import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.dart';
12 import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
13 import 'package:cake_wallet/utils/device_info.dart';
@@ -101,6 +102,9 @@ class _DashboardPageView extends BasePage {
102 @override
103 Widget get endDrawer => MenuWidget(dashboardViewModel);
104
105 + @override
106 + Widget leading(BuildContext context) => ServicesUpdatesWidget(dashboardViewModel.getServicesStatus());
107 +
108 @override
109 Widget middle(BuildContext context) {
110 return SyncIndicator(
lib/src/widgets/base_alert_dialog.dart
+1 -1
@@ -179,7 +179,7 @@ class BaseAlertDialog extends StatelessWidget {
179 Column(
180 crossAxisAlignment: CrossAxisAlignment.center,
181 children: <Widget>[
182 - if (headerText != null) headerTitle(context),
182 + if (headerText?.isNotEmpty ?? false) headerTitle(context),
183 Padding(
184 padding: EdgeInsets.fromLTRB(24, 20, 24, 0),
185 child: title(context),
lib/src/widgets/service_status_tile.dart new
+71
@@ -0,0 +1,71 @@
1 +import 'package:auto_size_text/auto_size_text.dart';
2 +import 'package:cake_wallet/entities/service_status.dart';
3 +import 'package:flutter/material.dart';
4 +import 'package:intl/intl.dart';
5 +
6 +class ServiceStatusTile extends StatelessWidget {
7 + final ServiceStatus status;
8 +
9 + const ServiceStatusTile(this.status, {super.key});
10 +
11 + @override
12 + Widget build(BuildContext context) {
13 + return ListTile(
14 + contentPadding: const EdgeInsets.all(8),
15 + title: Padding(
16 + padding: const EdgeInsets.symmetric(vertical: 8),
17 + child: Row(
18 + crossAxisAlignment: CrossAxisAlignment.end,
19 + children: [
20 + Expanded(
21 + child: AutoSizeText(
22 + "${status.title}${status.status != null ? " - ${status.status}" : ""}",
23 + style: TextStyle(
24 + fontSize: 16,
25 + fontFamily: 'Lato',
26 + fontWeight: FontWeight.w800,
27 + height: 1,
28 + ),
29 + maxLines: 1,
30 + textAlign: TextAlign.start,
31 + ),
32 + ),
33 + Text(
34 + _getTimeString(status.date),
35 + style: TextStyle(fontSize: 12),
36 + ),
37 + ],
38 + ),
39 + ),
40 + leading: RotatedBox(
41 + child: Icon(
42 + Icons.info,
43 + color: status.status == "resolved" ? Colors.green : Colors.red,
44 + ),
45 + quarterTurns: 2,
46 + ),
47 + subtitle: Row(
48 + children: [
49 + Expanded(child: Text(status.description)),
50 + if (status.image != null)
51 + SizedBox(
52 + height: 50,
53 + width: 50,
54 + child: Image.network(status.image!),
55 + ),
56 + ],
57 + ),
58 + );
59 + }
60 +
61 + String _getTimeString(DateTime date) {
62 + int difference = DateTime.now().difference(date).inHours;
63 + if (difference == 0) {
64 + return "few minutes ago";
65 + }
66 + if (difference < 24) {
67 + return DateFormat('h:mm a').format(date);
68 + }
69 + return DateFormat('d-MM-yyyy').format(date);
70 + }
71 +}
lib/src/widgets/services_updates_widget.dart new
+120
@@ -0,0 +1,120 @@
1 +import 'package:cake_wallet/di.dart';
2 +import 'package:cake_wallet/entities/preferences_key.dart';
3 +import 'package:cake_wallet/entities/service_status.dart';
4 +import 'package:cake_wallet/src/widgets/primary_button.dart';
5 +import 'package:cake_wallet/src/widgets/service_status_tile.dart';
6 +import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
7 +import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
8 +import 'package:flutter/material.dart';
9 +import 'package:shared_preferences/shared_preferences.dart';
10 +import 'package:url_launcher/url_launcher.dart';
11 +
12 +class ServicesUpdatesWidget extends StatelessWidget {
13 + final Future<ServicesResponse> servicesResponse;
14 +
15 + const ServicesUpdatesWidget(this.servicesResponse, {super.key});
16 +
17 + @override
18 + Widget build(BuildContext context) {
19 + return Padding(
20 + padding: const EdgeInsets.all(8.0),
21 + child: FutureBuilder<ServicesResponse>(
22 + future: servicesResponse,
23 + builder: (context, state) {
24 + return InkWell(
25 + onTap: state.hasData
26 + ? () {
27 + // save currentSha when the user see the status
28 + getIt
29 + .get<SharedPreferences>()
30 + .setString(PreferencesKey.serviceStatusShaKey, state.data!.currentSha);
31 +
32 + showModalBottomSheet(
33 + context: context,
34 + shape: RoundedRectangleBorder(
35 + borderRadius: BorderRadius.only(
36 + topLeft: Radius.circular(50),
37 + topRight: Radius.circular(50),
38 + ),
39 + ),
40 + constraints: BoxConstraints(
41 + maxHeight: MediaQuery.of(context).size.height / 2,
42 + minHeight: MediaQuery.of(context).size.height / 4,
43 + ),
44 + builder: (context) {
45 + Widget body;
46 + if (state.data!.servicesStatus.isEmpty) {
47 + body = Center(
48 + child: Text("Everything is up and running as expected"),
49 + );
50 + } else {
51 + body = SingleChildScrollView(
52 + child: Column(
53 + children: state.data!.servicesStatus
54 + .map((status) => ServiceStatusTile(status))
55 + .toList()),
56 + );
57 + }
58 + return Padding(
59 + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 20),
60 + child: Stack(
61 + children: [
62 + body,
63 + Align(
64 + alignment: Alignment.bottomCenter,
65 + child: Padding(
66 + padding: EdgeInsets.symmetric(
67 + horizontal: MediaQuery.of(context).size.width / 8),
68 + child: PrimaryImageButton(
69 + onPressed: () {
70 + try {
71 + launchUrl(Uri.parse("https://status.cakewallet.com/"));
72 + } catch (_) {}
73 + },
74 + image: Image.asset(
75 + "assets/images/status_website_image.png",
76 + color: Theme.of(context).brightness == Brightness.light
77 + ? Colors.white
78 + : null,
79 + ),
80 + text: "Status Website",
81 + color: Theme.of(context)
82 + .extension<WalletListTheme>()!
83 + .createNewWalletButtonBackgroundColor,
84 + textColor: Theme.of(context)
85 + .extension<WalletListTheme>()!
86 + .restoreWalletButtonTextColor,
87 + ),
88 + ),
89 + )
90 + ],
91 + ),
92 + );
93 + },
94 + );
95 + }
96 + : null,
97 + child: Stack(
98 + children: [
99 + Image.asset(
100 + "assets/images/notification_icon.png",
101 + color: Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
102 + ),
103 + if (state.hasData && state.data!.hasUpdates)
104 + Container(
105 + height: 7,
106 + width: 7,
107 + margin: EdgeInsetsDirectional.only(start: 8),
108 + decoration: BoxDecoration(
109 + color: Colors.red,
110 + shape: BoxShape.circle,
111 + ),
112 + ),
113 + ],
114 + ),
115 + );
116 + },
117 + ),
118 + );
119 + }
120 +}
lib/view_model/dashboard/dashboard_view_model.dart
+28 -1
@@ -4,8 +4,10 @@ import 'package:cake_wallet/buy/buy_provider.dart';
4 import 'package:cake_wallet/core/key_service.dart';
5 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
6 import 'package:cake_wallet/entities/balance_display_mode.dart';
7 +import 'package:cake_wallet/entities/preferences_key.dart';
8 import 'package:cake_wallet/entities/provider_types.dart';
9 import 'package:cake_wallet/entities/exchange_api_mode.dart';
10 +import 'package:cake_wallet/entities/service_status.dart';
11 import 'package:cake_wallet/exchange/exchange_provider_description.dart';
12 import 'package:cake_wallet/generated/i18n.dart';
13 import 'package:cake_wallet/monero/monero.dart';
@@ -42,7 +44,8 @@ import 'package:cw_core/wallet_type.dart';
44 import 'package:eth_sig_util/util/utils.dart';
45 import 'package:flutter/services.dart';
46 import 'package:mobx/mobx.dart';
45 -import 'package:cake_wallet/entities/provider_types.dart';
47 +import 'package:http/http.dart' as http;
48 +import 'package:shared_preferences/shared_preferences.dart';
49
50 part 'dashboard_view_model.g.dart';
51
@@ -59,6 +62,7 @@ abstract class DashboardViewModelBase with Store {
62 required this.yatStore,
63 required this.ordersStore,
64 required this.anonpayTransactionsStore,
65 + required this.sharedPreferences,
66 required this.keyService})
67 : hasSellAction = false,
68 hasBuyAction = false,
@@ -280,6 +284,7 @@ abstract class DashboardViewModelBase with Store {
284 bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven;
285
286 final KeyService keyService;
287 + final SharedPreferences sharedPreferences;
288
289 BalanceViewModel balanceViewModel;
290
@@ -497,4 +502,26 @@ abstract class DashboardViewModelBase with Store {
502
503 return affectedWallets;
504 }
505 +
506 + Future<ServicesResponse> getServicesStatus() async {
507 + try {
508 + final res = await http.get(Uri.parse("https://service-api.cakewallet.com/v1/active-notices"));
509 +
510 + if (res.statusCode < 200 || res.statusCode >= 300) {
511 + throw res.body;
512 + }
513 +
514 + final oldSha = sharedPreferences.getString(PreferencesKey.serviceStatusShaKey);
515 +
516 +
517 + final hash = await Cryptography.instance.sha256().hash(utf8.encode(res.body));
518 + final currentSha = bytesToHex(hash.bytes);
519 +
520 + final hasUpdates = oldSha != currentSha;
521 +
522 + return ServicesResponse.fromJson(json.decode(res.body) as Map<String, dynamic>, hasUpdates, currentSha);
523 + } catch (_) {
524 + return ServicesResponse([], false, '');
525 + }
526 + }
527 }
lib/view_model/send/send_view_model.dart
+2 -1
@@ -427,9 +427,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
427 ) {
428 if (walletType == WalletType.ethereum ||
429 walletType == WalletType.polygon ||
430 + walletType == WalletType.solana ||
431 walletType == WalletType.haven) {
432 if (error.contains('gas required exceeds allowance') ||
432 - error.contains('insufficient funds for')) {
433 + error.contains('insufficient funds')) {
434 return S.current.do_not_have_enough_gas_asset(currency.toString());
435 }
436
scripts/android/build_monero.sh
+1 -1
@@ -1,7 +1,7 @@
1 #!/bin/sh
2
3 . ./config.sh
4 -MONERO_BRANCH=release-v0.18.2.2-android
4 +MONERO_BRANCH=release-v0.18.2.2-android_tx_priority_fix
5 MONERO_SRC_DIR=${WORKDIR}/monero
6
7 git clone https://github.com/cake-tech/monero.git ${MONERO_SRC_DIR} --branch ${MONERO_BRANCH}
scripts/ios/build_monero.sh
+1 -1
@@ -4,7 +4,7 @@
4
5 MONERO_URL="https://github.com/cake-tech/monero.git"
6 MONERO_DIR_PATH="${EXTERNAL_IOS_SOURCE_DIR}/monero"
7 -MONERO_VERSION=release-v0.18.2.2
7 +MONERO_VERSION=release-v0.18.2.2_tx_priority_fix
8 BUILD_TYPE=release
9 PREFIX=${EXTERNAL_IOS_DIR}
10 DEST_LIB_DIR=${EXTERNAL_IOS_LIB_DIR}/monero
scripts/ios/build_sodium.sh
+5 -4
@@ -8,9 +8,10 @@ SODIUM_URL="https://github.com/jedisct1/libsodium.git"
8 echo "============================ SODIUM ============================"
9
10 echo "Cloning SODIUM from - $SODIUM_URL"
11 -git clone $SODIUM_URL $SODIUM_PATH --branch stable
11 +git clone $SODIUM_URL $SODIUM_PATH
12 cd $SODIUM_PATH
13 -./dist-build/ios.sh
13 +git checkout 443617d7507498f7477703f0b51cb596d4539262
14 +./dist-build/apple-xcframework.sh
15
15 -mv ${SODIUM_PATH}/libsodium-ios/include/* $EXTERNAL_IOS_INCLUDE_DIR
16 -mv ${SODIUM_PATH}/libsodium-ios/lib/* $EXTERNAL_IOS_LIB_DIR
\ No newline at end of file
16 +mv ${SODIUM_PATH}/libsodium-apple/ios/include/* $EXTERNAL_IOS_INCLUDE_DIR
17 +mv ${SODIUM_PATH}/libsodium-apple/ios/lib/* $EXTERNAL_IOS_LIB_DIR
\ No newline at end of file
scripts/macos/build_monero.sh
+1 -1
@@ -4,7 +4,7 @@
4
5 MONERO_URL="https://github.com/cake-tech/monero.git"
6 MONERO_DIR_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/monero"
7 -MONERO_VERSION=release-v0.18.2.2
7 +MONERO_VERSION=release-v0.18.2.2_tx_priority_fix
8 BUILD_TYPE=release
9 PREFIX=${EXTERNAL_MACOS_DIR}
10 DEST_LIB_DIR=${EXTERNAL_MACOS_LIB_DIR}/monero