mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-19 05:58:50 +02:00
feat: Pay Anything from Any Wallet (#2450)
* feat: Pay Anything from Any Wallet * feat: Trigger payment flow when address is pasted * chore: Remove unneeded changes * feat Expand address checks and fix scanning bch qrs * fix: Add support for dogecoin * feat: More modifications for Pay from Any Wallet This change: - Adds the pay from any wallet flow to externally signed QR codes - Adds fixes for review comments - Fixes failing tests --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Omar Hatem
parent
16f058e9e2
commit
13a74133f5
@@ -0,0 +1,124 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cake_wallet/core/universal_address_detector.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'payment_view_model.g.dart';
|
||||
|
||||
class PaymentViewModel = PaymentViewModelBase with _$PaymentViewModel;
|
||||
|
||||
abstract class PaymentViewModelBase with Store {
|
||||
PaymentViewModelBase({
|
||||
required this.appStore,
|
||||
required this.walletInfoSource,
|
||||
});
|
||||
|
||||
final AppStore appStore;
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
|
||||
@observable
|
||||
WalletType? detectedWalletType;
|
||||
|
||||
@observable
|
||||
bool isProcessing = false;
|
||||
|
||||
@computed
|
||||
WalletType get currentWalletType => appStore.wallet!.type;
|
||||
|
||||
/// Main entry point - detect address type and check compatibility
|
||||
@action
|
||||
Future<PaymentFlowResult> processAddress(String addressData) async {
|
||||
try {
|
||||
detectedWalletType = null;
|
||||
isProcessing = true;
|
||||
|
||||
// Detect address type
|
||||
final detectionResult = UniversalAddressDetector.detectAddress(addressData);
|
||||
|
||||
if (!detectionResult.isValid || detectionResult.detectedWalletType == null) {
|
||||
return PaymentFlowResult.incompatible('Unable to detect address type');
|
||||
}
|
||||
|
||||
detectedWalletType = detectionResult.detectedWalletType!;
|
||||
|
||||
// Check if current wallet is compatible
|
||||
final currentWallet = appStore.wallet;
|
||||
if (currentWallet != null && currentWallet.type == detectedWalletType) {
|
||||
return PaymentFlowResult.currentWalletCompatible();
|
||||
}
|
||||
|
||||
final compatibleWallets = getWalletsByType(detectedWalletType!);
|
||||
|
||||
switch (compatibleWallets.length) {
|
||||
case 0:
|
||||
return PaymentFlowResult.noWallets(detectedWalletType!);
|
||||
case 1:
|
||||
return PaymentFlowResult.singleWallet(compatibleWallets.first);
|
||||
default:
|
||||
return PaymentFlowResult.multipleWallets(compatibleWallets);
|
||||
}
|
||||
} catch (e) {
|
||||
printV('PaymentViewModel error: $e');
|
||||
return PaymentFlowResult.error('Payment processing failed: $e');
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
List<WalletInfo> getWalletsByType(WalletType walletType) {
|
||||
return walletInfoSource.values.where((wallet) => wallet.type == walletType).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class PaymentFlowResult {
|
||||
final PaymentFlowType type;
|
||||
final String? message;
|
||||
final WalletInfo? wallet;
|
||||
final List<WalletInfo> wallets;
|
||||
final WalletType? walletType;
|
||||
|
||||
PaymentFlowResult._({
|
||||
required this.type,
|
||||
this.message,
|
||||
this.wallet,
|
||||
this.wallets = const [],
|
||||
this.walletType,
|
||||
});
|
||||
|
||||
/// Current wallet is compatible
|
||||
factory PaymentFlowResult.currentWalletCompatible() => PaymentFlowResult._(type: PaymentFlowType.currentWalletCompatible);
|
||||
|
||||
/// Single compatible wallet available
|
||||
factory PaymentFlowResult.singleWallet(WalletInfo wallet) =>
|
||||
PaymentFlowResult._(type: PaymentFlowType.singleWallet, wallet: wallet);
|
||||
|
||||
/// Multiple compatible wallets available
|
||||
factory PaymentFlowResult.multipleWallets(List<WalletInfo> wallets) =>
|
||||
PaymentFlowResult._(type: PaymentFlowType.multipleWallets, wallets: wallets);
|
||||
|
||||
/// No compatible wallets available
|
||||
factory PaymentFlowResult.noWallets(WalletType walletType) =>
|
||||
PaymentFlowResult._(type: PaymentFlowType.noWallets, walletType: walletType);
|
||||
|
||||
/// Error occurred
|
||||
factory PaymentFlowResult.error(String message) =>
|
||||
PaymentFlowResult._(type: PaymentFlowType.error, message: message);
|
||||
|
||||
/// Incompatible address
|
||||
factory PaymentFlowResult.incompatible(String message) =>
|
||||
PaymentFlowResult._(type: PaymentFlowType.incompatible, message: message);
|
||||
}
|
||||
|
||||
enum PaymentFlowType {
|
||||
currentWalletCompatible,
|
||||
singleWallet,
|
||||
multipleWallets,
|
||||
noWallets,
|
||||
error,
|
||||
incompatible,
|
||||
}
|
||||
@@ -37,7 +37,9 @@ import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/core/wallet_loading_service.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'wallet_switcher_view_model.g.dart';
|
||||
|
||||
class WalletSwitcherViewModel = WalletSwitcherViewModelBase with _$WalletSwitcherViewModel;
|
||||
|
||||
abstract class WalletSwitcherViewModelBase with Store {
|
||||
WalletSwitcherViewModelBase({
|
||||
required this.appStore,
|
||||
required this.walletLoadingService,
|
||||
required this.walletInfoSource,
|
||||
});
|
||||
|
||||
final AppStore appStore;
|
||||
final WalletLoadingService walletLoadingService;
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
|
||||
@observable
|
||||
WalletInfo? selectedWallet;
|
||||
|
||||
@observable
|
||||
bool isProcessing = false;
|
||||
|
||||
@action
|
||||
List<WalletInfo> getWallets(WalletType? walletType) {
|
||||
if (walletType == null) {
|
||||
return walletInfoSource.values.toList();
|
||||
}
|
||||
|
||||
return walletInfoSource.values.where((wallet) => wallet.type == walletType).toList();
|
||||
}
|
||||
|
||||
@action
|
||||
void selectWallet(WalletInfo walletInfo) => selectedWallet = walletInfo;
|
||||
|
||||
@action
|
||||
Future<bool> switchToSelectedWallet() async {
|
||||
if (selectedWallet == null) return false;
|
||||
|
||||
try {
|
||||
isProcessing = true;
|
||||
|
||||
final wallet = await walletLoadingService.load(selectedWallet!.type, selectedWallet!.name);
|
||||
|
||||
await appStore.changeCurrentWallet(wallet);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
printV('Failed to switch wallet: $e');
|
||||
return false;
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user