Add Radar to Apps screen (#3366)
* Add Radar to Apps screen * ignore flutter error [skip ci]
Omar Hatem committed
Jul 11, 2026 at 04:57 UTC
38acc625f0b772400c665cc426546209dcad186c
8 files changed
+65
-21
assets/images/radar.png
Binary files /dev/null and b/assets/images/radar.png differ
cw_core/lib/payment_uris.dart
+7
@@ -7,6 +7,13 @@ abstract class PaymentURI {
7
final String address;
8
}
9
10
+class ExternalAddressURI extends PaymentURI {
11
+ ExternalAddressURI({required super.amount, required super.address});
12
+
13
+ @override
14
+ String toString() => address;
15
+}
16
+
17
class MoneroURI extends PaymentURI {
18
MoneroURI({required super.amount, required super.address});
19
lib/new-ui/widgets/send_page/l2_send_external_modal.dart
+2
-7
@@ -5,13 +5,12 @@ import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
5
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
6
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
7
import 'package:cake_wallet/utils/address_formatter.dart';
8
+import 'package:cake_wallet/utils/clipboard_util.dart';
9
import 'package:cake_wallet/view_model/send/send_view_model.dart';
10
import 'package:cw_core/payment_uris.dart';
11
import 'package:flutter/cupertino.dart';
12
import 'package:flutter/material.dart';
13
import "package:cw_core/wallet_type.dart";
13
-import 'package:flutter/services.dart';
14
-import 'package:flutter_svg/flutter_svg.dart';
14
15
class L2SendExternalModal extends StatefulWidget {
16
const L2SendExternalModal({super.key, required this.sendViewModel});
@@ -180,11 +179,7 @@ class _L2SendExternalModalState extends State<L2SendExternalModal> {
179
children: [
180
Flexible(
181
child: NewPrimaryButton(
183
- onPressed:(){
184
- Clipboard.setData(
185
- ClipboardData(text: output.address),
186
- );
187
- },
182
+ onPressed: () => ClipboardUtil.copyToClipboard(context, output.address),
183
text: S.of(context).copy,
184
color: Theme.of(context).colorScheme.primary,
185
textColor: Theme.of(context).colorScheme.onPrimary),
lib/new-ui/widgets/swap_page/swap_send_external_modal.dart
+4
-4
@@ -4,11 +4,11 @@ import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
4
import 'package:cake_wallet/new-ui/widgets/swap_page/swap_modal_header.dart';
5
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
6
import 'package:cake_wallet/utils/address_formatter.dart';
7
+import 'package:cake_wallet/utils/clipboard_util.dart';
8
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
9
import 'package:cw_core/crypto_currency.dart';
10
import 'package:cw_core/payment_uris.dart';
11
import 'package:flutter/material.dart';
11
-import 'package:flutter/services.dart';
12
13
class SwapSendExternalModal extends StatefulWidget {
14
const SwapSendExternalModal(
@@ -45,7 +45,7 @@ class _SwapSendExternalModalState extends State<SwapSendExternalModal> {
45
46
@override
47
Widget build(BuildContext context) {
48
- if (uri == null) return SizedBox.shrink();
48
+ if (widget.address.isEmpty) return SizedBox.shrink();
49
final resolvedSize = MediaQuery.of(context).size.width * (largeQrMode ? 0.8 : 0.54);
50
51
return PopScope(
@@ -139,7 +139,7 @@ class _SwapSendExternalModalState extends State<SwapSendExternalModal> {
139
child: QrImage(
140
size: animatedSize,
141
embeddedImagePath: widget.from.iconPath,
142
- data: uri.toString(),
142
+ data: uri?.toString() ?? widget.address,
143
),
144
),
145
);
@@ -175,7 +175,7 @@ class _SwapSendExternalModalState extends State<SwapSendExternalModal> {
175
children: [
176
Flexible(
177
child: NewPrimaryButton(
178
- onPressed: ()=> Clipboard.setData(ClipboardData(text:widget.address)),
178
+ onPressed: () => ClipboardUtil.copyToClipboard(context, widget.address),
179
text: S.of(context).copy,
180
color: Theme.of(context).colorScheme.primary,
181
textColor: Theme.of(context).colorScheme.onPrimary),
lib/src/screens/dashboard/pages/cake_features_page.dart
+9
@@ -148,6 +148,15 @@ class CakeFeaturesPage extends StatelessWidget {
148
subTitle: "Turn your old phone into your new hardware wallet with our new app",
149
image: 'assets/images/cupcake.png',
150
),
151
+ AppsWidget(
152
+ isWide: true,
153
+ isLink: true,
154
+ isCake: true,
155
+ onTap: () => _launchUrl("radar.chat"),
156
+ title: "Radar",
157
+ subTitle: "Your messages. Your Bitcoin. Together, at last",
158
+ image: 'assets/images/radar.png',
159
+ ),
160
const SizedBox(height: 12),
161
Padding(
162
padding: const EdgeInsets.only(left: 24, top: 16, bottom: 8),
lib/utils/clipboard_util.dart
+18
-2
@@ -1,6 +1,8 @@
1
-import 'package:flutter/services.dart';
2
-
1
+import 'package:cake_wallet/generated/i18n.dart';
2
import 'package:cake_wallet/utils/device_info.dart';
3
+import 'package:cake_wallet/utils/show_bar.dart';
4
+import 'package:flutter/material.dart';
5
+import 'package:flutter/services.dart';
6
import 'package:sensitive_clipboard/sensitive_clipboard.dart';
7
8
class ClipboardUtil {
@@ -12,4 +14,18 @@ class ClipboardUtil {
14
15
return Clipboard.setData(data);
16
}
17
+
18
+ /// Copies [text] to the clipboard and shows a "Copied to Clipboard"
19
+ /// confirmation toast. Prefer this for any user-facing copy action so the
20
+ /// feedback stays consistent across the app.
21
+ static Future<void> copyToClipboard(
22
+ BuildContext context,
23
+ String text, {
24
+ bool isSensitive = false,
25
+ }) async {
26
+ await setSensitiveDataToClipboard(ClipboardData(text: text), isSensitive: isSensitive);
27
+ if (context.mounted) {
28
+ showBar<void>(context, S.of(context).copied_to_clipboard);
29
+ }
30
+ }
31
}
lib/utils/exception_handler.dart
+18
-5
@@ -299,14 +299,15 @@ class ExceptionHandler {
299
"FocusScopeNode was used after being disposed",
300
"_getDismissibleFlushbar",
301
"_QueuedFuture.execute (package:universal_ble/src/queue.dart:65)",
302
+ "Pending Request Canceled | RequestQueue disposed",
303
"reown_core/relay_client/websocket/websocket_handler.dart",
304
"Image upload failed due to loss of GPU access",
305
"transport error",
306
"SdkError.sparkError(field0: Operator RPC error: Connection error: status: Unavailable, message: \"dns error\", details: []",
307
"the timeout of the request was reached",
308
308
- "support for coin removed, your seedphrase:"
309
- "Exception: Invalid image data"
309
+ "support for coin removed, your seedphrase:",
310
+ "Exception: Invalid image data",
311
];
312
313
static Future<void> _addDeviceInfo(File file) async {
@@ -441,8 +442,20 @@ class ExceptionHandler {
442
}
443
444
static bool _flutterErrorIgnore(FlutterErrorDetails errorDetails) {
444
- // most probably a flutter context error so just ignore it if there is no stack we can debug with
445
- return errorDetails.exception.toString().contains("Null check operator used on a null value") &&
446
- errorDetails.stack == null;
445
+ if (errorDetails.exception.toString().contains("Null check operator used on a null value")) {
446
+ // Most probably a flutter context error so just ignore it if there is no
447
+ // stack we can debug with.
448
+ if (errorDetails.stack == null) {
449
+ return true;
450
+ }
451
+
452
+ final stack = errorDetails.stack.toString();
453
+ if (stack.contains("handleFocusHighlightModeChange") ||
454
+ stack.contains("_HighlightModeManager")) {
455
+ return true;
456
+ }
457
+ }
458
+
459
+ return false;
460
}
461
}
lib/view_model/exchange/exchange_trade_view_model.dart
+7
-3
@@ -415,9 +415,13 @@ abstract class ExchangeTradeViewModelBase with Store {
415
return null;
416
}
417
418
- // Using trade currency here so the external-send QR encodes the correct scheme
419
- final uriWalletType =
420
- cryptoCurrencyOrTokenToWalletType(fromCurrency) ?? wallet.type;
418
+ // Using the trade's `from` currency so the external-send QR encodes the correct scheme.
419
+ final uriWalletType = cryptoCurrencyOrTokenToWalletType(fromCurrency);
420
+
421
+ // for other currencies that we don't have wallets for
422
+ if (uriWalletType == null) {
423
+ return ExternalAddressURI(address: inputAddress, amount: amount);
424
+ }
425
426
printV(uriWalletType);
427