diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml
index 36fb28a25..99a489bb1 100644
--- a/.github/workflows/pr_test_build_linux.yml
+++ b/.github/workflows/pr_test_build_linux.yml
@@ -118,7 +118,7 @@ jobs:
pushd scripts/monero_c/release
mkdir -p $(git describe --tags)
pushd $(git describe --tags)
- wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC1/release-bundle.zip
+ wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC2/release-bundle.zip
unzip release-bundle.zip
rm release-bundle.zip
popd
diff --git a/.gitignore b/.gitignore
index 9fce14fcf..268167bc4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -225,3 +225,5 @@ scripts/zcash_lib
scripts/android/yttrium
scripts/bitbox_flutter
scripts/reown_flutter
+
+*.salive
\ No newline at end of file
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 6c1540222..26f5544c9 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -60,7 +60,7 @@ android {
defaultConfig {
applicationId appProperties['id']
- minSdkVersion 24
+ minSdkVersion 26
targetSdkVersion 36
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml
index a7364d670..0f15c75bc 100644
--- a/android/app/src/main/AndroidManifestBase.xml
+++ b/android/app/src/main/AndroidManifestBase.xml
@@ -128,6 +128,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/images/hardware_wallet/device_ledger_nano_gen_5.svg b/assets/images/hardware_wallet/device_ledger_nano_gen_5.svg
deleted file mode 100644
index 9d2fc8bbd..000000000
--- a/assets/images/hardware_wallet/device_ledger_nano_gen_5.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/assets/images/hardware_wallet/device_trezor_safe_5.svg b/assets/images/hardware_wallet/device_trezor_safe_5.svg
deleted file mode 100644
index 14c0329be..000000000
--- a/assets/images/hardware_wallet/device_trezor_safe_5.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/assets/images/hardware_wallet/ledger_flex.png b/assets/images/hardware_wallet/ledger_flex.png
deleted file mode 100644
index fa39f241f..000000000
Binary files a/assets/images/hardware_wallet/ledger_flex.png and /dev/null differ
diff --git a/assets/images/hardware_wallet/ledger_nano_s.png b/assets/images/hardware_wallet/ledger_nano_s.png
deleted file mode 100644
index 02777aeb6..000000000
Binary files a/assets/images/hardware_wallet/ledger_nano_s.png and /dev/null differ
diff --git a/assets/images/hardware_wallet/ledger_stax.png b/assets/images/hardware_wallet/ledger_stax.png
deleted file mode 100644
index 06c9c848e..000000000
Binary files a/assets/images/hardware_wallet/ledger_stax.png and /dev/null differ
diff --git a/cw_core/lib/hardware/device_connection_type.dart b/cw_core/lib/hardware/device_connection_type.dart
index 39e0400c9..3b1dd9349 100644
--- a/cw_core/lib/hardware/device_connection_type.dart
+++ b/cw_core/lib/hardware/device_connection_type.dart
@@ -29,7 +29,7 @@ enum DeviceConnectionType {
break;
case HardwareWalletType.trezor:
isSupported = [
- // WalletType.monero,
+ WalletType.monero,
WalletType.bitcoin,
WalletType.litecoin,
WalletType.ethereum,
diff --git a/cw_core/lib/hardware/hardware_wallet_service.dart b/cw_core/lib/hardware/hardware_wallet_service.dart
index 19125ba84..94cbdc443 100644
--- a/cw_core/lib/hardware/hardware_wallet_service.dart
+++ b/cw_core/lib/hardware/hardware_wallet_service.dart
@@ -4,7 +4,8 @@ import 'dart:typed_data';
import 'package:cw_core/hardware/hardware_account_data.dart';
abstract class HardwareWalletService {
- Future> getAvailableAccounts({int index = 0, int limit = 5});
+ Future> getAvailableAccounts({int index = 0, int limit = 5}) =>
+ throw UnimplementedError();
Future signTransaction({required String transaction}) => throw UnimplementedError();
diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart
index 7550fc626..b7f2703b8 100644
--- a/cw_core/lib/node.dart
+++ b/cw_core/lib/node.dart
@@ -132,6 +132,26 @@ class Node {
};
}
+ Node.fromUri(Uri uri, WalletType type)
+ : id = 0,
+ uriRaw =
+ "${uri.host}${uri.hasPort ? ':${uri.port}' : (uri.queryParameters['port']?.isNotEmpty == true ? ':${uri.queryParameters['port']}' : '')}",
+ path = uri.path,
+ login = uri.userInfo.contains(':')
+ ? uri.userInfo.split(':')[0]
+ : uri.queryParameters['username'],
+ password = uri.userInfo.contains(':')
+ ? uri.userInfo.split(':')[1]
+ : uri.queryParameters['password'],
+ useSSL = uri.queryParameters['protocol'] == 'https',
+ trusted = uri.queryParameters['trusted'] == 'true',
+ isPow = false,
+ isEnabledForAutoSwitching = false,
+ isOfficial = false,
+ isBuiltin = false,
+ isDefault = false,
+ typeRaw = serializeToInt(type);
+
Future delete() async {
return await db!.delete(tableName, where: '${selfIdColumn} = ?', whereArgs: [id]);
}
@@ -378,14 +398,14 @@ class Node {
final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName};
try {
- final client = ProxyWrapper().getHttpIOClient();
final jsonBody = json.encode(body);
- final response = await client.post(
- rpcUri,
+ final response = await ProxyWrapper().post(
+ clearnetUri: rpcUri,
headers: {'Content-Type': 'application/json'},
body: jsonBody,
+ allowMitmMoneroBypassSSLCheck: true
);
// Check if we received a 401 Unauthorized response
if (response.statusCode == 401) {
diff --git a/cw_core/lib/node_list.dart b/cw_core/lib/node_list.dart
index e46f0eee4..2c5007bea 100644
--- a/cw_core/lib/node_list.dart
+++ b/cw_core/lib/node_list.dart
@@ -1,5 +1,4 @@
import 'package:flutter/services.dart';
-import 'package:hive/hive.dart';
import "package:yaml/yaml.dart";
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_type.dart';
@@ -78,6 +77,7 @@ Future> loadDefaultNodes(WalletType type) async {
map["isBuiltin"] = 1;
map["isOfficial"] = ((map["isOfficial"] as bool? ?? false) ? 1 : 0);
map["isDefault"] = ((map["isDefault"] as bool? ?? false) ? 1 : 0);
+
final node = Node.fromMap(Map.from(map));
node.type = type;
nodes.add(node);
@@ -94,7 +94,11 @@ Future> loadDefaultNanoPowNodes() async {
for (final raw in loadedPowNodes) {
if (raw is Map) {
- final node = Node.fromMap(Map.from(raw));
+ final map = Map.from(raw);
+ map["isBuiltin"] = 1;
+ map["isOfficial"] = ((map["isOfficial"] as bool? ?? false) ? 1 : 0);
+ map["isDefault"] = ((map["isDefault"] as bool? ?? false) ? 1 : 0);
+ final node = Node.fromMap(Map.from(map));
node.isPow = true;
node.type = WalletType.nano;
nodes.add(node);
diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart
index 1ce397267..5877ad2da 100644
--- a/cw_core/lib/wallet_info.dart
+++ b/cw_core/lib/wallet_info.dart
@@ -44,7 +44,7 @@ enum HardwareWalletType {
coldcard,
seedsigner,
keystone,
- trezor,
+ trezor;
}
enum WalletInfoAddressType {
diff --git a/cw_monero/lib/ledger.dart b/cw_monero/lib/ledger.dart
index 7707acd48..7c1742ada 100644
--- a/cw_monero/lib/ledger.dart
+++ b/cw_monero/lib/ledger.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:ffi';
import 'dart:typed_data';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
@@ -118,3 +119,10 @@ void _logLedgerCommand(Uint8List command, [bool isResponse = true]) {
"> ${_ledgerMoneroCommands[command[1]]} ${toHexString(command.sublist(2))}");
}
}
+
+class MoneroLedgerService extends HardwareWalletService {
+ final LedgerConnection connection;
+
+ MoneroLedgerService(this.connection);
+}
+
diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart
index e315f24fa..30c550721 100644
--- a/cw_monero/lib/monero_wallet.dart
+++ b/cw_monero/lib/monero_wallet.dart
@@ -37,6 +37,7 @@ import 'package:cw_monero/monero_unspent.dart';
import 'package:cw_monero/monero_wallet_addresses.dart';
import 'package:cw_monero/monero_wallet_service.dart';
import 'package:cw_monero/pending_monero_transaction.dart';
+import 'package:cw_monero/trezor.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
@@ -385,8 +386,42 @@ abstract class MoneroWalletBase extends WalletBase syncTrezor() async {
+ if (trezorService == null) throw Exception("Trezor not connected");
+
+ final ptr = Pointer.fromAddress(currentWallet!.ffiAddress());
+ final tdis = monero.Wallet_exportTrezorTdis(ptr);
+
+ final response = await Trezor(trezorService!).keyImageSync(tdis);
+
+ final success = monero.Wallet_importTrezorEncryptedKeyImagesJson(ptr, response);
+
+ if (!success) throw Exception(monero.Wallet_errorString(ptr));
+ }
+
+ Future signTrezorTransaction(String json) async {
+ if (trezorService == null) throw Exception("Trezor not connected");
+ return Trezor(trezorService!).signTransaction(json);
+ }
+
@override
Future createTransaction(Object credentials) async {
+ if (hardwareWalletType == HardwareWalletType.trezor) {
+ for (int i = 0; i < 2; i++) {
+ try {
+ return await _createTransaction(credentials);
+ } catch (e) {
+ printV(e);
+ }
+ await save();
+ await Future.delayed(Duration(seconds: i));
+ }
+ }
+ return await _createTransaction(credentials);
+ }
+
+ Future _createTransaction(Object credentials) async {
final _credentials = credentials as MoneroTransactionCreationCredentials;
final inputs = [];
final outputs = _credentials.outputs;
diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart
index 2a153c5e1..1b0e9e347 100644
--- a/cw_monero/lib/monero_wallet_service.dart
+++ b/cw_monero/lib/monero_wallet_service.dart
@@ -3,8 +3,8 @@ import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
-import 'package:collection/collection.dart';
import 'package:cw_core/get_height_by_date.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/monero_wallet_utils.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/unspent_coins_info.dart';
@@ -20,8 +20,8 @@ import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/bip39_seed.dart';
import 'package:cw_monero/ledger.dart';
import 'package:cw_monero/monero_wallet.dart';
+import 'package:cw_monero/trezor.dart';
import 'package:hive/hive.dart';
-import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:monero/monero.dart' as monero;
import 'package:polyseed/polyseed.dart';
@@ -46,11 +46,11 @@ class MoneroNewWalletCredentials extends WalletCredentials {
class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
MoneroRestoreWalletFromHardwareCredentials(
{required String name,
- required this.ledgerConnection,
+ required this.hardwareWalletService,
int height = 0,
String? password})
: super(name: name, password: password, height: height);
- LedgerConnection ledgerConnection;
+ HardwareWalletService hardwareWalletService;
}
class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
@@ -193,7 +193,7 @@ class MoneroWalletService extends WalletService<
unspentCoinsInfo: unspentCoinsInfoSource,
password: password);
- if (wallet.isHardwareWallet) {
+ if (wallet.hardwareWalletType == HardwareWalletType.ledger) {
wallet.setLedgerConnection(gLedger!);
gLedger = null;
}
@@ -301,15 +301,29 @@ class MoneroWalletService extends WalletService<
try {
final path = await pathForWallet(name: credentials.name, type: getType());
final password = credentials.password;
- final height = credentials.height;
- enableLedgerExchange(credentials.ledgerConnection);
+ if (credentials.hardwareWalletService case MoneroLedgerService service) {
+ enableLedgerExchange(service.connection);
- await monero_wallet_manager.restoreWalletFromHardwareWallet(
+ await monero_wallet_manager.restoreWalletFromHardwareWallet(
path: path,
password: password!,
- restoreHeight: height!,
- deviceName: 'Ledger');
+ restoreHeight: credentials.height!,
+ deviceName: 'Ledger',
+ );
+ } else if (credentials.hardwareWalletService case MoneroTrezorService service) {
+ final watchCredentials = await Trezor(service).getWatchCredentials();
+
+ monero_wallet_manager.restoreWalletFromKeys(
+ path: path,
+ password: credentials.password!,
+ language: "English",
+ restoreHeight: credentials.height!,
+ address: watchCredentials.address,
+ viewKey: watchCredentials.watchKey,
+ spendKey: "",
+ );
+ }
final wallet = MoneroWallet(
walletInfo: credentials.walletInfo!,
@@ -567,7 +581,7 @@ class MoneroWalletService extends WalletService<
if (walletInfo == null) {
return false;
}
- return walletInfo.isHardwareWallet;
+ return walletInfo.hardwareWalletType == HardwareWalletType.ledger;
}
}
@@ -585,4 +599,4 @@ Future closeWalletAwaitIfShould(int wmaddr, int waddr) async {
monero.WalletManager_errorString(Pointer.fromAddress(wmaddr));
}));
}
-}
\ No newline at end of file
+}
diff --git a/cw_monero/lib/pending_monero_transaction.dart b/cw_monero/lib/pending_monero_transaction.dart
index e1d191eac..c32a2cc9c 100644
--- a/cw_monero/lib/pending_monero_transaction.dart
+++ b/cw_monero/lib/pending_monero_transaction.dart
@@ -1,5 +1,8 @@
import 'dart:async';
+import 'dart:ffi';
+import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/wallet_info.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/structs/pending_transaction.dart';
import 'package:cw_monero/api/transaction_history.dart'
@@ -10,6 +13,7 @@ import 'package:cw_core/amount_converter.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:cw_monero/monero_wallet.dart';
+import 'package:monero/monero.dart' as monero;
class DoubleSpendException implements Exception {
DoubleSpendException();
@@ -43,22 +47,39 @@ class PendingMoneroTransaction with PendingTransaction {
CryptoCurrency.xmr, pendingTransactionDescription.fee);
@override
- bool shouldCommitUR() => isViewOnly;
+ bool shouldCommitUR() => isViewOnly && wallet.hardwareWalletType != HardwareWalletType.trezor;
@override
Future commit() async {
- try {
- await monero_transaction_history.commitTransactionFromPointerAddress(
- address: pendingTransactionDescription.pointerAddress,
- useUR: false);
- } catch (e) {
- final message = e.toString();
+ if (wallet.hardwareWalletType == HardwareWalletType.trezor) {
+ final ptr = Pointer.fromAddress(pendingTransactionDescription.pointerAddress);
+ final ret = await monero.PendingTransaction_commitTrezor(ptr, 0);
- if (message.contains('Reason: double spend')) {
- throw DoubleSpendException();
+ final json = await wallet.signTrezorTransaction(ret);
+
+ final wptr = Pointer.fromAddress(currentWallet!.ffiAddress());
+
+ final suc = await monero.Wallet_submitTransactionHex(wptr, json);
+
+ if (!suc) {
+ final err = monero.UnsignedTransaction_errorString(ptr);
+ printV(err);
+ throw err;
}
+ } else {
+ try {
+ await monero_transaction_history.commitTransactionFromPointerAddress(
+ address: pendingTransactionDescription.pointerAddress,
+ useUR: false);
+ } catch (e) {
+ final message = e.toString();
- rethrow;
+ if (message.contains('Reason: double spend')) {
+ throw DoubleSpendException();
+ }
+
+ rethrow;
+ }
}
storeSync(force: true);
unawaited(() async {
diff --git a/cw_monero/lib/trezor.dart b/cw_monero/lib/trezor.dart
new file mode 100644
index 000000000..f2651d062
--- /dev/null
+++ b/cw_monero/lib/trezor.dart
@@ -0,0 +1,53 @@
+import 'dart:convert';
+
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:trezor_flutter/trezor_flutter.dart';
+
+class MoneroTrezorService extends HardwareWalletService {
+ final TrezorClient client;
+
+ MoneroTrezorService(this.client);
+}
+
+class MoneroTrezorWatchCredentials {
+ final String watchKey;
+ final String address;
+
+ const MoneroTrezorWatchCredentials(this.watchKey, this.address);
+}
+
+class Trezor {
+ final MoneroTrezorService service;
+
+ Trezor(this.service);
+
+ Future getWatchCredentials() async {
+ final credentials = await TrezorMonero(service.client).getWatchCredentials();
+
+ return MoneroTrezorWatchCredentials(credentials.$1, credentials.$2);
+ }
+
+ Future keyImageSync(String tdis) async {
+ final tdisMap = jsonDecode(tdis) as Map;
+ final tdisList = tdisMap["tdis"] as List;
+
+ final txIds = [];
+ for (final tdi in tdisList) {
+ txIds.add(MoneroKeyImageTxData(
+ outKey: tdi["out_key"],
+ txPubKey: tdi["tx_pub_key"],
+ internalOutputIndex: tdi["internal_output_index"],
+ subAddrMajor: tdi["sub_addr_major"],
+ subAddrMinor: tdi["sub_addr_minor"],
+ additionalTxPubKeys: (tdi["additional_tx_pub_keys"] as List?)?.map((e) => e as String).toList() ?? []
+ ));
+ }
+ final keyImages = await TrezorMonero(service.client).syncKeyImages(txIds);
+
+ return jsonEncode(keyImages.toMap());
+ }
+
+ Future signTransaction(String json) async {
+ return TrezorMonero(service.client).signTransaction(jsonDecode(json));
+ }
+}
diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock
index 6f6174d08..d00f2149e 100644
--- a/cw_monero/pubspec.lock
+++ b/cw_monero/pubspec.lock
@@ -296,6 +296,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.3"
+ fast_base58:
+ dependency: transitive
+ description:
+ name: fast_base58
+ sha256: "611f65633b734f27a850b51371b3eba993a5165650e12e8e7b02959f3768ba06"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.1"
ffi:
dependency: "direct main"
description:
@@ -342,10 +350,10 @@ packages:
dependency: transitive
description:
name: flutter_web_bluetooth
- sha256: "1363831def5eed1e1064d1eca04e8ccb35446e8f758579c3c519e156b77926da"
+ sha256: ad26a1b3fef95b86ea5f63793b9a0cdc1a33490f35d754e4e711046cae3ebbf8
url: "https://pub.dev"
source: hosted
- version: "1.0.0"
+ version: "1.1.0"
frontend_server_client:
dependency: transitive
description:
@@ -494,18 +502,18 @@ packages:
dependency: "direct main"
description:
name: ledger_flutter_plus
- sha256: "1c03f3c4a9754b5f0170a9eb9552ec54fa86e985f8ee71a255ee2c5629b53d31"
+ sha256: "59c5a6062505c721c2ca909fa085c52cffe93de995174b8418b54a6acf52eb70"
url: "https://pub.dev"
source: hosted
- version: "1.5.1"
+ version: "1.5.6"
ledger_usb_plus:
dependency: transitive
description:
name: ledger_usb_plus
- sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87"
+ sha256: "911df2d1d55cdae612ff2c9db140cf71a9b28a67ba16adfdc4983cd9f02c531e"
url: "https://pub.dev"
source: hosted
- version: "1.0.4"
+ version: "1.0.8"
logging:
dependency: transitive
description:
@@ -582,9 +590,9 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
- ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
- resolved-ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
- url: "https://github.com/mrcyjanek/monero_c"
+ ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ url: "http://github.com/mrcyjanek/monero_c.git"
source: git
version: "0.0.0"
mutex:
@@ -724,6 +732,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.1"
+ protobuf:
+ dependency: transitive
+ description:
+ name: protobuf
+ sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
provider:
dependency: transitive
description:
@@ -962,6 +978,24 @@ packages:
relative: true
source: path
version: "0.0.1"
+ trezor_flutter:
+ dependency: "direct main"
+ description:
+ path: trezor-flutter
+ ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107"
+ resolved-ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107"
+ url: "https://github.com/cake-tech/trezor-flutter.git"
+ source: git
+ version: "0.1.0"
+ trezor_usb_transport:
+ dependency: transitive
+ description:
+ path: trezor_usb_transport
+ ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107"
+ resolved-ref: "4bf54339a1caefd4c53a4a8873a9686ec13a9107"
+ url: "https://github.com/cake-tech/trezor-flutter.git"
+ source: git
+ version: "0.0.1"
tuple:
dependency: transitive
description:
@@ -982,10 +1016,10 @@ packages:
dependency: transitive
description:
name: universal_ble
- sha256: "1fad089150a29db82b3b7d60327e18c5ad6b3a5bb509defc1c690b0a76b9c098"
+ sha256: "8325ca9f5cce2fba2b4efef74a4fcfe7144f303b409d6fc6ed16197b00a1a241"
url: "https://pub.dev"
source: hosted
- version: "0.15.0"
+ version: "1.2.0"
universal_platform:
dependency: transitive
description:
diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml
index 7f38c0ea0..4c10e94d7 100644
--- a/cw_monero/pubspec.yaml
+++ b/cw_monero/pubspec.yaml
@@ -25,11 +25,16 @@ dependencies:
path: ../cw_core
monero:
git:
- url: https://github.com/mrcyjanek/monero_c
- ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
+ url: https://github.com/mrcyjanek/monero_c.git
+ ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
path: impls/monero.dart
mutex: ^3.1.0
ledger_flutter_plus: ^1.4.1
+ trezor_flutter:
+ git:
+ url: https://github.com/cake-tech/trezor-flutter.git
+ ref: 4bf54339a1caefd4c53a4a8873a9686ec13a9107
+ path: trezor-flutter
dev_dependencies:
flutter_test:
diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock
index 734af9338..55cfb8fdf 100644
--- a/cw_wownero/pubspec.lock
+++ b/cw_wownero/pubspec.lock
@@ -502,9 +502,9 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
- ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
- resolved-ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
- url: "https://github.com/mrcyjanek/monero_c"
+ ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ url: "http://github.com/mrcyjanek/monero_c.git"
source: git
version: "0.0.0"
mutex:
diff --git a/cw_wownero/pubspec.yaml b/cw_wownero/pubspec.yaml
index a23fc3955..ca9c70bea 100644
--- a/cw_wownero/pubspec.yaml
+++ b/cw_wownero/pubspec.yaml
@@ -23,8 +23,8 @@ dependencies:
path: ../cw_core
monero:
git:
- url: https://github.com/mrcyjanek/monero_c
- ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 # monero_c hash
+ url: https://github.com/mrcyjanek/monero_c.git
+ ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
path: impls/monero.dart
mutex: ^3.1.0
diff --git a/cw_zano/pubspec.lock b/cw_zano/pubspec.lock
index 662ccded6..6e2916d71 100644
--- a/cw_zano/pubspec.lock
+++ b/cw_zano/pubspec.lock
@@ -507,9 +507,9 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
- ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
- resolved-ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2
- url: "https://github.com/mrcyjanek/monero_c"
+ ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ resolved-ref: "3bfb3856a838f2bf6b729501837bb0295dedf25d"
+ url: "http://github.com/mrcyjanek/monero_c.git"
source: git
version: "0.0.0"
nested:
diff --git a/cw_zano/pubspec.yaml b/cw_zano/pubspec.yaml
index 8eb5d3e14..16585b715 100644
--- a/cw_zano/pubspec.yaml
+++ b/cw_zano/pubspec.yaml
@@ -24,8 +24,8 @@ dependencies:
fluttertoast: ^8.2.12
monero:
git:
- url: https://github.com/mrcyjanek/monero_c
- ref: bc8d1a0b75b97156d71579581b4cdfe58c777ed2 # monero_c hash
+ url: https://github.com/mrcyjanek/monero_c.git
+ ref: 3bfb3856a838f2bf6b729501837bb0295dedf25d
path: impls/monero.dart
dev_dependencies:
flutter_test:
diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart
index d885e3e14..888c2ce03 100644
--- a/integration_test/components/common_test_flows.dart
+++ b/integration_test/components/common_test_flows.dart
@@ -231,12 +231,9 @@ class CommonTestFlows {
await _newWalletTypePageRobot.findParticularWalletTypeInScrollableList(type);
+ // Tapping the wallet type navigates directly (no separate Next button).
_tester.printToConsole('Tapping wallet type: ${type.name}');
await _newWalletTypePageRobot.selectWalletType(type);
- await _tester.pumpAndSettle(Duration(milliseconds: 500));
-
- _tester.printToConsole('Pressing next button');
- await _newWalletTypePageRobot.onNextButtonPressed();
await _tester.pumpAndSettle(Duration(milliseconds: 2000));
diff --git a/integration_test/robots/new_wallet_type_page_robot.dart b/integration_test/robots/new_wallet_type_page_robot.dart
index fc3e94f59..11785b67d 100644
--- a/integration_test/robots/new_wallet_type_page_robot.dart
+++ b/integration_test/robots/new_wallet_type_page_robot.dart
@@ -77,11 +77,9 @@ class NewWalletTypePageRobot {
tester.printToConsole('Wallet type ${type.name} is now visible and tappable');
}
+ // Tapping a wallet type now navigates directly (the page no longer has a
+ // separate "Next" button — selection happens on tap).
Future selectWalletType(WalletType type) async {
await commonTestCases.tapItemByKey('new_wallet_type_${type.name}_button_key');
}
-
- Future onNextButtonPressed() async {
- await commonTestCases.tapItemByKey('new_wallet_type_next_button_key');
- }
}
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index e661632c8..2d32cb833 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -295,6 +295,7 @@ SPEC CHECKSUMS:
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
+ warp_api: 997d8fb3e777669bc6260d0c12e56cd40b9db1d1
YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966
PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a
diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart
index f927b702d..582cccd5d 100644
--- a/lib/buy/dfx/dfx_buy_provider.dart
+++ b/lib/buy/dfx/dfx_buy_provider.dart
@@ -336,7 +336,7 @@ class DFXBuyProvider extends BuyProvider {
required String cryptoCurrencyAddress,
String? countryCode}) async {
if (wallet.isHardwareWallet) {
- if (!hardwareWalletVM!.isConnected) {
+ if (!hardwareWalletVM!.isConnected(wallet.walletInfo.type)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: wallet.walletInfo.type,
diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart
index 7cd992812..2c26fa4ca 100644
--- a/lib/buy/robinhood/robinhood_buy_provider.dart
+++ b/lib/buy/robinhood/robinhood_buy_provider.dart
@@ -173,7 +173,7 @@ class RobinhoodBuyProvider extends BuyProvider {
required String cryptoCurrencyAddress,
String? countryCode}) async {
if (wallet.isHardwareWallet) {
- if (!hardwareWalletVM!.isConnected) {
+ if (!hardwareWalletVM!.isConnected(wallet.walletInfo.type)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: wallet.walletInfo.type,
diff --git a/lib/di.dart b/lib/di.dart
index aa42887d7..cd7ee62d1 100644
--- a/lib/di.dart
+++ b/lib/di.dart
@@ -240,7 +240,7 @@ import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
-import 'package:cake_wallet/view_model/hardware_wallet/trezor_view_model.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart';
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
@@ -414,7 +414,7 @@ Future setup({
switch(type) {
case HardwareWalletType.bitbox: return getIt();
case HardwareWalletType.ledger: return getIt();
- case HardwareWalletType.trezor: return getIt();
+ case HardwareWalletType.trezor: return getIt();
case HardwareWalletType.cupcake:
case HardwareWalletType.coldcard:
case HardwareWalletType.seedsigner:
@@ -429,7 +429,7 @@ Future setup({
getIt.registerLazySingleton(() => TrezorConnect("cakewallet://trezor_connect",
appName: "Cake Wallet"));
- getIt.registerLazySingleton(() => TrezorViewModel(getIt()));
+ getIt.registerLazySingleton(() => TrezorConnectViewModel(getIt()));
getIt.registerFactory(() => KeyService(getIt.get()));
@@ -1118,9 +1118,9 @@ Future setup({
getIt.registerFactory(() => AddressListPage(getIt.get()));
- getIt.registerLazySingleton(() {
+ getIt.registerFactoryParam((isPow, _) {
final appStore = getIt.get();
- return NodeListViewModel(appStore);
+ return NodeListViewModel(appStore, isPow);
});
getIt.registerFactoryParam(
@@ -1131,11 +1131,6 @@ Future setup({
),
);
- getIt.registerLazySingleton(() {
- final appStore = getIt.get();
- return PowNodeListViewModel(appStore);
- });
-
getIt.registerFactory(() => ConnectionSyncViewModel(getIt.get(), getIt.get().wallet!));
getIt.registerFactory(() => ConnectionSyncPage(getIt.get()));
@@ -1178,8 +1173,6 @@ Future setup({
final Node? editingNode = args['editingNode'] as Node?;
return NodeCreateOrEditViewModel(
isPow,
- getIt.get(),
- getIt.get(),
type,
getIt.get(),
editingNode: editingNode
@@ -1700,10 +1693,7 @@ Future setup({
);
getIt.registerFactoryParam((bool isPow, _) {
- if (isPow) {
- return ManageNodesPage(isPow, powNodeListViewModel: getIt.get(), dashboardViewModel: getIt.get());
- }
- return ManageNodesPage(isPow, nodeListViewModel: getIt.get(), dashboardViewModel: getIt.get());
+ return ManageNodesPage(isPow, nodeListViewModel: getIt.get(param1: isPow), dashboardViewModel: getIt.get());
});
getIt.registerFactory(
diff --git a/lib/entities/hardware_wallet/hardware_wallet_device.dart b/lib/entities/hardware_wallet/hardware_wallet_device.dart
index 646f5a2c2..757e68099 100644
--- a/lib/entities/hardware_wallet/hardware_wallet_device.dart
+++ b/lib/entities/hardware_wallet/hardware_wallet_device.dart
@@ -1,5 +1,6 @@
import 'package:bitbox_flutter/usb/bitbox_device.dart' as bitbox;
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
+import 'package:trezor_flutter/trezor_flutter.dart' as trezor;
abstract class HardwareWalletDevice {
String get name;
@@ -41,6 +42,21 @@ class BitboxHardwareWalletDevice extends HardwareWalletDevice {
HardwareWalletConnectionType get connectionType => HardwareWalletConnectionType.usb;
}
+class TrezorHardwareWalletDevice extends HardwareWalletDevice {
+ final trezor.TrezorDevice device;
+
+ TrezorHardwareWalletDevice(this.device);
+
+ @override
+ String get name => device.name;
+
+ @override
+ HardwareWalletDeviceType get type => device.deviceInfo.toGeneric();
+
+ @override
+ HardwareWalletConnectionType get connectionType => device.connectionType.toGeneric();
+}
+
enum HardwareWalletDeviceType {
ledgerBlue,
ledgerNanoS,
@@ -50,7 +66,12 @@ enum HardwareWalletDeviceType {
ledgerStax,
ledgerFlex,
BitBox02,
- BitBox02Nova;
+ BitBox02Nova,
+ trezorModelOne,
+ trezorModelT,
+ trezorSafe3,
+ trezorSafe5,
+ trezorSafe7;
}
enum HardwareWalletConnectionType {
@@ -60,7 +81,7 @@ enum HardwareWalletConnectionType {
qr;
}
-extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
+extension LedgerToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
HardwareWalletDeviceType toGeneric() {
switch (this) {
case ledger.LedgerDeviceType.blue:
@@ -81,7 +102,24 @@ extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
}
}
-extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType {
+extension TrezorToGenericHardwareWalletDeviceType on trezor.TrezorDeviceType {
+ HardwareWalletDeviceType toGeneric() {
+ switch (this) {
+ case trezor.TrezorDeviceType.modelOne:
+ return HardwareWalletDeviceType.trezorModelOne;
+ case trezor.TrezorDeviceType.modelT:
+ return HardwareWalletDeviceType.trezorModelT;
+ case trezor.TrezorDeviceType.safe3:
+ return HardwareWalletDeviceType.trezorSafe3;
+ case trezor.TrezorDeviceType.safe5:
+ return HardwareWalletDeviceType.trezorSafe5;
+ case trezor.TrezorDeviceType.safe7:
+ return HardwareWalletDeviceType.trezorSafe7;
+ }
+ }
+}
+
+extension LedgerToGenericHardwareWalletConnectionType on ledger.ConnectionType {
HardwareWalletConnectionType toGeneric() {
switch (this) {
case ledger.ConnectionType.usb:
@@ -91,3 +129,14 @@ extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType {
}
}
}
+
+extension TrezorToGenericHardwareWalletConnectionType on trezor.ConnectionType {
+ HardwareWalletConnectionType toGeneric() {
+ switch (this) {
+ case trezor.ConnectionType.usb:
+ return HardwareWalletConnectionType.usb;
+ case trezor.ConnectionType.ble:
+ return HardwareWalletConnectionType.ble;
+ }
+ }
+}
diff --git a/lib/entities/new_ui_entities/list_item/list_item_regular_row.dart b/lib/entities/new_ui_entities/list_item/list_item_regular_row.dart
index f6df44a25..e5fdc4595 100644
--- a/lib/entities/new_ui_entities/list_item/list_item_regular_row.dart
+++ b/lib/entities/new_ui_entities/list_item/list_item_regular_row.dart
@@ -16,7 +16,9 @@ class ListItemRegularRow extends ListItem {
this.truncateTrailingText = false,
this.foregroundColor,
this.trailingIconSize,
- this.copyableText
+ this.copyableText,
+ this.leadingIconErrorWidget,
+ this.leadingIconSize,
});
final String? subtitle;
@@ -31,4 +33,6 @@ class ListItemRegularRow extends ListItem {
final bool truncateTrailingText;
final Color? foregroundColor;
final double? trailingIconSize;
+ final Widget? leadingIconErrorWidget;
+ final double? leadingIconSize;
}
diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart
index 0eea5a85d..7a6a47bc8 100644
--- a/lib/entities/parse_address_from_domain.dart
+++ b/lib/entities/parse_address_from_domain.dart
@@ -216,6 +216,14 @@ class AddressResolver {
return emailRegex.hasMatch(address);
}
+ String _stripHtmlTags(String value) {
+ return value
+ .replaceAll(RegExp(r'
', caseSensitive: false), '\n')
+ .replaceAll(RegExp(r'
', caseSensitive: false), '\n')
+ .replaceAll(RegExp(r'<[^>]*>'), '')
+ .trim();
+ }
+
Future resolve(BuildContext context, String text, CryptoCurrency currency) async {
final ticker = currency.title;
try {
@@ -348,7 +356,8 @@ class AddressResolver {
await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName);
if (mastodonUser != null) {
- String? addressFromBio = extractAddressByType(raw: mastodonUser.note, type: currency, requireSurroundingWhitespaces: true);
+ final mastodonNote = _stripHtmlTags(mastodonUser.note);
+ String? addressFromBio = extractAddressByType(raw: mastodonNote, type: currency, requireSurroundingWhitespaces: true);
if (addressFromBio != null && addressFromBio.isNotEmpty) {
return ParsedAddress.fetchMastodonAddress(
@@ -361,7 +370,7 @@ class AddressResolver {
await MastodonAPI.getPinnedPosts(userId: mastodonUser.id, apiHost: hostName);
if (pinnedPosts.isNotEmpty) {
- final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n');
+ final userPinnedPostsText = pinnedPosts.map((item) => _stripHtmlTags(item.content)).join('\n');
String? addressFromPinnedPost =
extractAddressByType(raw: userPinnedPostsText, type: currency,
requireSurroundingWhitespaces: true);
diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart
index 08143b3b3..0a378e219 100644
--- a/lib/monero/cw_monero.dart
+++ b/lib/monero/cw_monero.dart
@@ -236,13 +236,14 @@ class CWMonero extends Monero {
required String name,
required String password,
required int height,
- required ledger.LedgerConnection ledgerConnection,
+ required HardwareWalletService hardwareWalletService,
}) =>
MoneroRestoreWalletFromHardwareCredentials(
- name: name,
- password: password,
- height: height,
- ledgerConnection: ledgerConnection);
+ name: name,
+ password: password,
+ height: height,
+ hardwareWalletService: hardwareWalletService,
+ );
@override
WalletCredentials createMoneroRestoreWalletFromSeedCredentials(
@@ -416,9 +417,7 @@ class CWMonero extends Monero {
}
@override
- void monerocCheck() {
- checkIfMoneroCIsFine();
- }
+ void monerocCheck() => checkIfMoneroCIsFine();
@override
Future setLedgerConnection(Object wallet, ledger.LedgerConnection connection) async {
@@ -427,29 +426,41 @@ class CWMonero extends Monero {
}
@override
- void resetLedgerConnection() {
- disableLedgerExchange();
- }
+ void resetLedgerConnection() => disableLedgerExchange();
@override
- void setGlobalLedgerConnection(ledger.LedgerConnection connection) {
- gLedger = connection;
- }
+ void setGlobalLedgerConnection(ledger.LedgerConnection connection) => gLedger = connection;
@override
String? getLastLedgerCommand() => latestLedgerCommand;
- bool isViewOnly() {
- return isViewOnlyBySpendKey(null);
+ @override
+ void setHardwareWalletService(Object wallet, HardwareWalletService service) {
+ final moneroWallet = wallet as MoneroWallet;
+ moneroWallet.trezorService = service as MoneroTrezorService;
}
- @override
- Map> debugCallLength() {
- return monero_wallet_api.debugCallLength();
- }
@override
- Map getWalletCacheDebug() {
- return monero_wallet_api.getWalletCacheDebug();
+ HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
+ MoneroLedgerService(connection);
+
+ @override
+ HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorClient client) =>
+ MoneroTrezorService(client);
+
+ @override
+ Future syncTrezor(Object wallet) async {
+ final moneroWallet = wallet as MoneroWallet;
+
+ return moneroWallet.syncTrezor();
}
+
+ bool isViewOnly() => isViewOnlyBySpendKey(null);
+
+ @override
+ Map> debugCallLength() => monero_wallet_api.debugCallLength();
+
+ @override
+ Map getWalletCacheDebug() => monero_wallet_api.getWalletCacheDebug();
}
diff --git a/lib/new-ui/pages/send_page.dart b/lib/new-ui/pages/send_page.dart
index c3f5da957..9292d1af2 100644
--- a/lib/new-ui/pages/send_page.dart
+++ b/lib/new-ui/pages/send_page.dart
@@ -726,12 +726,13 @@ class _NewSendPageState extends State {
}
if (widget.sendViewModel.wallet.isHardwareWallet) {
- if (!widget.sendViewModel.hardwareWalletViewModel!.isConnected) {
+ if (!widget.sendViewModel.hardwareWalletViewModel!
+ .isConnected(widget.sendViewModel.walletType)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: widget.sendViewModel.walletType,
hardwareWalletType: widget.sendViewModel.wallet.walletInfo.hardwareWalletType!,
- onConnectDevice: (BuildContext context, _) {
+ onConnectDevice: (_, __) {
widget.sendViewModel.hardwareWalletViewModel!
.initWallet(widget.sendViewModel.wallet);
Navigator.of(context).pop();
@@ -748,8 +749,12 @@ class _NewSendPageState extends State {
amount += item.formattedCryptoAmount;
}
if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) {
- await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage,
- arguments: monero!.exportOutputsUR(widget.sendViewModel.wallet));
+ if (widget.sendViewModel.wallet.hardwareWalletType == HardwareWalletType.trezor) {
+ await monero!.syncTrezor(widget.sendViewModel.wallet);
+ } else {
+ await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage,
+ arguments: monero!.exportOutputsUR(widget.sendViewModel.wallet));
+ }
await Future.delayed(Duration(seconds: 1)); // wait for monero to refresh the state
}
if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) {
diff --git a/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart b/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart
index 10a411c2f..b700f4aab 100644
--- a/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart
+++ b/lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart
@@ -17,10 +17,10 @@ import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/lnurl.dart';
+import 'package:cw_core/node.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_svg/svg.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import '../../../pages/receive_page.dart';
@@ -198,7 +198,12 @@ class CoinActionRow extends StatelessWidget {
Navigator.of(context)
.pushNamed(Routes.walletConnectConnectionsListing, arguments: Uri.parse(code));
return;
- } else {
+ } else if(["http", "https", "tcp"].contains(Uri.tryParse(code)?.scheme)) {
+ Navigator.of(context).pushNamed(Routes.newNode,
+ arguments: {"editingNode": Node.fromUri(Uri.parse(code), walletType)});
+ return;
+
+ }else {
final uri = Uri.tryParse(code);
if (uri == null) return;
req = PaymentRequest.fromUri(uri);
diff --git a/lib/new-ui/widgets/coins_page/wallet_info.dart b/lib/new-ui/widgets/coins_page/wallet_info.dart
index 21c83db44..4963513fc 100644
--- a/lib/new-ui/widgets/coins_page/wallet_info.dart
+++ b/lib/new-ui/widgets/coins_page/wallet_info.dart
@@ -86,11 +86,11 @@ class WalletInfoBar extends StatelessWidget {
case null:
return null;
case HardwareWalletType.bitbox:
- return "assets/images/hardware_wallet/device_bitbox.svg";
+ return "assets/new-ui/hardware_wallets/device_bitbox.svg";
case HardwareWalletType.ledger:
- return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
+ return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
case HardwareWalletType.trezor:
- return "assets/images/hardware_wallet/device_trezor_safe_5.svg";
+ return "assets/new-ui/hardware_wallets/device_trezor_safe_5.svg";
case HardwareWalletType.cupcake:
return "assets/images/cupcake.svg";
case HardwareWalletType.coldcard:
diff --git a/lib/new-ui/widgets/digit_input.dart b/lib/new-ui/widgets/digit_input.dart
new file mode 100644
index 000000000..d6889db67
--- /dev/null
+++ b/lib/new-ui/widgets/digit_input.dart
@@ -0,0 +1,182 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+class DigitInputController {
+ String _text = "";
+
+ String get text => _text;
+
+ set text(String newText) {
+ _text = newText;
+ for (final listener in _listeners) {
+ listener.call();
+ }
+ }
+
+ final List _listeners = [];
+
+ void addListener(void Function() listener) => _listeners.add(listener);
+}
+
+class DigitInputPill extends StatelessWidget {
+ const DigitInputPill({super.key, this.digit, required this.highlighted});
+
+ final String? digit;
+ final bool highlighted;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: 51,
+ height: 84,
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainerHigh,
+ borderRadius: BorderRadius.circular(16),
+ border:
+ highlighted ? Border.all(color: Theme.of(context).colorScheme.primary, width: 2) : null,
+ ),
+ child: Center(
+ child: Padding(
+ padding: EdgeInsets.symmetric(vertical: 16),
+ child: Text(digit ?? " ", style: TextStyle(fontSize: 40)),
+ ),
+ ),
+ );
+ }
+}
+
+class DigitInput extends StatefulWidget {
+ const DigitInput({
+ super.key,
+ required this.controller,
+ required this.desiredLength,
+ this.breakAt = 3,
+ });
+
+ final DigitInputController controller;
+ final int desiredLength;
+ final int breakAt;
+
+ @override
+ State createState() => _DigitInputState();
+}
+
+class _DigitInputState extends State implements TextInputClient {
+ TextInputConnection? _textInputConnection;
+
+ @override
+ void initState() {
+ super.initState();
+ _openVirtualKeyboard();
+ }
+
+ void _openVirtualKeyboard() {
+ if (_textInputConnection == null || !_textInputConnection!.attached) {
+ _textInputConnection = TextInput.attach(
+ this,
+ const TextInputConfiguration(
+ inputType: TextInputType.numberWithOptions(
+ signed: false,
+ decimal: false,
+ ),
+ inputAction: TextInputAction.none,
+ ),
+ );
+ }
+
+ _textInputConnection!.setEditingState(
+ TextEditingValue(
+ text: widget.controller.text,
+ selection: TextSelection.collapsed(offset: widget.controller.text.length),
+ ),
+ );
+ _textInputConnection!.show();
+ }
+
+ void _closeVirtualKeyboard() => _textInputConnection?.close();
+
+ @override
+ void dispose() {
+ _closeVirtualKeyboard();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: _openVirtualKeyboard,
+ child: SizedBox(
+ height: 84,
+ child: ListView.separated(
+ scrollDirection: Axis.horizontal,
+ shrinkWrap: true,
+ physics: NeverScrollableScrollPhysics(),
+ itemBuilder: (context, index) => DigitInputPill(
+ digit: widget.controller.text.length > index ? widget.controller.text[index] : null,
+ highlighted: widget.controller.text.length == index,
+ ),
+ separatorBuilder: (context, index) =>
+ SizedBox(width: (index + 1) % widget.breakAt == 0 ? 16 : 4),
+ itemCount: widget.desiredLength,
+ ),
+ ),
+ );
+ }
+
+ @override
+ void updateEditingValue(TextEditingValue value) {
+ final RegExp numberRegExp = RegExp(r'^\d{0,6}$');
+
+ if (numberRegExp.hasMatch(value.text)) {
+ setState(() => widget.controller.text = value.text);
+ } else {
+ // flutter keeps the text editing state regardless of whether you actually used it or not.
+ // so, if validation fails, we gotta set it back to what it was explicitly
+ _textInputConnection?.setEditingState(
+ TextEditingValue(
+ text: widget.controller.text,
+ selection: TextSelection.collapsed(offset: widget.controller.text.length),
+ ),
+ );
+ }
+ }
+
+ @override
+ void performAction(TextInputAction action) {}
+
+ @override
+ TextEditingValue? get currentTextEditingValue => TextEditingValue(text: widget.controller.text);
+
+ @override
+ void connectionClosed() => _textInputConnection = null;
+
+ @override
+ AutofillScope? get currentAutofillScope => null;
+
+ @override
+ void performPrivateCommand(String action, Map data) {}
+
+ @override
+ void showAutocorrectionPromptRect(int start, int end) {}
+
+ @override
+ void updateFloatingCursor(RawFloatingCursorPoint point) {}
+
+ @override
+ void insertTextPlaceholder(Size size) {}
+
+ @override
+ void removeTextPlaceholder() {}
+
+ @override
+ void showToolbar() {}
+
+ @override
+ void performSelector(String selectorName) {}
+
+ @override
+ void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) {}
+
+ @override
+ void insertContent(KeyboardInsertedContent content) {}
+}
diff --git a/lib/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart
new file mode 100644
index 000000000..100325dbe
--- /dev/null
+++ b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart
@@ -0,0 +1,60 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:flutter/material.dart';
+
+class HardwareWalletProceedOnDeviceMessage extends StatelessWidget {
+ const HardwareWalletProceedOnDeviceMessage({super.key, required this.hardwareWalletType});
+
+ final HardwareWalletType hardwareWalletType;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: double.infinity,
+ decoration: BoxDecoration(
+ border: Border.all(
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ borderRadius: BorderRadius.circular(16),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: Column(spacing: 8, children: [
+ if (hardwareWalletIcon != null)
+ CakeImageWidget(
+ imageUrl: hardwareWalletIcon!,
+ width: 36,
+ height: 36,
+ colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
+ ),
+ Text(
+ S.of(context).proceed_on_device,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w400,
+ color: Theme.of(context).colorScheme.onSurface,
+ ),
+ )
+ ]),
+ ),
+ );
+ }
+
+ String? get hardwareWalletIcon {
+ switch (hardwareWalletType) {
+ case HardwareWalletType.bitbox:
+ return "assets/new-ui/hardware_wallets/device_bitbox.svg";
+ case HardwareWalletType.ledger:
+ return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
+ case HardwareWalletType.trezor:
+ return "assets/new-ui/hardware_wallets/device_trezor_safe_7.svg";
+ case HardwareWalletType.cupcake:
+ return "assets/images/cupcake.svg";
+ case HardwareWalletType.coldcard:
+ case HardwareWalletType.seedsigner:
+ case HardwareWalletType.keystone:
+ return "assets/images/hardware_wallet/device_qr.svg";
+ }
+ }
+}
diff --git a/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart
new file mode 100644
index 000000000..2a06f37e6
--- /dev/null
+++ b/lib/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.dart
@@ -0,0 +1,338 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/new-ui/widgets/digit_input.dart';
+import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
+import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
+import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
+import 'package:cake_wallet/new-ui/widgets/send_page/directional_switcher.dart';
+import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
+import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
+import 'package:cake_wallet/utils/show_pop_up.dart';
+import 'package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:mobx/mobx.dart';
+
+class HardwareWalletProceedOnDeviceSheet extends StatefulWidget {
+ const HardwareWalletProceedOnDeviceSheet({
+ super.key,
+ required this.hardwareWalletType,
+ required this.trezorConnectVM,
+ required this.onRetry,
+ });
+
+ final HardwareWalletType hardwareWalletType;
+ final TrezorConnectViewModelBase trezorConnectVM;
+ final void Function() onRetry;
+
+ @override
+ State createState() =>
+ _HardwareWalletProceedOnDeviceSheetState();
+}
+
+class _HardwareWalletProceedOnDeviceSheetState extends State {
+ TrezorParingState _paringState = TrezorParingState.initial;
+
+ final DigitInputController _controller = DigitInputController();
+
+ static const pinOpenDuration = Duration(milliseconds: 300);
+
+ // you should probably check if there can be other pin lengths
+ static const pinLength = 6;
+
+ ReactionDisposer? paringStateReaction;
+
+ @override
+ void initState() {
+ super.initState();
+ _controller.addListener(() => setState(() {}));
+
+ _paringState = widget.trezorConnectVM.paringState;
+ paringStateReaction = reaction((_) => widget.trezorConnectVM.paringState, (paringState) {
+ if (paringState is SuccessTrezorParingState) {
+ if (mounted) Navigator.of(context).pop();
+ return;
+ }
+ setState(() => _paringState = paringState);
+ });
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ paringStateReaction?.reaction.dispose();
+ }
+
+ bool get _isAwaitingPin => _paringState == TrezorParingState.enterPin;
+
+ @override
+ Widget build(BuildContext context) {
+ return PopScope(
+ canPop: false,
+ child: SafeArea(
+ bottom: false,
+ child: Container(
+ width: MediaQuery.of(context).size.width,
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surface,
+ borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
+ ),
+ child: SafeArea(
+ child: Padding(
+ padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
+ child: Column(
+ children: [
+ ModalTopBar(
+ title: "",
+ leadingWidget: AnimatedSwitcher(
+ layoutBuilder: (currentChild, previousChildren) {
+ return Stack(
+ alignment: Alignment.centerLeft,
+ children: [
+ ...previousChildren,
+ if (currentChild != null) currentChild,
+ ],
+ );
+ },
+ duration: pinOpenDuration,
+ child: Text(
+ key: ValueKey(pageTitle),
+ style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
+ pageTitle,
+ ),
+ ),
+ trailingIcon: Icon(
+ Icons.close,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ onTrailingPressed: () => showPopUp(
+ context: context,
+ builder: (context) => AlertWithTwoActions(
+ alertTitle: S.of(context).are_you_sure_exit,
+ alertContent: S.of(context).hww_exit_desc,
+ leftButtonText: S.of(context).cancel,
+ rightButtonText: S.of(context).yes_exit,
+ rightAlertButtonStyle: AlertButtonStyle.error(context),
+ actionLeftButton: Navigator.of(context).pop,
+ actionRightButton: () {
+ Navigator.of(context).pop();
+ Navigator.of(context).pop();
+ },
+ ),
+ ),
+ ),
+ Expanded(
+ child: DirectionalAnimatedSwitcher(
+ duration: Duration(milliseconds: 400),
+ child: content,
+ ),
+ ),
+ Padding(
+ padding: EdgeInsets.only(bottom: 24, right: 24),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ SizedBox(),
+ AnimatedOpacity(
+ curve: Curves.easeOutQuad,
+ opacity: hasFullPin ? 1 : 0,
+ duration: pinOpenDuration,
+ child: ModernButton(
+ backgroundColor: Theme.of(context).colorScheme.primary,
+ iconColor: Theme.of(context).colorScheme.onPrimary,
+ size: 36,
+ icon: Icon(Icons.arrow_forward),
+ onPressed: () =>
+ widget.trezorConnectVM.setParingPin(_controller.text.trim()),
+ ),
+ ),
+ ],
+ ),
+ )
+ ],
+ ),
+ )),
+ ),
+ ),
+ );
+ }
+
+ Widget get content {
+ if (_paringState is InitialTrezorParingState || _paringState is EnterPinTrezorParingState) {
+ return _enterPinCode();
+ }
+
+ if (_paringState is VerifyingPinTrezorParingState) return _verifyingCode();
+
+ if (_paringState is FailTrezorParingState)
+ return _errorBox((_paringState as FailTrezorParingState).message);
+
+ return SizedBox.shrink(key: ValueKey(3));
+ }
+
+ void retry() {
+ _controller.text = "";
+ widget.onRetry();
+ }
+
+ String get pageTitle {
+ if (_paringState is InitialTrezorParingState) return S.of(context).device_confirmation;
+ if (_isAwaitingPin) return S.of(context).pairing_code;
+ return "";
+ }
+
+ bool get hasFullPin => _controller.text.length == pinLength && _isAwaitingPin;
+
+ String? get hardwareWalletIcon {
+ switch (widget.hardwareWalletType) {
+ case HardwareWalletType.bitbox:
+ return "assets/new-ui/hardware_wallets/device_bitbox.svg";
+ case HardwareWalletType.ledger:
+ return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
+ case HardwareWalletType.trezor:
+ return "assets/new-ui/hardware_wallets/device_trezor_safe_7.svg";
+ case HardwareWalletType.cupcake:
+ return "assets/images/cupcake.svg";
+ case HardwareWalletType.coldcard:
+ case HardwareWalletType.seedsigner:
+ case HardwareWalletType.keystone:
+ return "assets/images/hardware_wallet/device_qr.svg";
+ }
+ }
+
+ Widget _verifyingCode() => Container(
+ key: ValueKey(1),
+ width: MediaQuery.of(context).size.width,
+ child: Column(
+ spacing: 12,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const CupertinoActivityIndicator(radius: 36),
+ const SizedBox(),
+ Text(
+ "${S.of(context).verifying_code}...",
+ style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20),
+ ),
+ Text(
+ S.of(context).this_can_take_few_seconds,
+ style: TextStyle(
+ fontWeight: FontWeight.w400,
+ fontSize: 16,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ )
+ ],
+ ),
+ );
+
+ Widget _errorBox(String errorText) => Container(
+ key: ValueKey(2),
+ width: MediaQuery.of(context).size.width,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 18),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ spacing: 12,
+ children: [
+ const Spacer(),
+ Icon(
+ Icons.error_outline,
+ size: 80,
+ color: Theme.of(context).colorScheme.error,
+ ),
+ Text(
+ S.of(context).pairing_error,
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.w500,
+ color: Theme.of(context).colorScheme.onSurface,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ Text(
+ errorText,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w400,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ const Spacer(),
+ NewPrimaryButton(
+ onPressed: retry,
+ text: S.of(context).try_again,
+ color: Theme.of(context).colorScheme.primary,
+ textColor: Theme.of(context).colorScheme.onPrimary,
+ )
+ ],
+ ),
+ ),
+ );
+
+ Widget _enterPinCode() => Column(
+ key: ValueKey(0),
+ spacing: 12,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ AnimatedContainer(
+ duration: pinOpenDuration,
+ curve: Curves.easeOutCubic,
+ width: _isAwaitingPin ? 40 : 100,
+ height: _isAwaitingPin ? 40 : 100,
+ child: CakeImageWidget(
+ imageUrl: hardwareWalletIcon,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onSurfaceVariant,
+ BlendMode.srcIn,
+ ),
+ ),
+ ),
+ AnimatedSwitcher(
+ duration: pinOpenDuration,
+ switchInCurve: Curves.easeInCubic,
+ switchOutCurve: Curves.easeOutCubic,
+ child: _isAwaitingPin
+ ? Column(
+ spacing: 24,
+ key: ValueKey(1),
+ children: [
+ Text(
+ textAlign: TextAlign.center,
+ "${S.of(context).pairing_code_desc_1}\n${S.of(context).pairing_code_desc_2}",
+ ),
+ DigitInput(
+ controller: _controller,
+ desiredLength: pinLength,
+ )
+ ],
+ )
+ : Column(
+ key: ValueKey(0),
+ spacing: 12,
+ children: [
+ Text(
+ S.of(context).proceed_on_device,
+ style: TextStyle(
+ fontWeight: FontWeight.w500,
+ fontSize: 20,
+ ),
+ ),
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 20),
+ child: Text(
+ S.of(context).proceed_on_device_description,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 16,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ ),
+ )
+ ],
+ ),
+ )
+ ],
+ );
+}
diff --git a/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart b/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart
new file mode 100644
index 000000000..9b429edc4
--- /dev/null
+++ b/lib/new-ui/widgets/hardware_wallet/trezor_paring_sheet.dart
@@ -0,0 +1,106 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
+import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
+import 'package:flutter/material.dart';
+
+class HardwareWalletTrezorParingSheet extends StatefulWidget {
+ const HardwareWalletTrezorParingSheet({super.key});
+
+ @override
+ State createState() => _HardwareWalletTrezorParingSheetState();
+}
+
+class _HardwareWalletTrezorParingSheetState extends State {
+ final TextEditingController _paringCodeController = TextEditingController();
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
+ child: SafeArea(
+ child: Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.vertical(top: Radius.circular(18)),
+ color: Theme.of(context).colorScheme.surface,
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ModalTopBar(
+ title: "Paring code",
+ onLeadingPressed: Navigator.of(context).pop,
+ onTrailingPressed: () {},
+ leadingIcon: Icon(Icons.close),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(18.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 12,
+ children: [
+ SizedBox(),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Expanded(
+ flex: 75,
+ child: AnimatedContainer(
+ duration: Duration(milliseconds: 300),
+ height: 60,
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(16),
+ bottomLeft: Radius.circular(16),
+ ),
+ border: Border.all(
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ width: 2,
+ ),
+ ),
+ child: TextField(
+ textAlign: TextAlign.left,
+ textAlignVertical: TextAlignVertical.center,
+ controller: _paringCodeController,
+ keyboardType:
+ TextInputType.numberWithOptions(signed: false, decimal: false),
+ decoration: InputDecoration(
+ hint: Text(
+ "000 000",
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 16,
+ color: Theme.of(
+ context,
+ ).colorScheme.onSurface.withValues(alpha: 0.5),
+ ),
+ ),
+ border: InputBorder.none,
+ filled: true,
+ fillColor: Colors.transparent,
+ ),
+ style: TextStyle(color: Theme.of(context).colorScheme.onSurface),
+ ),
+ ),
+ ),
+ ],
+ ),
+ SizedBox(),
+ NewPrimaryButton(
+ text: S.of(context).continue_text,
+ onPressed: () => Navigator.of(context).pop(_paringCodeController.text),
+ color: Theme.of(context).colorScheme.primary,
+ textColor: Theme.of(context).colorScheme.onPrimary,
+ )
+ ],
+ ),
+ )
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/new-ui/widgets/modal_header.dart b/lib/new-ui/widgets/modal_header.dart
index 46c292cea..6bef2bba5 100644
--- a/lib/new-ui/widgets/modal_header.dart
+++ b/lib/new-ui/widgets/modal_header.dart
@@ -4,11 +4,12 @@ import 'package:flutter_svg/svg.dart';
class ModalHeader extends StatelessWidget {
const ModalHeader(
- {super.key, required this.iconPath, required this.message, required this.title});
+ {super.key, required this.iconPath, required this.message, required this.title, this.bottomWidget});
final String iconPath;
final String title;
final String message;
+ final Widget? bottomWidget;
@override
Widget build(BuildContext context) {
@@ -41,7 +42,11 @@ class ModalHeader extends StatelessWidget {
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant),
textAlign: TextAlign.center,
- ))
+ )),
+ if(bottomWidget != null) ...[
+ Padding(padding: EdgeInsets.symmetric(horizontal: 18), child: Container(height:1, color: Theme.of(context).colorScheme.surfaceContainerHigh),),
+ bottomWidget!
+ ]
],
),
),
diff --git a/lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart b/lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart
index 68f8c6889..671579914 100644
--- a/lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart
+++ b/lib/new-ui/widgets/send_page/send_confirm_bottom_widget.dart
@@ -1,16 +1,14 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/confirm_swiper.dart';
+import 'package:cake_wallet/new-ui/widgets/hardware_wallet/proceed_on_device_message.dart';
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
-import 'package:cw_core/wallet_info.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
-import 'package:flutter_svg/svg.dart';
-import 'package:vector_graphics/vector_graphics.dart';
class SendConfirmBottomWidget extends StatelessWidget {
const SendConfirmBottomWidget({super.key, required this.sendViewModel});
@@ -61,7 +59,7 @@ class SendConfirmBottomWidget extends StatelessWidget {
text: "${S.of(context).signing_transaction}...",
);
case IsAwaitingDeviceResponseState:
- return HardwareWalletConfirmationMessage(
+ return HardwareWalletProceedOnDeviceMessage(
hardwareWalletType: sendViewModel.wallet.hardwareWalletType!);
case TransactionCommitting:
return LoadingBottomWidget(
@@ -173,58 +171,3 @@ class TransactionErrorActions extends StatelessWidget {
);
}
}
-
-class HardwareWalletConfirmationMessage extends StatelessWidget {
- const HardwareWalletConfirmationMessage({super.key, required this.hardwareWalletType});
-
- final HardwareWalletType hardwareWalletType;
-
- @override
- Widget build(BuildContext context) {
- return Container(
- width: double.infinity,
- decoration: BoxDecoration(
- border: Border.all(
- color: Theme.of(context).colorScheme.primary,
- ),
- borderRadius: BorderRadius.circular(16),
- ),
- child: Padding(
- padding: const EdgeInsets.all(12.0),
- child: Column(spacing: 8, children: [
- if (hardwareWalletIcon != null)
- CakeImageWidget(imageUrl:
- hardwareWalletIcon!,
- width: 36,
- height: 36,
- colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
- ),
- Text(
- S.of(context).proceed_on_device,
- style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w400,
- color: Theme.of(context).colorScheme.onSurface),
- )
- ]),
- ),
- );
- }
-
- String? get hardwareWalletIcon {
- switch (hardwareWalletType) {
- case HardwareWalletType.bitbox:
- return "assets/images/hardware_wallet/device_bitbox.svg";
- case HardwareWalletType.ledger:
- return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
- case HardwareWalletType.trezor:
- return "assets/images/hardware_wallet/device_trezor_safe_5.svg";
- case HardwareWalletType.cupcake:
- return "assets/images/cupcake.svg";
- case HardwareWalletType.coldcard:
- case HardwareWalletType.seedsigner:
- case HardwareWalletType.keystone:
- return "assets/images/hardware_wallet/device_qr.svg";
- }
- }
-}
diff --git a/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart b/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart
index 9ccfb1453..3676cd6e4 100644
--- a/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart
+++ b/lib/new-ui/widgets/swap_page/swap_confirm_sheet.dart
@@ -41,7 +41,7 @@ class _SwapConfirmSheetState extends State {
final sendVM = widget.exchangeTradeViewModel.sendViewModel;
if (sendVM.wallet.isHardwareWallet) {
- if (!sendVM.hardwareWalletViewModel!.isConnected) {
+ if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendVM.walletType,
diff --git a/lib/router.dart b/lib/router.dart
index a5af558eb..89365171a 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -371,7 +371,7 @@ Route createRoute(RouteSettings settings) {
(_) => getIt.get(
param1: NewWalletTypeArguments(
onTypeSelected: (BuildContext context, WalletType type) {
- if (hardwareWalletType == HardwareWalletType.trezor) {
+ if (hardwareWalletType == HardwareWalletType.trezor && type != WalletType.monero) {
Navigator.of(context).pushNamed(Routes.chooseHardwareWalletAccount,
arguments: [type, hardwareWalletType]);
return;
diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart
index 2122db44a..22e466cf3 100644
--- a/lib/src/screens/connect_device/connect_device_page.dart
+++ b/lib/src/screens/connect_device/connect_device_page.dart
@@ -3,10 +3,13 @@ import 'dart:io';
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/main.dart';
+import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
@@ -91,6 +94,8 @@ class ConnectDevicePageBodyState extends State {
var bleDevices = [];
var usbDevices = [];
+ List get allDevices => [...bleDevices, ...usbDevices];
+
late Timer? _usbRefreshTimer = null;
late Timer? _bleRefreshTimer = null;
late Timer? _bleStateTimer = null;
@@ -158,24 +163,38 @@ class ConnectDevicePageBodyState extends State {
Future _connectToDevice(HardwareWalletDevice device) async {
final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType);
- if (isConnected) widget.onConnectDevice(context, widget.hardwareWalletVM);
+
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ if (isConnected) widget.onConnectDevice(navigatorKey.currentContext!, widget.hardwareWalletVM);
+ });
}
String _getDeviceTileLeading(HardwareWalletDeviceType deviceType) {
switch (deviceType) {
case HardwareWalletDeviceType.ledgerNanoX:
- return 'assets/images/hardware_wallet/ledger_nano_x.png';
+ return 'assets/new-ui/hardware_wallets/ledger_nano_x.svg';
case HardwareWalletDeviceType.ledgerNanoGen5:
- return 'assets/images/hardware_wallet/device_ledger_nano_gen_5.svg';
+ return 'assets/new-ui/hardware_wallets/device_ledger_nano_gen_5.svg';
case HardwareWalletDeviceType.ledgerStax:
- return 'assets/images/hardware_wallet/ledger_stax.png';
+ return 'assets/new-ui/hardware_wallets/ledger_stax.svg';
case HardwareWalletDeviceType.ledgerFlex:
- return 'assets/images/hardware_wallet/ledger_flex.png';
+ return 'assets/new-ui/hardware_wallets/ledger_flex.svg';
case HardwareWalletDeviceType.BitBox02:
- return 'assets/images/hardware_wallet/device_bitbox.svg';
+ case HardwareWalletDeviceType.BitBox02Nova:
+ return 'assets/new-ui/hardware_wallets/device_bitbox.svg';
+ case HardwareWalletDeviceType.trezorModelOne:
+ return 'assets/new-ui/hardware_wallets/device_trezor_model_one.svg';
+ case HardwareWalletDeviceType.trezorModelT:
+ return 'assets/new-ui/hardware_wallets/device_trezor_model_t.svg';
+ case HardwareWalletDeviceType.trezorSafe3:
+ return 'assets/new-ui/hardware_wallets/device_trezor_safe_3.svg';
+ case HardwareWalletDeviceType.trezorSafe5:
+ return 'assets/new-ui/hardware_wallets/device_trezor_safe_5.svg';
+ case HardwareWalletDeviceType.trezorSafe7:
+ return 'assets/new-ui/hardware_wallets/device_trezor_safe_7.svg';
default:
- return 'assets/images/hardware_wallet/ledger_nano_x.png';
+ return 'assets/new-ui/hardware_wallets/ledger_nano_x.svg';
}
}
@@ -197,25 +216,15 @@ class ConnectDevicePageBodyState extends State {
Expanded(
child: SingleChildScrollView(
child: Column(
+ spacing: 24,
children: [
- Padding(
- padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
- child: Text(
- description,
- style: Theme.of(context).textTheme.titleMedium,
- textAlign: TextAlign.center,
- ),
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/hardware_wallet.svg",
),
- Offstage(
- offstage: !longWait,
- child: Padding(
- padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
- child: Text(
- S.of(context).if_you_dont_see_your_device,
- style: Theme.of(context).textTheme.titleMedium,
- textAlign: TextAlign.center,
- ),
- ),
+ Text(
+ description,
+ style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
+ textAlign: TextAlign.center,
),
Observer(
builder: (_) => Offstage(
@@ -231,56 +240,69 @@ class ConnectDevicePageBodyState extends State {
),
),
),
- if (bleDevices.length > 0) ...[
- Padding(
- padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
- child: Container(
- width: double.infinity,
- child: Text(
- S.of(context).bluetooth,
- style: Theme.of(context).textTheme.bodyMedium,
- ),
- ),
- ),
- ...bleDevices
- .map(
- (device) => Padding(
- padding: EdgeInsets.only(bottom: 20),
- child: DeviceTile(
- onPressed: () => _connectToDevice(device),
- title: device.name,
- leading: _getDeviceTileLeading(device.type),
- connectionType: device.connectionType,
+ Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ borderRadius: BorderRadius.circular(18)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemBuilder: (context, index) {
+ final item = allDevices[index];
+ return GestureDetector(
+ onTap: () => _connectToDevice(item),
+ child: SizedBox(
+ height: 48,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ spacing: 12,
+ children: [
+ CakeImageWidget(
+ imageUrl: _getDeviceTileLeading(item.type),
+ width: 24,
+ height: 24,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onSurfaceVariant,
+ BlendMode.srcIn),
+ ),
+ Text(item.name)
+ ],
+ ),
+ Row(
+ spacing: 12,
+ children: [
+ if (item.connectionType ==
+ HardwareWalletConnectionType.ble)
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/bluetooth.svg",
+ width: 24,
+ height: 24,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.primary,
+ BlendMode.srcIn),
+ ),
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/arrow_forward.svg",
+ height: 16,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onSurfaceVariant,
+ BlendMode.srcIn))
+ ],
+ )
+ ],
+ ),
+ ),
),
- ),
- )
- .toList()
- ],
- if (usbDevices.length > 0) ...[
- Padding(
- padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
- child: Container(
- width: double.infinity,
- child: Text(
- S.of(context).usb,
- style: Theme.of(context).textTheme.bodyMedium,
- ),
- ),
- ),
- ...usbDevices
- .map(
- (device) => Padding(
- padding: EdgeInsets.only(bottom: 20),
- child: DeviceTile(
- onPressed: () => _connectToDevice(device),
- title: device.name,
- leading: _getDeviceTileLeading(device.type),
- connectionType: device.connectionType,
- ),
- ),
- )
- .toList(),
- ],
+ );
+ },
+ separatorBuilder: (context, index) => Container(
+ height: 1,
+ color: Theme.of(context).colorScheme.surfaceContainerHigh),
+ itemCount: allDevices.length),
+ ),
if (widget.allowChangeWallet) ...[
PrimaryButton(
text: S.of(context).wallets,
@@ -293,10 +315,10 @@ class ConnectDevicePageBodyState extends State {
),
),
),
- PrimaryButton(
+ NewPrimaryButton(
text: S.of(context).how_to_connect,
color: Theme.of(context).colorScheme.surfaceContainer,
- textColor: Theme.of(context).colorScheme.onSecondaryContainer,
+ textColor: Theme.of(context).colorScheme.primary,
onPressed: () => _onHowToConnect(context),
)
],
diff --git a/lib/src/screens/connect_device/widgets/device_tile.dart b/lib/src/screens/connect_device/widgets/device_tile.dart
index 803a648bb..4014e5040 100644
--- a/lib/src/screens/connect_device/widgets/device_tile.dart
+++ b/lib/src/screens/connect_device/widgets/device_tile.dart
@@ -1,4 +1,5 @@
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -43,14 +44,12 @@ class DeviceTile extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
- if (leading != null && !leading!.endsWith(".svg"))
- Image.asset(
- leading!,
- height: 30,
- color: Theme.of(context).colorScheme.onSurface,
- ),
- if (leading != null && leading!.endsWith(".svg"))
- SvgPicture.asset(leading!, height: 30),
+ CakeImageWidget(
+ height: 30,
+ imageUrl: leading,
+ colorFilter:
+ ColorFilter.mode(Theme.of(context).colorScheme.onSurface, BlendMode.srcIn),
+ ),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 16),
diff --git a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
index a6ea7b340..de05bccf4 100644
--- a/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
+++ b/lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
@@ -34,11 +34,11 @@ class CryptoBalanceWidget extends StatelessWidget {
case null:
return null;
case HardwareWalletType.bitbox:
- return "assets/images/hardware_wallet/device_bitbox.svg";
+ return "assets/new-ui/hardware_wallets/device_bitbox.svg";
case HardwareWalletType.ledger:
- return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
+ return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
case HardwareWalletType.trezor:
- return "assets/images/hardware_wallet/device_trezor_safe_5.svg";
+ return "assets/new-ui/hardware_wallets/device_trezor_safe_5.svg";
case HardwareWalletType.cupcake:
return "assets/images/cupcake.svg";
case HardwareWalletType.coldcard:
diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart
index d7fd3196e..6ebd2526f 100644
--- a/lib/src/screens/exchange_trade/exchange_trade_page.dart
+++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart
@@ -213,7 +213,7 @@ class ExchangeTradeState extends State {
final sendVM = widget.exchangeTradeViewModel.sendViewModel;
if (sendVM.wallet.isHardwareWallet) {
- if (!sendVM.hardwareWalletViewModel!.isConnected) {
+ if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendVM.walletType,
diff --git a/lib/src/screens/integrations/deuro/savings_page.dart b/lib/src/screens/integrations/deuro/savings_page.dart
index 4a5d62a02..a08437822 100644
--- a/lib/src/screens/integrations/deuro/savings_page.dart
+++ b/lib/src/screens/integrations/deuro/savings_page.dart
@@ -160,7 +160,7 @@ class DEuroSavingsPage extends BasePage {
Future _requireHardwareWallet(BuildContext context) async {
if (_dEuroViewModel.wallet.isHardwareWallet) {
- if (!_dEuroViewModel.hardwareWalletViewModel!.isConnected) {
+ if (!_dEuroViewModel.hardwareWalletViewModel!.isConnected(_dEuroViewModel.wallet.type)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: _dEuroViewModel.wallet.type,
diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart
index fff206836..4b170dc03 100644
--- a/lib/src/screens/new_wallet/new_wallet_type_page.dart
+++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart
@@ -6,14 +6,8 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_utils.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
-import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
-import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
-import 'package:cake_wallet/src/widgets/primary_button.dart';
-import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
-import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
-import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
@@ -21,10 +15,38 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
+class NewWalletTypePageHeader extends StatelessWidget {
+ const NewWalletTypePageHeader({super.key});
+
+ @override
+ Widget build(BuildContext context) => const SizedBox(
+ width: 138,
+ height: 75,
+ child: Stack(
+ children: [
+ Positioned(
+ left: 0,
+ child: CakeImageWidget(
+ imageUrl: "assets/new-ui/hardware_wallet.svg",
+ width: 75,
+ height: 75,
+ ),
+ ),
+ Positioned(
+ left: 63,
+ child: CakeImageWidget(
+ imageUrl: "assets/new-ui/cake_coin.svg",
+ width: 75,
+ height: 75,
+ ),
+ ),
+ ],
+ ),
+ );
+}
+
class NewWalletTypePage extends BasePage {
- NewWalletTypePage({
- required this.newWalletTypeArguments,
- });
+ NewWalletTypePage({required this.newWalletTypeArguments});
final NewWalletTypeArguments newWalletTypeArguments;
@@ -38,23 +60,21 @@ class NewWalletTypePage extends BasePage {
@override
Function(BuildContext)? get pushToNextWidget => (context) {
- FocusScopeNode currentFocus = FocusScope.of(context);
- if (!currentFocus.hasPrimaryFocus) {
- currentFocus.focusedChild?.unfocus();
- }
- };
+ final currentFocus = FocusScope.of(context);
+ if (!currentFocus.hasPrimaryFocus) currentFocus.focusedChild?.unfocus();
+ };
@override
Widget body(BuildContext context) => WalletTypeForm(
- walletImage: currentTheme.isDark ? walletTypeImage : walletTypeLightImage,
- isCreate: newWalletTypeArguments.isCreate,
- onTypeSelected: newWalletTypeArguments.onTypeSelected,
- hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
- );
+ walletImage: currentTheme.isDark ? walletTypeImage : walletTypeLightImage,
+ isCreate: newWalletTypeArguments.isCreate,
+ onTypeSelected: newWalletTypeArguments.onTypeSelected,
+ hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
+ );
}
class WalletTypeForm extends StatefulWidget {
- WalletTypeForm({
+ const WalletTypeForm({
required this.walletImage,
required this.isCreate,
this.onTypeSelected,
@@ -77,9 +97,6 @@ class WalletTypeFormState extends State {
static const aspectRatioImage = 1.22;
- final TextEditingController searchController = TextEditingController();
-
- WalletType? selected;
List types;
List filteredTypes = [];
@@ -87,21 +104,12 @@ class WalletTypeFormState extends State {
void initState() {
types = filteredTypes = availableWalletTypes
.where((element) =>
- !widget.isHardwareWallet ||
- DeviceConnectionType.supportedConnectionTypes(
- element, widget.hardwareWalletType!, Platform.isIOS)
- .isNotEmpty)
+ !widget.isHardwareWallet ||
+ DeviceConnectionType.supportedConnectionTypes(
+ element, widget.hardwareWalletType!, Platform.isIOS)
+ .isNotEmpty)
.toList();
super.initState();
-
- searchController.addListener(() {
- setState(() {
- filteredTypes = List.from(types.where((type) => walletTypeToDisplayName(type)
- .toLowerCase()
- .contains(searchController.text.toLowerCase())));
- return;
- });
- });
}
@override
@@ -110,96 +118,102 @@ class WalletTypeFormState extends State {
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
+ spacing: 24,
children: [
- Padding(
- padding: EdgeInsets.only(top: 48),
- child: Text(
- S.of(context).choose_wallet_currency,
- textAlign: TextAlign.center,
- style: Theme.of(context).textTheme.titleMedium?.copyWith(
- color: Theme.of(context).colorScheme.onSurfaceVariant,
- ),
- ),
+ const NewWalletTypePageHeader(),
+ Text(
+ S.of(context).choose_wallet_currency,
+ textAlign: TextAlign.center,
),
- Padding(
- padding: EdgeInsets.fromLTRB(24, 24, 24, 12),
- child: SearchBarWidget(searchController: searchController),
- ),
- Expanded(
- child: ScrollableWithBottomSection(
- contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
- scrollableKey: ValueKey('new_wallet_type_scrollable_key'),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- ...filteredTypes.map(
- (type) => Padding(
- padding: EdgeInsets.only(top: 12),
- child: SelectButton(
- key: ValueKey('new_wallet_type_${type.name}_button_key'),
- image: CakeImageWidget(
- imageUrl: getCryptoCurrencyIconForWalletListItem(type),
- height: 24,
- width: 24,
+ Flexible(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 18),
+ child: Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ borderRadius: BorderRadius.circular(18),
+ ),
+ child: ListView.separated(
+ key: const ValueKey('new_wallet_type_scrollable_key'),
+ itemCount: filteredTypes.length,
+ shrinkWrap: true,
+ itemBuilder: (context, index) {
+ final item = filteredTypes[index];
+ final curr = getCryptoCurrencyForWalletListItem(item);
+
+ return GestureDetector(
+ key: ValueKey('new_wallet_type_${item.name}_button_key'),
+ behavior: HitTestBehavior.opaque,
+ onTap: () {
+ onTypeSelected(item);
+ },
+ child: SizedBox(
+ height: 48,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ CakeImageWidget(
+ height: 24,
+ width: 24,
+ imageUrl: curr.iconPath,
+ ),
+ const SizedBox(width: 12),
+ Text(curr.fullName ?? curr.name),
+ const SizedBox(width: 4),
+ Text(
+ curr.title,
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ )
+ ],
+ ),
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/arrow_forward.svg",
+ height: 16,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onSurfaceVariant,
+ BlendMode.srcIn,
+ ),
+ )
+ ],
+ ),
),
- text: walletTypeToDisplayName(type),
- showTrailingIcon: false,
- height: 54,
- isSelected: selected == type,
- onTap: () => setState(() => selected = type),
- deviceConnectionTypes: widget.isHardwareWallet
- ? DeviceConnectionType.supportedConnectionTypes(
- type, widget.hardwareWalletType!, Platform.isIOS)
- : [],
),
- ),
+ );
+ },
+ separatorBuilder: (context, index) => Container(
+ height: 1,
+ color: Theme.of(context).colorScheme.surfaceContainerHigh,
),
- ],
- ),
- bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
- bottomSection: PrimaryButton(
- key: ValueKey('new_wallet_type_next_button_key'),
- onPressed: () => onTypeSelected(),
- text: S.of(context).seed_language_next,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
- isDisabled: selected == null,
+ ),
),
),
- ),
+ )
],
),
),
);
}
- Future onTypeSelected() async {
- if (selected == null) throw Exception('Wallet Type is not selected yet.');
-
- if (selected == WalletType.haven && widget.isCreate) {
- return await showPopUp(
- context: context,
- builder: (BuildContext context) => PopUpCancellableAlertDialog(
- contentText: S.of(context).pause_wallet_creation,
- actionButtonText: S.of(context).ok,
- buttonAction: () => Navigator.of(context).pop(),
- ),
- );
- }
-
+ Future onTypeSelected(WalletType selected) async {
// If it's a restore flow, trigger the external callback
// If it's not a BIP39 Wallet or if there are no other wallets, route to the newWallet page
// Any other scenario, route to pre-existing seed page
final walletList = await WalletInfo.getAll();
if (!widget.isCreate) {
- widget.onTypeSelected!(context, selected!);
- } else if (!isBIP39Wallet(selected!) || walletList.isEmpty) {
+ widget.onTypeSelected!(context, selected);
+ } else if (!isBIP39Wallet(selected) || walletList.isEmpty) {
Navigator.of(context).pushNamed(
Routes.newWallet,
- arguments: NewWalletArguments(type: selected!),
+ arguments: NewWalletArguments(type: selected),
);
} else {
- Navigator.of(context).pushNamed(Routes.walletGroupDescription, arguments: selected!);
+ Navigator.of(context).pushNamed(Routes.walletGroupDescription, arguments: selected);
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/src/screens/nodes/node_create_or_edit_page.dart b/lib/src/screens/nodes/node_create_or_edit_page.dart
index 92bccb27a..0e45c51f4 100644
--- a/lib/src/screens/nodes/node_create_or_edit_page.dart
+++ b/lib/src/screens/nodes/node_create_or_edit_page.dart
@@ -1,20 +1,20 @@
import 'package:cake_wallet/core/execution_state.dart';
+import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
+import 'package:cake_wallet/src/screens/nodes/node_share_page.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
-import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
-import 'package:cake_wallet/src/widgets/primary_button.dart';
-import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
+import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
+import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_type.dart';
+import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_mobx/flutter_mobx.dart';
-import 'package:flutter_svg/svg.dart';
import 'package:mobx/mobx.dart';
class NodeCreateOrEditPage extends StatefulWidget {
@@ -76,6 +76,14 @@ class _NodeCreateOrEditPageState extends State {
},
);
+
+ reaction((_)=>widget.nodeCreateOrEditViewModel.state, (state) async {
+ if(state is ExecutedSuccessfullyState) {
+ await Future.delayed(Duration(milliseconds: 100));
+ if (mounted) Navigator.of(context).pop(widget.nodeCreateOrEditViewModel.editingNode);
+ }
+
+ });
}
@override
@@ -90,86 +98,62 @@ class _NodeCreateOrEditPageState extends State {
title: widget.editingNode != null ? S.current.edit_node : S.current.node_new,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: Navigator.of(context).pop,
- trailingIcon: CakeImageWidget(imageUrl:
- "assets/new-ui/scan.svg",width:24,height:24,
- colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
- ),
- onTrailingPressed: () => widget.nodeCreateOrEditViewModel.scanQRCodeForNewNode(context),
+ trailingIcon: Icon(Icons.check),
+ onTrailingPressed: () async {
+ if (_nodeFormKey.currentState != null && !_nodeFormKey.currentState!.validate()) {
+ return;
+ }
+
+ await widget.nodeCreateOrEditViewModel
+ .save(saveAsCurrent: widget.isSelected ?? false);
+ },
),
Expanded(
child: KeyboardHideOverlay(
child: Container(
padding: const EdgeInsets.only(left: 18, right: 18),
- child: ScrollableWithBottomSection(
- contentPadding: const EdgeInsets.only(bottom: 24.0, top: 8),
- content: NodeForm(
+ child: Column(
+ spacing: 18,
+ children: [NodeForm(
key: _nodeFormKey,
nodeViewModel: widget.nodeCreateOrEditViewModel,
),
- bottomSectionPadding: const EdgeInsets.only(bottom: 24),
- bottomSection: Observer(
- builder: (_) => Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Flexible(
- child: Container(
- padding: const EdgeInsets.only(right: 8.0),
- child: LoadingPrimaryButton(
- onPressed: () async {
- final confirmed = await showPopUp(
- context: context,
- builder: (context) => AlertWithTwoActions(
- alertTitle: S.of(context).remove_node,
- alertContent: S.of(context).remove_node_message,
- rightButtonText: S.of(context).remove,
- leftButtonText: S.of(context).cancel,
- actionRightButton: () => Navigator.pop(context, true),
- actionLeftButton: () => Navigator.pop(context, false),
- ),
- ) ??
- false;
+ NewListSections(sections: {"":[
+ if(widget.editingNode != null)
- if (confirmed) {
- await widget.nodeCreateOrEditViewModel.delete(editingNode: widget.editingNode!);
- Navigator.of(context).pop();
- }
- },
- text: S.of(context).delete,
- isDisabled: widget.editingNode == null ||
- !widget.nodeCreateOrEditViewModel.isReady ||
- widget.editingNode!.isBuiltin ||
- (widget.isSelected ?? false),
- color: Theme.of(context).colorScheme.errorContainer,
- textColor: Theme.of(context).colorScheme.onErrorContainer,
- ),
- ),
- ),
- Flexible(
- child: Container(
- padding: const EdgeInsets.only(left: 8.0),
- child: PrimaryButton(
- onPressed: () async {
- if (_nodeFormKey.currentState != null && !_nodeFormKey.currentState!.validate()) {
- return;
- }
+ ListItemRegularRow(keyValue: "share", label: S.of(context).share_this_node, foregroundColor: Theme.of(context).colorScheme.primary, showArrow: false, onTap: (){
- await widget.nodeCreateOrEditViewModel.save(
- saveAsCurrent: widget.isSelected ?? false);
- if (context.mounted) {
- Navigator.of(context).pop();
- }
- },
- text: S.of(context).save,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
- isDisabled: (!widget.nodeCreateOrEditViewModel.isReady) ||
- (widget.nodeCreateOrEditViewModel.connectionState is IsExecutingState),
- ),
- ),
- ),
- ],
- ),
- ),
+ Navigator.of(context).push(CupertinoPageRoute(builder: (context)=>NodeSharePage(uri: widget.editingNode!.uri, currency: walletTypeToCryptoCurrency(widget.type!))));
+
+
+ }),
+ if(!(widget.editingNode == null ||
+ !widget.nodeCreateOrEditViewModel.isReady ||
+ widget.editingNode!.isBuiltin ||
+ (widget.isSelected ?? false)))
+ ListItemRegularRow(keyValue: "delete", label: S.of(context).delete_this_node, foregroundColor: Theme.of(context).colorScheme.errorContainer, showArrow: false, onTap: ()async{
+ final confirmed = await showPopUp(
+ context: context,
+ builder: (context) => AlertWithTwoActions(
+ alertTitle: S.of(context).remove_node,
+ alertContent: S.of(context).remove_node_message,
+ rightButtonText: S.of(context).remove,
+ leftButtonText: S.of(context).cancel,
+ actionRightButton: () => Navigator.pop(context, true),
+ actionLeftButton: () => Navigator.pop(context, false),
+ ),
+ ) ??
+ false;
+
+ if (confirmed) {
+ await widget.nodeCreateOrEditViewModel.delete(editingNode: widget.editingNode!);
+ Navigator.of(context).pop();
+ }
+
+ })
+ ]})
+
+ ]
),
),
),
diff --git a/lib/src/screens/nodes/node_share_page.dart b/lib/src/screens/nodes/node_share_page.dart
new file mode 100644
index 000000000..1319d8426
--- /dev/null
+++ b/lib/src/screens/nodes/node_share_page.dart
@@ -0,0 +1,82 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
+import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
+import 'package:cw_core/crypto_currency.dart';
+import 'package:flutter/material.dart';
+
+class NodeSharePage extends StatelessWidget {
+ const NodeSharePage({super.key, required this.uri, required this.currency});
+
+ final Uri uri;
+ final CryptoCurrency currency;
+
+ @override
+ Widget build(BuildContext context) {
+ final qrSize = MediaQuery.of(context).size.width * 0.786;
+ final padding = 16.0;
+ return Container(
+ color: Theme.of(context).colorScheme.surface,
+ child: SafeArea(
+ child: Column(
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ ModalTopBar(
+ title: S.of(context).share,
+ leadingIcon: Icon(Icons.arrow_back_ios_new),
+ onLeadingPressed: Navigator.of(context).pop,
+ ),
+ Expanded(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ spacing: 40,
+ children: [
+ Stack(alignment: Alignment.center, children: [
+ SizedBox(
+ width: qrSize,
+ height: qrSize,
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(18),
+ child: QrImage(
+ size: qrSize - padding * 2,
+ embeddedImagePath: "",
+ data: uri.toString())),
+ ),
+ SizedBox(
+ width: 60,
+ height: 60,
+ child: Stack(
+ children: [
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/settings_row_icons/nodes.svg",
+ width: 54,
+ height: 54,
+ ),
+ Align(
+ alignment: Alignment.bottomRight,
+ child: CakeImageWidget(
+ imageUrl: currency.iconPath,
+ width: 24,
+ height: 24,
+ ))
+ ],
+ ),
+ ),
+ ]),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 36.0),
+ child: Text(S.of(context).node_share_desc,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontWeight: FontWeight.w500,
+ color: Theme.of(context).colorScheme.onSurfaceVariant)),
+ )
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/src/screens/nodes/widgets/node_list_row.dart b/lib/src/screens/nodes/widgets/node_list_row.dart
index 2e43ad3ff..5be027481 100644
--- a/lib/src/screens/nodes/widgets/node_list_row.dart
+++ b/lib/src/screens/nodes/widgets/node_list_row.dart
@@ -1,115 +1,55 @@
import 'package:cake_wallet/routes.dart';
-import 'package:cake_wallet/src/screens/nodes/widgets/node_indicator.dart';
-import 'package:cake_wallet/src/widgets/standard_list.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
+import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:flutter/material.dart';
-class NodeListRow extends StandardListRow {
+class NodeListRow extends StatelessWidget {
NodeListRow(
- {required String title,
- String? subtitle,
- required this.node,
- required void Function(BuildContext context) onTap,
- required bool isSelected,
- required this.isPow})
- : super(title: title, onTap: onTap, isSelected: isSelected, subtitle: subtitle);
+ {required this.node,
+ required this.onTap,
+ required this.onEditComplete,
+ required this.isSelected,
+ required this.isPow, this.speed});
final Node node;
final bool isPow;
+ final VoidCallback onTap;
+ final Future Function(Object?) onEditComplete;
+ final bool isSelected;
+ final NodeSpeed? speed;
@override
Widget build(BuildContext context) {
final leading = buildLeading(context);
final trailing = buildTrailing(context);
- return Container(
- padding: EdgeInsets.only(left: 18, right: 18, top: 2, bottom: 2),
- margin: EdgeInsets.only(top: 2, bottom: 2),
- child: FilledButton(
- onPressed: () => onTap?.call(context),
- style: FilledButton.styleFrom(
- backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.all(Radius.circular(20)),
- ),
- ),
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 8.0),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- leading,
- buildCenter(context, hasLeftOffset: true),
- trailing,
- ],
- ),
- ),
- ),
- );
- }
- @override
- Widget buildLeading(BuildContext context) {
- return FutureBuilder(
- future: node.requestNode(),
- builder: (context, snapshot) {
- switch (snapshot.connectionState) {
- case ConnectionState.done:
- return NodeIndicator(isLive: snapshot.data ?? false);
- default:
- return NodeIndicator(isLive: false);
- }
- });
- }
-
- @override
- Widget buildTrailing(BuildContext context) {
+ final hasLabel = node.label != null && node.label!.isNotEmpty;
+ final uriText = "${node.uri.host}:${node.uri.port}";
return GestureDetector(
- onTap: () => Navigator.of(context).pushNamed(
- isPow ? Routes.newPowNode : Routes.newNode,
- arguments: {'editingNode': node, 'isSelected': isSelected},
- ),
- child: Container(
- padding: EdgeInsets.all(10),
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: Theme.of(context).colorScheme.surface,
- ),
- child: Icon(
- Icons.edit,
- size: 14,
- color: Theme.of(context).colorScheme.onSurfaceVariant,
- ),
- ),
- );
- }
-}
-
-class NodeHeaderListRow extends StandardListRow {
- NodeHeaderListRow({required String title, required void Function(BuildContext context) onTap})
- : super(title: title, onTap: onTap, isSelected: false);
-
- @override
- Widget build(BuildContext context) {
- final leading = buildLeading(context);
- final trailing = buildTrailing(context);
- return Container(
- height: 56,
- padding: EdgeInsets.only(left: 12, right: 12, top: 2, bottom: 2),
- child: TextButton(
- onPressed: () => onTap?.call(context),
- style: ButtonStyle(
- backgroundColor: WidgetStateProperty.all(Theme.of(context).colorScheme.surface),
- shape: WidgetStateProperty.all(
- RoundedRectangleBorder(
- borderRadius: BorderRadius.all(Radius.circular(10)),
- ),
- ),
- ),
+ behavior: HitTestBehavior.opaque,
+ onTap: onTap,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 12),
child: Row(
+ spacing: 12,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- if (leading != null) leading,
- buildCenter(context, hasLeftOffset: leading != null),
+ leading,
+ Expanded(
+ child: Column(crossAxisAlignment:CrossAxisAlignment.start,spacing:4,children: [
+ Row(
+ spacing: 4,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Text(hasLabel ? node.label! : uriText, style: TextStyle(fontSize: 12),),
+ if(badgePath != null)
+ CakeImageWidget(imageUrl: badgePath, colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary,BlendMode.srcIn),)
+ ],
+ ),
+ if(hasLabel) Text(uriText, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),),
+ ],),
+ ),
trailing,
],
),
@@ -117,12 +57,44 @@ class NodeHeaderListRow extends StandardListRow {
);
}
- @override
+ Widget buildLeading(BuildContext context) => isSelected
+ ? Padding(
+ padding: const EdgeInsets.all(4.0),
+ child: Icon(
+ Icons.check,
+ size: 16,
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ )
+ : SizedBox(width: 24, height: 24);
+
Widget buildTrailing(BuildContext context) {
- return SizedBox(
- width: 20,
- child: Icon(Icons.add,
- color: Theme.of(context).colorScheme.onSurfaceVariant, size: 24.0),
+ return Row(
+ children: [
+ if (speed != null) CakeImageWidget(imageUrl: speed!.iconPath, width: 24, height: 24),
+ GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: () async {
+ final res = await Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode,
+ arguments: {'editingNode': node, 'isSelected': isSelected});
+ await onEditComplete(res);
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8.0),
+ child: CakeImageWidget(
+ imageUrl: "assets/new-ui/3dots_vertical.svg",
+ colorFilter:
+ ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
+ ),
+ ),
+ )
+ ],
);
}
+
+ String? get badgePath {
+ if(node.isOfficial) return "assets/new-ui/official_node.svg";
+ if(node.isBuiltin) return "assets/new-ui/default_node.svg";
+ return null;
+ }
}
diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart
index 7e44e413d..db0d5e883 100644
--- a/lib/src/screens/send/send_page.dart
+++ b/lib/src/screens/send/send_page.dart
@@ -426,7 +426,7 @@ class SendPage extends BasePage {
}
if (sendViewModel.wallet.isHardwareWallet) {
- if (!sendViewModel.hardwareWalletViewModel!.isConnected) {
+ if (!sendViewModel.hardwareWalletViewModel!.isConnected(sendViewModel.walletType)) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendViewModel.walletType,
diff --git a/lib/src/screens/settings/manage_nodes_page.dart b/lib/src/screens/settings/manage_nodes_page.dart
index 02c2e4e6f..9f69c6587 100644
--- a/lib/src/screens/settings/manage_nodes_page.dart
+++ b/lib/src/screens/settings/manage_nodes_page.dart
@@ -1,42 +1,42 @@
import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/new-ui/widgets/modal_header.dart';
import 'package:cake_wallet/new-ui/widgets/modal_page_wrapper.dart';
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/routes.dart';
-import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart';
-import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
-import 'package:cake_wallet/src/widgets/standard_list.dart';
-import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
-import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
+import 'package:cw_core/node.dart';
+import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
-class ManageNodesPage extends BasePage {
- ManageNodesPage(this.isPow, {this.nodeListViewModel, this.powNodeListViewModel, this.dashboardViewModel})
- : assert((isPow && powNodeListViewModel != null) || (!isPow && nodeListViewModel != null)),
- assert(powNodeListViewModel == null || nodeListViewModel == null);
+class ManageNodesPage extends StatefulWidget {
+ ManageNodesPage(this.isPow, {required this.nodeListViewModel, this.dashboardViewModel});
final DashboardViewModel? dashboardViewModel;
- final NodeListViewModel? nodeListViewModel;
- final PowNodeListViewModel? powNodeListViewModel;
+ final NodeListViewModel nodeListViewModel;
final bool isPow;
@override
- bool get hideAppBar => true;
+ State createState() => _ManageNodesPageState();
+}
+
+class _ManageNodesPageState extends State {
+ @override
+ void initState() {
+ super.initState();
+ Future.delayed(Duration(milliseconds: 300))
+ .then((_) => widget.nodeListViewModel.speedTestNodes());
+ }
@override
- String get title => S.current.manage_nodes;
-
- @override
- Widget body(BuildContext context) {
+ Widget build(BuildContext context) {
return ModalPageWrapper(
- horizontalPadding: 0,
topBar: ModalTopBar(
title: S.of(context).manage_nodes,
leadingIcon: Icon(Icons.arrow_back_ios_new),
@@ -44,87 +44,96 @@ class ManageNodesPage extends BasePage {
trailingWidget: Row(
spacing: 8,
children: [
- if (dashboardViewModel?.hasRescan ?? true)
- ModernButton(
+ Observer(
+ builder: (_) => ModernButton(
size: 36,
- icon: Icon(Icons.history),
- onPressed: () => Navigator.of(context).pushNamed(Routes.rescan)),
+ icon: widget.nodeListViewModel.isTestingNodeSpeed
+ ? CupertinoActivityIndicator()
+ : Icon(Icons.refresh),
+ onPressed: () => widget.nodeListViewModel.speedTestNodes()),
+ ),
ModernButton(
size: 36,
icon: Icon(Icons.add),
- onPressed: () => Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode))
+ onPressed: ()async {
+ final res = await Navigator.of(context)
+ .pushNamed(widget.isPow ? Routes.newPowNode : Routes.newNode);
+ if(res != null && res is Node) {
+ widget.nodeListViewModel.nodes.add(res);
+ }
+ })
],
),
),
+ header: ModalHeader(
+ iconPath: "assets/new-ui/settings_row_icons/nodes.svg",
+ message: S.of(context).nodes_desc,
+ title: S.of(context).nodes,
+ bottomWidget: Observer(
+ builder: (_) => NodeListRow(
+ node: widget.nodeListViewModel.currentNode,
+ speed: widget.nodeListViewModel.nodeSpeedFor(widget.nodeListViewModel.currentNode),
+ onEditComplete: (res)async{
+ if(res != null && res is Node) {
+ widget.nodeListViewModel.nodes.removeWhere((item)=>item.id == res.id);
+ widget.nodeListViewModel.nodes.add(res);
+ }
+
+ },
+ onTap: () {},
+ isSelected: true,
+ isPow: widget.isPow)),
+ ),
content: Column(
children: [
- if (FeatureFlag.isAutomaticNodeSwitchingEnabled)
- Observer(
- builder: (_) => SettingsSwitcherCell(
- key: ValueKey('manage_nodes_page_enable_auto_node_switching_button_key'),
- title: S.current.enable_auto_node_switching,
- value: isPow
- ? powNodeListViewModel!.enableAutomaticNodeSwitching
- : nodeListViewModel!.enableAutomaticNodeSwitching,
- onValueChange: (BuildContext context, bool value) {
- if (isPow) {
- powNodeListViewModel!.setEnableAutomaticNodeSwitching(value);
- } else {
- nodeListViewModel!.setEnableAutomaticNodeSwitching(value);
- }
- },
- ),
- ),
- if (FeatureFlag.isAutomaticNodeSwitchingEnabled) SizedBox(height: 8),
- Observer(
- builder: (BuildContext context) {
- int itemsCount =
- nodeListViewModel?.nodes.length ?? powNodeListViewModel!.nodes.length;
- return SectionStandardList(
- scrollController: ModalScrollController.of(context),
- sectionCount: 1,
- shrinkWrap: true,
- physics: NeverScrollableScrollPhysics(),
- dividerPadding: EdgeInsets.symmetric(horizontal: 24),
- itemCounter: (int sectionIndex) => itemsCount,
- itemBuilder: (_, index) {
- return Observer(
- builder: (context) {
- final node =
- nodeListViewModel?.nodes[index] ?? powNodeListViewModel!.nodes[index];
- late bool isSelected;
- if (isPow) {
- isSelected = node.id == powNodeListViewModel!.currentNode.id;
- } else {
- isSelected = node.id == nodeListViewModel!.currentNode.id;
- }
- final nodeListRow = NodeListRow(
- title: node.uriRaw,
- subtitle: node.label ?? '',
- node: node,
- isSelected: isSelected,
- isPow: isPow,
- onTap: (_) async {
- if (isSelected) {
- return;
- }
+ Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ borderRadius: BorderRadius.circular(18)),
+ child: Observer(
+ builder: (BuildContext context) {
+ int itemsCount = widget.nodeListViewModel.nonCurrentNodes.length;
+ return ListView.separated(
+ controller: ModalScrollController.of(context),
+ shrinkWrap: true,
+ physics: BouncingScrollPhysics(),
+ itemCount: itemsCount,
+ separatorBuilder: (context, index) => Padding(
+ padding: EdgeInsets.symmetric(horizontal: 12),
+ child: Container(
+ height: 1,
+ color: Theme.of(context).colorScheme.surfaceContainerHigh,
+ ),
+ ),
+ itemBuilder: (_, index) {
+ return Observer(
+ builder: (context) {
+ final node = widget.nodeListViewModel.nonCurrentNodes[index];
+ final nodeListRow = NodeListRow(
+ node: node,
+ isSelected: false,
+ isPow: widget.isPow,
+ speed: widget.nodeListViewModel.nodeSpeedFor(node),
+ onEditComplete: (res) async {
+ if(res != null && res is Node) {
+ widget.nodeListViewModel.nodes.removeWhere((item)=>item.id == res.id);
+ widget.nodeListViewModel.nodes.add(res);
+ }
+ },
+ onTap: () async {
await showPopUp(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
- alertContent: nodeListViewModel?.getAlertContent(node.uriRaw) ??
- powNodeListViewModel!.getAlertContent(node.uriRaw),
+ alertContent:
+ widget.nodeListViewModel.getAlertContent(node.uriRaw),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () async {
- if (isPow) {
- await powNodeListViewModel!.setAsCurrent(node);
- } else {
- await nodeListViewModel!.setAsCurrent(node);
- }
+ await widget.nodeListViewModel.setAsCurrent(node);
Navigator.of(context).pop();
},
);
@@ -132,32 +141,16 @@ class ManageNodesPage extends BasePage {
);
},
);
- return nodeListRow;
- },
- );
- },
- );
- },
+ return nodeListRow;
+ },
+ );
+ },
+ );
+ },
+ ),
),
],
),
);
}
- Future _presentReconnectAlert(BuildContext context) async {
- await showPopUp(
- context: context,
- builder: (BuildContext context) {
- return AlertWithTwoActions(
- alertTitle: S.of(context).reconnection,
- alertContent: S.of(context).reconnect_alert_text,
- rightButtonText: S.of(context).ok,
- leftButtonText: S.of(context).cancel,
- actionRightButton: () async {
- Navigator.of(context).pop();
- await dashboardViewModel!.reconnect();
- },
- actionLeftButton: () => Navigator.of(context).pop());
- },
- );
- }
}
diff --git a/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart b/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart
index 44637ac1b..b8e21c9b8 100644
--- a/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart
+++ b/lib/src/screens/wallet_connect/services/bottom_sheet_service.dart
@@ -29,7 +29,6 @@ class BottomSheetServiceImpl implements BottomSheetService {
int closeAfter = 0,
bool isModalDismissible = false,
}) async {
- // Create the bottom sheet queue item
final completer = Completer();
final queueItem = BottomSheetQueueItemModel(
widget: widget,
@@ -38,15 +37,12 @@ class BottomSheetServiceImpl implements BottomSheetService {
isModalDismissible: isModalDismissible,
);
- // If the current sheet it null, set it to the queue item
if (currentSheet.value == null) {
currentSheet.value = queueItem;
} else {
- // Otherwise, add it to the queue
queue.add(queueItem);
}
- // Return the future
return await completer.future;
}
diff --git a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart
index 15d2bdc00..c8618d85f 100644
--- a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart
+++ b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart
@@ -1,6 +1,10 @@
import 'dart:convert';
+import 'package:cake_wallet/di.dart';
+import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
+import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:eth_sig_util/eth_sig_util.dart';
import 'package:eth_sig_util/util/utils.dart';
@@ -106,7 +110,6 @@ class EvmChainServiceImpl {
if (isApproved) {
try {
- // Load the private key
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
final credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@@ -155,7 +158,6 @@ class EvmChainServiceImpl {
if (isApproved) {
try {
- // Load the private key
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
final credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@@ -297,7 +299,6 @@ class EvmChainServiceImpl {
if (transaction is Transaction) {
try {
- // Load the private key
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
final credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@@ -309,7 +310,6 @@ class EvmChainServiceImpl {
chainId: int.parse(chainId),
);
- // Sign the transaction
final signedTx = bytesToHex(signature, include0x: true);
response = response.copyWith(result: signedTx);
} on RPCError catch (e) {
@@ -349,7 +349,6 @@ class EvmChainServiceImpl {
);
if (transaction is Transaction) {
try {
- // Load the private key
final keys = wcKeyService.getKeysForChain(appStore.wallet!);
final credentials = EthPrivateKey.fromHex(keys[0].privateKey);
final chainId = getChainId().split(':').last;
@@ -440,30 +439,26 @@ class EvmChainServiceImpl {
transaction = await _applyWCBufferedFees(transaction);
- final gweiGasPrice =
- (transaction.gasPrice?.getInWei ?? transaction.maxFeePerGas?.getInWei ?? BigInt.zero) /
- BigInt.from(1000000000);
+ final nativeCurrency = evm?.getChainInfoByChainId(reference.chainId ?? 1)?.currency;
+ final nativeSymbol = nativeCurrency?.title ?? 'ETH';
final amount = (transaction.value?.getInWei ?? BigInt.zero) / BigInt.from(1e18);
- final txMessageText = '${S.current.value}: ${amount.toStringAsFixed(9)} ETH\n'
+ final txMessageText = '${S.current.value}: ${amount.toStringAsFixed(9)} $nativeSymbol\n'
'${S.current.from}: ${transaction.from?.hex}\n'
'${S.current.to}: ${transaction.to?.hex}';
+ final feeRows = _buildFeeExtraModels(transaction, nativeCurrency, nativeSymbol);
+
if (await MethodsUtils.requestApproval(
txMessageText,
title: title,
method: method,
chainId: chainId,
- address: address,
+ address: address ?? transaction.from?.hex ?? '',
transportType: transportType,
verifyContext: verifyContext,
- extraModels: [
- WCConnectionModel(
- title: S.current.gas_price,
- elements: ['${gweiGasPrice.toStringAsFixed(2)} GWEI'],
- ),
- ],
+ extraModels: feeRows,
)) {
return transaction;
}
@@ -471,6 +466,66 @@ class EvmChainServiceImpl {
return JsonRpcError(code: 5002, message: S.current.user_rejected_method);
}
+ List _buildFeeExtraModels(
+ Transaction transaction,
+ CryptoCurrency? nativeCurrency,
+ String nativeSymbol,
+ ) {
+ final gasLimit = transaction.maxGas;
+ if (gasLimit == null || gasLimit <= 0) return const [];
+
+ final gasLimitBig = BigInt.from(gasLimit);
+ final isEip1559 = transaction.isEIP1559;
+ final perGasWei =
+ isEip1559 ? transaction.maxFeePerGas?.getInWei : transaction.gasPrice?.getInWei;
+ if (perGasWei == null || perGasWei <= BigInt.zero) return const [];
+
+ final feeWei = gasLimitBig * perGasWei;
+ final feeNative = feeWei.toDouble() / 1e18;
+
+ final label = isEip1559 ? S.current.wc_max_network_fee : S.current.wc_network_fee;
+
+ return [
+ WCConnectionModel(
+ title: label,
+ elements: [_formatFeeLine(feeNative, nativeSymbol, nativeCurrency)],
+ ),
+ ];
+ }
+
+ String _formatFeeLine(
+ double feeNative,
+ String nativeSymbol,
+ CryptoCurrency? nativeCurrency,
+ ) {
+ final cryptoPart = '${_formatNativeAmount(feeNative)} $nativeSymbol';
+
+ if (nativeCurrency == null) return cryptoPart;
+
+ try {
+ final fiatStore = getIt.get();
+ final price = fiatStore.prices[nativeCurrency];
+ if (price == null || price <= 0) return cryptoPart;
+
+ final fiatSymbol = appStore.settingsStore.fiatCurrency.title;
+ final fiatValue = calculateFiatAmount(
+ price: price,
+ cryptoAmount: feeNative.toString(),
+ );
+ if (fiatValue.isEmpty || fiatValue == '0.00') return cryptoPart;
+
+ return '$cryptoPart (~ $fiatValue $fiatSymbol)';
+ } catch (_) {
+ return cryptoPart;
+ }
+ }
+
+ String _formatNativeAmount(double value) {
+ if (value == 0) return '0';
+ if (value >= 0.0001) return value.toStringAsFixed(6);
+ return value.toStringAsExponential(4);
+ }
+
Future _ensureWCTransactionHasGasLimit(Transaction transaction) async {
final hasGasLimit = transaction.maxGas != null && transaction.maxGas! > 0;
if (hasGasLimit) return transaction;
@@ -637,7 +692,6 @@ $messageDetails''';
if (value == null) continue;
if (types.containsKey(fieldType)) {
- // Handle nested types
final nestedFields = types[fieldType] as List;
if (fieldType == 'Person') {
// Special formatting for Person type
diff --git a/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart b/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart
index f7a57b51d..b5d12f357 100644
--- a/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart
+++ b/lib/src/screens/wallet_connect/services/chain_service/solana/solana_chain_service.dart
@@ -80,7 +80,6 @@ class SolanaChainService {
error: JsonRpcError(code: error.code, message: error.message),
);
}
- //
} catch (e) {
debugPrint('solanaSignMessage error $e');
final error = Errors.getSdkError(Errors.MALFORMED_REQUEST_PARAMS);
diff --git a/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart
index 498585f0e..38772da10 100644
--- a/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart
+++ b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart
@@ -6,12 +6,7 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
abstract class WalletConnectKeyService {
- /// Returns a list of all the keys.
List getKeys(WalletBase wallet);
-
- /// Returns a list of all the keys for a given chain id.
- /// If the chain is not found, returns an empty list.
- /// - [chain]: The chain to get the keys for.
List getKeysForChain(WalletBase wallet);
}
diff --git a/lib/src/screens/wallet_connect/services/walletkit_service.dart b/lib/src/screens/wallet_connect/services/walletkit_service.dart
index 5e215ea7e..7d5a0ca67 100644
--- a/lib/src/screens/wallet_connect/services/walletkit_service.dart
+++ b/lib/src/screens/wallet_connect/services/walletkit_service.dart
@@ -20,10 +20,9 @@ import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/chai
import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart';
import 'package:cake_wallet/src/screens/wallet_connect/utils/eth_utils.dart';
import 'package:cake_wallet/src/screens/wallet_connect/utils/method_utils.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_request_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_request_widget.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_session_auth_request_widget.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_request_sheet.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'bottom_sheet_service.dart';
@@ -56,8 +55,6 @@ abstract class WalletKitServiceBase with Store {
bool isInitialized;
/// The list of requests from the dapp
- /// Potential types include, but aren't limited to:
- /// [SessionProposalEvent], [SessionAuthRequest]
@observable
ObservableList pairings;
@@ -69,7 +66,6 @@ abstract class WalletKitServiceBase with Store {
@action
void create() {
- // Create the walletkit client
_walletKit = ReownWalletKit(
core: ReownCore(
projectId: secrets.walletConnectProjectId,
@@ -85,7 +81,6 @@ abstract class WalletKitServiceBase with Store {
_walletKit.core.addLogListener(_logListener);
- // Setup our listeners
log('Created instance of walletKit');
_walletKit.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid);
@@ -288,16 +283,12 @@ abstract class WalletKitServiceBase with Store {
if (args != null) {
final proposer = args.params.proposer;
final result = (await _bottomSheetHandler.queueBottomSheet(
- widget: WCRequestWidget(
+ widget: WCConnectionRequestSheet(
+ proposalData: args.params,
+ requester: proposer,
verifyContext: args.verifyContext,
- child: WCConnectionRequestWidget(
- proposalData: args.params,
- verifyContext: args.verifyContext,
- requester: proposer,
- walletKeyService: walletKeyService,
- walletKit: walletKit,
- appStore: appStore,
- ),
+ walletKeyService: walletKeyService,
+ appStore: appStore,
),
)) ??
WCBottomSheetResult.reject;
@@ -400,10 +391,8 @@ abstract class WalletKitServiceBase with Store {
debugPrint('_onPairingCreate $args');
if (args != null && args.topic != null && args.topic!.isNotEmpty) {
- // Save the pairing topic when pairing is created
savePairingTopicToLocalStorage(args.topic!);
- // Refresh pairings to show the new pairing in the list
_refreshPairings();
}
}
@@ -440,16 +429,28 @@ abstract class WalletKitServiceBase with Store {
formattedMessages.add({iss: message});
}
+ final requesterMetadata = args.requester.metadata;
+ final requesterIcon = requesterMetadata.icons.isNotEmpty
+ ? requesterMetadata.icons.first
+ : null;
+ final chainKeysForAuth = walletKeyService.getKeysForChain(appStore.wallet!);
+ final addressForAuth =
+ chainKeysForAuth.isNotEmpty ? chainKeysForAuth.first.publicKey : '';
+ final combinedMessageBody =
+ formattedMessages.map((m) => m.values.first as String).join('\n\n');
+
final WCBottomSheetResult result = (await _bottomSheetHandler.queueBottomSheet(
- widget: WCSessionAuthRequestWidget(
- child: WCConnectionRequestWidget(
- sessionAuthPayload: newAuthPayload,
- verifyContext: args.verifyContext,
- requester: args.requester,
- walletKeyService: walletKeyService,
- walletKit: _walletKit,
- appStore: appStore,
- ),
+ widget: WCSigningRequestSheet(
+ title: S.current.wc_signing_request_title,
+ swipeLabel: S.current.wc_swipe_to_sign,
+ dappName: requesterMetadata.name,
+ dappIconUrl: requesterIcon,
+ dappSubtitle: requesterMetadata.url,
+ message: combinedMessageBody,
+ walletName: appStore.wallet?.name ?? '',
+ address: addressForAuth,
+ verifyContext: args.verifyContext,
+ signAllCount: formattedMessages.length,
),
) as WCBottomSheetResult?) ??
WCBottomSheetResult.reject;
@@ -458,10 +459,8 @@ abstract class WalletKitServiceBase with Store {
final chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
final privateKey = '0x${chainKeys.first.privateKey}';
final credentials = EthPrivateKey.fromHex(privateKey);
- //
final messageToSign = formattedMessages.length;
final count = (result == WCBottomSheetResult.one) ? 1 : messageToSign;
- //
final List cacaos = [];
for (var i = 0; i < count; i++) {
final iss = formattedMessages[i].keys.first;
@@ -572,19 +571,6 @@ abstract class WalletKitServiceBase with Store {
_refreshPairings();
}
- @action
- Future updateSession({
- required String topic,
- required Map namespaces,
- }) async {
- await walletKit.updateSession(topic: topic, namespaces: namespaces);
- }
-
- @action
- Future extendSession({required String topic}) async {
- await walletKit.extendSession(topic: topic);
- }
-
@action
Future pairWithUri(Uri uri) async {
try {
@@ -633,7 +619,7 @@ abstract class WalletKitServiceBase with Store {
@action
List getSessionsForPairingInfo(PairingInfo pairing) {
- return sessions.where((element) => element.pairingTopic == pairing.topic).toList();
+ return sessions.where((element) => element.pairingTopic == pairing.topic).toList();
}
String getKeyForStoringTopicsForWallet() {
@@ -664,18 +650,14 @@ abstract class WalletKitServiceBase with Store {
}
List getPairingTopicsForWallet(String key) {
- // Get the JSON-encoded string from shared preferences
final jsonString = sharedPreferences.getString(key);
- // If the string is null, return an empty list
if (jsonString == null) {
return [];
}
- // Decode the JSON string to a list of strings
final List jsonList = jsonDecode(jsonString) as List;
- // Cast each item to a string
return jsonList.map((item) => item as String).toList();
}
@@ -691,12 +673,10 @@ abstract class WalletKitServiceBase with Store {
}
Future savePairingTopicToLocalStorage(String pairingTopic) async {
- // Get key specific to the current wallet
final key = getKeyForStoringTopicsForWallet();
if (key.isEmpty) return;
- // Get all pairing topics attached to this key
final pairingTopicsForWallet = getPairingTopicsForWallet(key);
bool isPairingTopicAlreadySaved = pairingTopicsForWallet.contains(pairingTopic);
@@ -704,13 +684,10 @@ abstract class WalletKitServiceBase with Store {
'Is Pairing Topic Saved: $isPairingTopicAlreadySaved, Key: $key, Topic: $pairingTopic');
if (!isPairingTopicAlreadySaved) {
- // Update the list with the most recent pairing topic
pairingTopicsForWallet.add(pairingTopic);
- // Convert the list of updated pairing topics to a JSON-encoded string
final jsonString = jsonEncode(pairingTopicsForWallet);
- // Save the encoded string to shared preferences
await sharedPreferences.setString(key, jsonString);
}
}
diff --git a/lib/src/screens/wallet_connect/utils/method_utils.dart b/lib/src/screens/wallet_connect/utils/method_utils.dart
index d79adfede..b22c57259 100644
--- a/lib/src/screens/wallet_connect/utils/method_utils.dart
+++ b/lib/src/screens/wallet_connect/utils/method_utils.dart
@@ -3,8 +3,9 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_widget.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_request_widget.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_message_card.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_signing_request_sheet.dart';
+import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/themes/core/custom_theme_colors.dart';
import 'package:flutter/material.dart';
import 'package:reown_walletkit/reown_walletkit.dart';
@@ -12,6 +13,14 @@ import 'package:reown_walletkit/reown_walletkit.dart';
class MethodsUtils {
static final walletKit = getIt.get().walletKit;
static final bottomSheetService = getIt.get();
+
+ static const _transactionMethods = {
+ 'eth_sendTransaction',
+ 'eth_signTransaction',
+ 'solana_signTransaction',
+ 'solana_signAllTransactions',
+ 'solana_signAndSendTransaction',
+ };
static Future requestApproval(
String text, {
@@ -23,23 +32,53 @@ class MethodsUtils {
List extraModels = const [],
VerifyContext? verifyContext,
}) async {
+ final appStore = getIt.get();
+ final pending = walletKit.pendingRequests.getAll();
+ final session = pending.isNotEmpty
+ ? walletKit.sessions.get(pending.last.topic)
+ : null;
+ final dAppMetadata = session?.peer.metadata;
+
+ final isTransaction = method != null && _transactionMethods.contains(method);
+ final resolvedTitle = title ??
+ (isTransaction
+ ? S.current.wc_approve_request_title
+ : S.current.wc_signing_request_title);
+ final swipeLabel =
+ isTransaction ? S.current.wc_swipe_to_approve : S.current.wc_swipe_to_sign;
+
+ final extraRows = [];
+ if (method != null && method.isNotEmpty) {
+ extraRows.add(WCMessageRow(label: S.current.method, value: method));
+ }
+ if (chainId != null && chainId.isNotEmpty) {
+ extraRows.add(WCMessageRow(label: S.current.chain_id, value: chainId));
+ }
+ if (transportType.isNotEmpty) {
+ extraRows.add(WCMessageRow(
+ label: S.current.transport_type,
+ value: transportType.toUpperCase(),
+ ));
+ }
+ for (final model in extraModels) {
+ if (model.title == null) continue;
+ final value = model.elements?.join(', ') ?? model.text ?? '';
+ extraRows.add(WCMessageRow(label: model.title!, value: value));
+ }
+
final WCBottomSheetResult result = (await bottomSheetService.queueBottomSheet(
- widget: WCRequestWidget(
+ widget: WCSigningRequestSheet(
+ title: resolvedTitle,
+ swipeLabel: swipeLabel,
+ dappName: dAppMetadata?.name ?? '',
+ dappIconUrl:
+ (dAppMetadata?.icons.isNotEmpty ?? false) ? dAppMetadata!.icons.first : null,
+ dappSubtitle: method ?? dAppMetadata?.url ?? '',
+ message: text,
+ walletName: appStore.wallet?.name ?? '',
+ address: address ?? '',
verifyContext: verifyContext,
- child: WCConnectionWidget(
- title: title ?? S.current.approve_request,
- info: [
- WCConnectionModel(
- title: '${S.current.method}: $method\n'
- '${S.current.transport_type}: ${transportType.toUpperCase()}\n'
- '${S.current.chain_id}: $chainId\n'
- '${S.current.address}: $address\n\n'
- '${S.current.message}:',
- elements: [text],
- ),
- ...extraModels,
- ],
- ),
+ extraRows: extraRows,
),
) as WCBottomSheetResult?) ??
WCBottomSheetResult.reject;
diff --git a/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart b/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart
deleted file mode 100644
index 26cb3bbcf..000000000
--- a/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart
+++ /dev/null
@@ -1,76 +0,0 @@
-import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/models/wc_connection_model.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_connection_widget.dart';
-import 'package:flutter/material.dart';
-import 'package:reown_walletkit/reown_walletkit.dart';
-
-class ConnectionWidgetBuilder {
- static List buildFromRequiredNamespaces(
- Map generatedNamespaces,
- ) {
- final List views = [];
- for (final key in generatedNamespaces.keys) {
- final namespaces = generatedNamespaces[key]!;
- final chains = NamespaceUtils.getChainsFromAccounts(namespaces.accounts);
-
- final List models = [];
-
- // If the chains property is present, add the chain data to the models
- models.add(WCConnectionModel(title: S.current.chains, elements: chains));
- models.add(WCConnectionModel(title: S.current.methods, elements: namespaces.methods));
-
- if (namespaces.events.isNotEmpty) {
- models.add(WCConnectionModel(title: S.current.events, elements: namespaces.events));
- }
-
- views.add(WCConnectionWidget(title: key, info: models));
- }
-
- return views;
- }
-
- static List buildFromNamespaces(
- String topic,
- Map namespaces,
- BuildContext context,
- ) {
- final List views = [];
- for (final key in namespaces.keys) {
- final ns = namespaces[key]!;
- final List models = [];
-
- // If the chains property is present, add the chain data to the models
- models.add(WCConnectionModel(title: S.current.accounts, elements: ns.accounts));
- models.add(WCConnectionModel(title: S.current.methods, elements: ns.methods));
-
- if (ns.events.isNotEmpty) {
- models.add(WCConnectionModel(title: S.current.events, elements: ns.events));
- }
-
- views.add(WCConnectionWidget(title: key, info: models));
- }
-
- return views;
- }
-
- static Map updateNamespaces(
- Map currentNamespaces,
- String namespace,
- List newChains,
- ) {
- final updatedNamespaces = Map.from(currentNamespaces);
-
- final accounts = currentNamespaces[namespace]!.accounts;
- final address = NamespaceUtils.getAccount(accounts.first);
- final newAccounts = newChains.map((c) => '$c:$address').toList();
-
- final newNamespaces = currentNamespaces[namespace]!.copyWith(
- chains: NamespaceUtils.getChainsFromAccounts(accounts)..addAll(newChains),
- accounts: List.from(accounts)..addAll(newAccounts),
- );
-
- updatedNamespaces[namespace] = newNamespaces;
-
- return updatedNamespaces;
- }
-}
diff --git a/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart b/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart
new file mode 100644
index 000000000..6a84f826e
--- /dev/null
+++ b/lib/src/screens/wallet_connect/utils/wc_permissions_mapper.dart
@@ -0,0 +1,80 @@
+import 'package:cake_wallet/generated/i18n.dart';
+import 'package:reown_walletkit/reown_walletkit.dart';
+
+class WCPermission {
+ const WCPermission({required this.iconUrl, required this.label});
+
+ final String iconUrl;
+ final String label;
+}
+
+class WCPermissionsMapper {
+ static const _transactionMethods = {
+ 'eth_sendTransaction',
+ 'eth_signTransaction',
+ 'solana_signTransaction',
+ 'solana_signAllTransactions',
+ 'solana_signAndSendTransaction',
+ };
+
+ static const _messageSigningMethods = {
+ 'personal_sign',
+ 'eth_sign',
+ 'eth_signTypedData',
+ 'eth_signTypedData_v1',
+ 'eth_signTypedData_v3',
+ 'eth_signTypedData_v4',
+ 'solana_signMessage',
+ };
+
+ static const _chainAdminMethods = {
+ 'wallet_switchEthereumChain',
+ 'wallet_addEthereumChain',
+ };
+
+ static List fromGeneratedNamespaces(Map generatedNamespaces) {
+ final methods = {};
+ for (final ns in generatedNamespaces.values) {
+ methods.addAll(ns.methods);
+ }
+
+ final permissions = [
+ WCPermission(iconUrl: "assets/new-ui/global_view.svg", label: S.current.wc_permission_view_balance),
+ ];
+
+ final wantsTransactionApproval = methods.any(_transactionMethods.contains);
+ if (wantsTransactionApproval) {
+ permissions.add(WCPermission(
+ iconUrl: "assets/new-ui/green_check.svg",
+ label: S.current.wc_permission_request_approval,
+ ));
+ }
+
+ final wantsMessageSigning = methods.any(_messageSigningMethods.contains);
+ if (wantsMessageSigning) {
+ permissions.add(WCPermission(
+ iconUrl: "assets/new-ui/pencil.svg",
+ label: S.current.wc_permission_sign_messages,
+ ));
+ }
+
+ final wantsChainAdmin = methods.any(_chainAdminMethods.contains);
+ if (wantsChainAdmin) {
+ permissions.add(WCPermission(
+ iconUrl: "assets/new-ui/exchange_providers.svg",
+ label: S.current.wc_permission_switch_chains,
+ ));
+ }
+
+ final known = {..._transactionMethods, ..._messageSigningMethods, ..._chainAdminMethods};
+ final unknown = methods.where((m) => !known.contains(m)).toList()..sort();
+ for (final method in unknown) {
+ permissions.add(WCPermission(
+ iconUrl: "assets/new-ui/help.svg",
+ label: S.current.wc_permission_other(method),
+ ));
+ }
+
+ return permissions;
+ }
+}
diff --git a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart
index 5bc60cfd6..a6caff932 100644
--- a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart
+++ b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart
@@ -1,122 +1,86 @@
+import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
+import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/src/screens/base_page.dart';
+import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
+import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/enter_wallet_connect_uri_widget.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_hero_card.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
+import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/utils/device_info.dart';
+import 'package:cake_wallet/utils/permission_handler.dart';
+import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:reown_walletkit/reown_walletkit.dart';
-import 'package:cake_wallet/entities/qr_scanner.dart';
-import 'package:cake_wallet/src/widgets/primary_button.dart';
-import 'package:cake_wallet/utils/show_pop_up.dart';
-import 'package:cake_wallet/utils/permission_handler.dart';
-import 'package:url_launcher/url_launcher.dart';
-import 'widgets/wc_pairing_item_widget.dart';
import 'wc_pairing_detail_page.dart';
class WalletConnectConnectionsView extends StatelessWidget {
- final WalletKitService walletKitService;
-
- WalletConnectConnectionsView({required this.walletKitService, Uri? launchUri, Key? key})
- : super(key: key) {
+ WalletConnectConnectionsView({
+ required this.walletKitService,
+ Uri? launchUri,
+ Key? key,
+ }) : super(key: key) {
_triggerPairingFromDeeplink(launchUri);
}
+ final WalletKitService walletKitService;
+
void _triggerPairingFromDeeplink(Uri? launchUri) async {
if (launchUri == null) return;
- if(launchUri.scheme == "wc") {
+ if (launchUri.scheme == 'wc') {
await walletKitService.pairWithUri(launchUri);
return;
}
- final actualLinkList = launchUri.query.split("uri=");
-
+ final actualLinkList = launchUri.query.split('uri=');
if (actualLinkList.length <= 1) return;
- final query = actualLinkList[1];
-
- final decoded = Uri.decodeComponent(query).trim();
-
+ final decoded = Uri.decodeComponent(actualLinkList[1]).trim();
final sanitized = decoded.startsWith('@') ? decoded.substring(1) : decoded;
-
final uriData = Uri.tryParse(sanitized);
-
- if (uriData == null || (uriData.scheme.isEmpty)) return;
+ if (uriData == null || uriData.scheme.isEmpty) return;
await walletKitService.pairWithUri(uriData);
}
- @override
- Widget build(BuildContext context) {
- return WCPairingsWidget(walletKitService: walletKitService);
- }
-}
-
-class WCPairingsWidget extends BasePage {
- WCPairingsWidget({required this.walletKitService, Key? key})
- : walletKit = walletKitService.walletKit;
-
- final ReownWalletKit walletKit;
- final WalletKitService walletKitService;
-
- @override
- String get title => S.current.walletConnect;
-
- Future _onScanQrCode(BuildContext context, ReownWalletKit web3Wallet) async {
- final String? uri;
-
- if (DeviceInfo.instance.isMobile) {
- bool isCameraPermissionGranted =
- await PermissionHandler.checkPermission(Permission.camera, context);
- if (!isCameraPermissionGranted) return;
- uri = await presentQRScanner(context);
- } else {
- uri = await _showEnterWalletConnectURIPopUp(context);
- }
+ Future _onScanQrCode(BuildContext context) async {
+ final isCameraPermissionGranted =
+ await PermissionHandler.checkPermission(Permission.camera, context);
+ if (!isCameraPermissionGranted) return;
+ final uri = await presentQRScanner(context);
await _handleWalletConnectURI(uri, context);
}
- Future _showEnterWalletConnectURIPopUp(BuildContext context) async {
- final walletConnectURI = await showPopUp(
+ Future _onPasteLink(BuildContext context) async {
+ final uri = await showPopUp(
context: context,
- builder: (BuildContext context) {
- return EnterWalletConnectURIWrapperWidget();
- },
+ builder: (BuildContext context) => EnterWalletConnectURIWrapperWidget(),
);
- return walletConnectURI;
+ await _handleWalletConnectURI(uri, context);
}
- Future _handleWalletConnectURI(
- String? walletConnectURI,
- BuildContext context,
- ) async {
+ Future _handleWalletConnectURI(String? walletConnectURI, BuildContext context) async {
if (walletConnectURI == null) return _invalidUriToast(context, S.current.nullURIError);
- log('_onFoundUri: $walletConnectURI');
- // Accept either a raw WC URI or a full URL containing `uri=` parameter
String input = walletConnectURI.trim();
-
if (input.contains('uri=')) {
final parts = input.split('uri=');
- if (parts.length > 1) {
- input = Uri.decodeComponent(parts.last);
- }
+ if (parts.length > 1) input = Uri.decodeComponent(parts.last);
}
+ if (input.startsWith('@')) input = input.substring(1);
- // Some scanners may prefix with '@', strip it
- if (input.startsWith('@')) {
- input = input.substring(1);
- }
- final Uri? uriData = Uri.tryParse(input);
- final bool hasValidScheme = uriData != null && uriData.scheme.isNotEmpty;
- if (!hasValidScheme) {
+ final uriData = Uri.tryParse(input);
+ if (uriData == null || uriData.scheme.isEmpty) {
return _invalidUriToast(context, S.current.invalid_input);
}
+
await walletKitService.pairWithUri(uriData);
}
@@ -135,88 +99,132 @@ class WCPairingsWidget extends BasePage {
);
}
- @override
- Widget body(BuildContext context) {
- return Observer(
- builder: (context) {
- return Column(
- children: [
- Padding(
- padding: EdgeInsets.symmetric(horizontal: 24),
- child: Column(
- children: [
- SizedBox(height: 24),
- Text(
- S.current.connectWalletPrompt,
- style: Theme.of(context).textTheme.bodyMedium!.copyWith(
- fontSize: 16.0,
- fontWeight: FontWeight.normal,
- color: Theme.of(context).colorScheme.onSurface,
- ),
- ),
- SizedBox(height: 16),
- PrimaryButton(
- text: S.current.newConnection,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
- onPressed: () => _onScanQrCode(context, walletKit),
- ),
- SizedBox(height: 4),
- TextButton(
- onPressed: () async {
- final uri = await _showEnterWalletConnectURIPopUp(context);
- await _handleWalletConnectURI(uri, context);
- },
- child: Text(
- 'Click to paste WalletConnect Link',
- style: Theme.of(context).textTheme.bodyMedium,
- ),
- ),
- ],
- ),
- ),
- SizedBox(height: 16),
- Expanded(
- child: Visibility(
- visible: walletKitService.pairings.isEmpty,
- child: Center(
- child: Text(
- S.current.activeConnectionsPrompt,
- textAlign: TextAlign.center,
- style: Theme.of(context).textTheme.bodyMedium!.copyWith(
- fontSize: 16.0,
- fontWeight: FontWeight.normal,
- color: Theme.of(context).colorScheme.onSurface,
- ),
- ),
- ),
- replacement: ListView.builder(
- itemCount: walletKitService.pairings.length,
- itemBuilder: (BuildContext context, int index) {
- final pairing = walletKitService.pairings[index];
- return WCPairingItemWidget(
- key: ValueKey(pairing.topic),
- pairing: pairing,
- onTap: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => WalletConnectPairingDetailsPage(
- pairing: pairing,
- walletKitService: walletKitService,
- ),
- ),
- );
- },
- );
- },
- ),
- ),
- ),
- SizedBox(height: 48),
- ],
- );
- },
+ void _openPairingDetails(BuildContext context, PairingInfo pairing) {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) => WalletConnectPairingDetailsPage(
+ pairing: pairing,
+ walletKitService: walletKitService,
+ ),
+ ),
);
}
-}
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ final colors = theme.colorScheme;
+ final isMobile = DeviceInfo.instance.isMobile;
+
+ return Scaffold(
+ backgroundColor: colors.surface,
+ body: SafeArea(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 24),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ const SizedBox(height: 8),
+ Align(
+ alignment: Alignment.centerLeft,
+ child: ModernButton(
+ size: 40,
+ onPressed: () => Navigator.of(context).maybePop(),
+ icon: Icon(Icons.arrow_back_ios_new, size: 16),
+ iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
+ ),
+ const SizedBox(height: 16),
+ Expanded(
+ child: SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ const WCHeroCard(),
+ const SizedBox(height: 24),
+ Observer(
+ builder: (_) {
+ if (walletKitService.pairings.isEmpty) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 32),
+ child: Center(
+ child: Text(
+ S.of(context).activeConnectionsPrompt,
+ textAlign: TextAlign.center,
+ style: theme.textTheme.bodyMedium?.copyWith(
+ color: colors.onSurfaceVariant,
+ ),
+ ),
+ ),
+ );
+ }
+
+ final items = [];
+ for (final pairing in walletKitService.pairings) {
+ final metadata = pairing.peerMetadata;
+ if (metadata == null) continue;
+ items.add(
+ ListItemRegularRow(
+ keyValue: pairing.topic,
+ label: metadata.name,
+ subtitle: metadata.url,
+ iconPath: metadata.icons.isNotEmpty
+ ? metadata.icons.first
+ : 'assets/new-ui/walletconnect_icon.svg',
+ onTap: () => _openPairingDetails(context, pairing),
+ leadingIconSize: 36,
+ leadingIconErrorWidget: CakeImageWidget(
+ imageUrl: 'assets/new-ui/walletconnect_icon.svg',
+ width: 36,
+ height: 36,
+ ),
+ ),
+ );
+ }
+
+ if (items.isEmpty) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 32),
+ child: Center(
+ child: Text(
+ S.of(context).activeConnectionsPrompt,
+ textAlign: TextAlign.center,
+ style: theme.textTheme.bodyMedium?.copyWith(
+ color: colors.onSurfaceVariant,
+ ),
+ ),
+ ),
+ );
+ }
+
+ return NewListSections(sections: {'': items});
+ },
+ ),
+ ],
+ ),
+ ),
+ ),
+ const SizedBox(height: 16),
+ NewPrimaryButton(
+ onPressed: () => _onPasteLink(context),
+ text: S.of(context).wc_paste_link,
+ color: colors.surfaceContainerHigh,
+ textColor: colors.primary,
+ ),
+ if (isMobile) ...[
+ const SizedBox(height: 12),
+ NewPrimaryButton(
+ onPressed: () => _onScanQrCode(context),
+ text: S.of(context).wc_scan_qr,
+ color: colors.primary,
+ textColor: colors.onPrimary,
+ ),
+ ],
+ const SizedBox(height: 16),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart
index 44f0681bd..0a01560e2 100644
--- a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart
+++ b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart
@@ -1,15 +1,18 @@
+import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/utils/wc_permissions_mapper.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_dapp_card.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_permissions_card.dart';
+import 'package:cake_wallet/src/screens/wallet_connect/widgets/wc_wallet_card.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
-import 'package:cake_wallet/src/widgets/primary_button.dart';
+import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
-import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:flutter/material.dart';
import 'package:reown_walletkit/reown_walletkit.dart';
-import 'utils/namespace_model_builder.dart';
-
class WalletConnectPairingDetailsPage extends StatefulWidget {
final PairingInfo pairing;
final WalletKitService walletKitService;
@@ -25,180 +28,100 @@ class WalletConnectPairingDetailsPage extends StatefulWidget {
}
class WalletConnectPairingDetailsPageState extends State {
- List sessionWidgets = [];
late String expiryDate;
+ List sessions = const [];
+
@override
void initState() {
super.initState();
initDateTime();
WidgetsBinding.instance.addPostFrameCallback((_) {
- initSessions();
+ if (!mounted) return;
+ setState(() {
+ sessions = widget.walletKitService.getSessionsForPairingInfo(widget.pairing);
+ });
});
}
void initDateTime() {
- DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(widget.pairing.expiry * 1000);
- int year = dateTime.year;
- int month = dateTime.month;
- int day = dateTime.day;
-
- expiryDate = '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
- }
-
- void initSessions() {
- List sessions = widget.walletKitService.getSessionsForPairingInfo(widget.pairing);
-
- for (final SessionData session in sessions) {
- List namespaceWidget = ConnectionWidgetBuilder.buildFromNamespaces(
- session.topic,
- session.namespaces,
- context,
- );
- // Loop through and add the namespace widgets, but put 20 pixels between each one
- for (int i = 0; i < namespaceWidget.length; i++) {
- sessionWidgets.add(namespaceWidget[i]);
- if (i != namespaceWidget.length - 1) {
- sessionWidgets.add(const SizedBox(height: 20.0));
- }
- }
-
- sessionWidgets.add(const SizedBox.square(dimension: 10.0));
- sessionWidgets.add(
- PrimaryButton(
- onPressed: () async {
- try {
- await widget.walletKitService.extendSession(
- topic: session.topic,
- );
- } catch (e) {
- debugPrint('${e.toString()}');
- }
- },
- text: S.current.extend_session,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
- ),
- );
- sessionWidgets.add(const SizedBox.square(dimension: 10.0));
- sessionWidgets.add(
- PrimaryButton(
- onPressed: () async {
- try {
- await widget.walletKitService.updateSession(
- topic: session.topic,
- namespaces: session.namespaces,
- );
- } catch (e) {
- debugPrint('${e.toString()}');
- }
- },
- text: S.current.update_session,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
- ),
- );
- sessionWidgets.add(const SizedBox.square(dimension: 10.0));
- sessionWidgets.add(
- PrimaryButton(
- onPressed: () async {
- try {
- await widget.walletKitService.disconnectSession(
- topic: session.topic,
- );
- } catch (e) {
- debugPrint('${e.toString()}');
- }
- },
- text: S.current.disconnect_session,
- color: Theme.of(context).colorScheme.primary,
- textColor: Theme.of(context).colorScheme.onPrimary,
- ),
- );
- }
+ final dateTime = DateTime.fromMillisecondsSinceEpoch(widget.pairing.expiry * 1000);
+ expiryDate = '${dateTime.year}-'
+ '${dateTime.month.toString().padLeft(2, '0')}-'
+ '${dateTime.day.toString().padLeft(2, '0')}';
}
@override
Widget build(BuildContext context) {
return WCCDetailsWidget(
- widget.pairing,
- expiryDate,
- sessionWidgets,
- widget.walletKitService,
+ pairing: widget.pairing,
+ expiryDate: expiryDate,
+ sessions: sessions,
+ walletKitService: widget.walletKitService,
);
}
}
class WCCDetailsWidget extends BasePage {
- WCCDetailsWidget(
- this.pairing,
- this.expiryDate,
- this.sessionWidgets,
- this.walletKitService,
- );
+ WCCDetailsWidget({
+ required this.pairing,
+ required this.expiryDate,
+ required this.sessions,
+ required this.walletKitService,
+ });
final PairingInfo pairing;
final String expiryDate;
- final List sessionWidgets;
+ final List sessions;
final WalletKitService walletKitService;
@override
Widget body(BuildContext context) {
- return Scaffold(
- body: SingleChildScrollView(
- child: Container(
- padding: const EdgeInsets.all(8),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Flexible(
- child: CircleAvatar(
- backgroundImage: (pairing.peerMetadata!.icons.isNotEmpty
- ? NetworkImage(pairing.peerMetadata!.icons[0])
- : AssetImage(
- CakeTor.instance!.enabled
- ? 'assets/images/tor_logo.svg'
- : 'assets/images/app_logo.png')) as ImageProvider