Files
hash-wallet/lib/view_model/link_view_model.dart
T
3b100eae33 CW1612: AnyPay Service Layer (#3516)
* fix android CI

* feat: add anypay core models, parser and routing engine

* feat: add anypay resolver, anypay service and wallet switch service

* refactor: route the send page payment flow through anypay service

* fix: show message when there's no swap providers for a pair and reset the trade guard on dismissal

* refactor: merge the swap from network and send to network decision pages together and clean up anypay widgets

* feat: reapply deep links to a currently open send flow and register the solana scheme

* feat: add support for zcash address detection to anypay and other minor fixes

* refactor: remove trailing icon

* feat: hide swap flow for sp and mweb addresses

* feat: enhance zcash address detection and deeplink handling

* fix: pin bitcoin_base

* refactor: Move the anypay decision navigation out of the send page and address minor review comments

* refactor: Move the anypay decision navigation out of the send page and address minor review comments

* feat: handle exception in switch service and mvoe balance compute to call site

* refactor: combine anypay entry points to one and extract selected mode widget

---------

Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
2026-08-20 09:35:44 +02:00

182 lines
4.9 KiB
Dart

import 'dart:async';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class LinkViewModel {
LinkViewModel({
required this.settingsStore,
required this.appStore,
required this.authenticationStore,
required this.navigatorKey,
});
final SettingsStore settingsStore;
final AppStore appStore;
final AuthenticationStore authenticationStore;
final GlobalKey<NavigatorState> navigatorKey;
Uri? currentLink;
bool readyToOpenPage = false;
final StreamController<Uri> _incomingPaymentLinksController = StreamController<Uri>.broadcast();
Stream<Uri> get incomingPaymentLinks => _incomingPaymentLinksController.stream;
bool get _isValidPaymentUri => currentLink?.path.isNotEmpty ?? false;
bool get isWalletConnectLink => currentLink?.authority == 'wc';
bool get isNanoGptLink => currentLink?.scheme == 'nano-gpt';
bool get isQuickActionLink =>
currentLink?.scheme == 'cakewallet' && currentLink?.host == 'quickaction';
String? getRouteToGo() {
if (isWalletConnectLink) {
if (!isWalletConnectCompatibleChain(appStore.wallet!.type)) {
_errorToast(S.current.switchToWCCompatibleWallet(walletConnectCompatibleChainsLabel()));
return null;
}
return Routes.walletConnectConnectionsListing;
}
// Check for a quick action first.
if (isQuickActionLink) {
final action = currentLink!.pathSegments.isNotEmpty ? currentLink!.pathSegments.first : null;
switch (action) {
case 'send':
return Routes.send;
case 'receive':
return Routes.newReceivePage;
default:
return null;
}
}
if (authenticationStore.state == AuthenticationState.uninitialized) {
return null;
}
if (isNanoGptLink) {
switch (currentLink?.authority ?? '') {
case "exchange":
return Routes.exchange;
case "send":
return Routes.send;
case "buy":
return Routes.buySellPage;
}
}
if (_isValidPaymentUri) {
return Routes.send;
}
return null;
}
dynamic getRouteArgs() {
if (isWalletConnectLink) {
return currentLink;
}
if (isQuickActionLink) {
return null;
}
if (isNanoGptLink) {
switch (currentLink?.authority ?? '') {
case "exchange":
return PaymentRequest.fromUri(currentLink);
case "send":
return {"paymentRequest": PaymentRequest.fromUri(currentLink)};
case "buy":
return true;
}
}
if (_isValidPaymentUri) {
return {
"paymentRequest": PaymentRequest.fromUri(currentLink),
"rawLink": currentLink.toString(),
};
}
return null;
}
Future<void> _errorToast(String message, {double fontSize = 16}) async {
try {
await Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.SNACKBAR,
backgroundColor: Colors.black,
textColor: Colors.white,
fontSize: fontSize,
);
} catch (_) {}
}
Future<void> markReadyToOpenPage() async {
readyToOpenPage = true;
await handleLink();
}
Future<void> handleLink() async {
if (!readyToOpenPage) {
return;
}
String? route = getRouteToGo();
dynamic args = getRouteArgs();
if (route != null) {
if (appStore.wallet == null) {
return;
}
if (route == Routes.send &&
!isQuickActionLink &&
currentLink != null &&
_incomingPaymentLinksController.hasListener) {
final link = currentLink!;
currentLink = null;
_incomingPaymentLinksController.add(link);
return;
}
// Prevent navigating to the same route again and clear the link so a later auth
// event cannot refire it.
if (appStore.currentRouteName == route) {
currentLink = null;
return;
}
if (isNanoGptLink) {
if (route == Routes.buySellPage || route == Routes.exchange) {
await _errorToast(S.current.nano_gpt_thanks_message, fontSize: 14);
}
}
// Quick actions must reset navigation to Dashboard → TargetPage
if (isQuickActionLink) {
currentLink = null;
navigatorKey.currentState?.pushNamedAndRemoveUntil(
route,
ModalRoute.withName(Routes.dashboard),
arguments: args,
);
return;
}
// Normal navigation flow
currentLink = null;
navigatorKey.currentState?.pushNamed(route, arguments: args);
}
}
}