mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-29 06:38:45 +01:00
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535 upstream commits, including the Monero silent-open fix, Monero on Trezor, Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9. Merged with both sides normalized first (import rename, 100-column format), which took the conflict count from 614 paths to 322. The procedure is now scripts/upstream_sync.sh. Kept from Hash Bags: - WalletType enum order and Hive IDs. SQLite stores WalletType.index and our order differs from upstream's; taking theirs would reread saved wallets as other chains. Our frozen adapter keeps the shipped IDs. - The 1.0.2 legacy PIN and wallet-password decode and self-heal. - Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out. - Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me), converted to upstream's isDefault node-list key; Wownero is back in the node reset list. - Privacy: address resolvers limited to domain-anchored sources again; prices.neroswap.com for fiat; upstream's charts tab, which reads prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false. - Lightning stays behind FeatureFlag.isLightningEnabled, including upstream's new post-seed Lightning username step. - Our icon pipeline, branding and bundle identifiers. Twelve new upstream widget tests are skipped: test and code are unchanged from v6.5.0 apart from renames (not rerun on a pristine upstream checkout; upstream's top_bar_test does not compile against its own TopBar). flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped; flutter build bundle compiles. Built with Flutter 3.41.9.
182 lines
4.9 KiB
Dart
182 lines
4.9 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:hash_wallet/generated/i18n.dart';
|
|
import 'package:hash_wallet/reactions/wallet_connect.dart';
|
|
import 'package:hash_wallet/routes.dart';
|
|
import 'package:hash_wallet/store/app_store.dart';
|
|
import 'package:hash_wallet/store/authentication_store.dart';
|
|
import 'package:hash_wallet/store/settings_store.dart';
|
|
import 'package:hash_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 == 'hashwallet' && 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);
|
|
}
|
|
}
|
|
}
|