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