Merge pull request #621 from cypherstack/staging

Update to v1.7.16
This commit is contained in:
Diego Salazar 2023-07-21 14:47:28 -06:00 committed by GitHub
commit 210b59bb65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
183 changed files with 4192 additions and 2054 deletions

View file

@ -4,7 +4,7 @@
# This file should be version controlled.
version:
revision: f1875d570e39de09040c8f79aa13cc56baab8db1
revision: f92f44110e87bad5ff168335c36da6f6053036e6
channel: stable
project_type: app
@ -13,17 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
- platform: linux
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
create_revision: f92f44110e87bad5ff168335c36da6f6053036e6
base_revision: f92f44110e87bad5ff168335c36da6f6053036e6
- platform: macos
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
- platform: windows
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
create_revision: f92f44110e87bad5ff168335c36da6f6053036e6
base_revision: f92f44110e87bad5ff168335c36da6f6053036e6
# User provided section

BIN
assets/icon/macos-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -1 +1 @@
Subproject commit 3f94722254d1c9ad54036e39a620ccc0bb53863b
Subproject commit f677dec0b34d3f9fe8fce2bc8ff5c508c3f3bb9a

@ -1 +1 @@
Subproject commit ec3cf5e8e1b90e006188aa8c323d4cd52dbfa9b9
Subproject commit 9cd241b5ea142e21c01dd7639b42603281c43287

@ -1 +1 @@
Subproject commit 26a152fea3ca4b8c3f1130392a02f579c2ff218c
Subproject commit 407425c9fcf7a30c81f1345246c7225bc18b5cd5

View file

@ -29,13 +29,6 @@ Then in `File > Settings > Plugins`, install the **Flutter** and **Dart** plugin
Make a Pixel 4 (API 30) x86_64 emulator with 2GB of storage space for emulation
### Scripted setup
[`scripts/setup.sh`](./../scripts/setup.sh) is provided as a tool to set up installation for building: download the script and run it anywhere. This script should skip the entire [Manual setup](#manual-setup) section below and prepare you for [running](#Running).
### Manual setup
> If you used the `setup.sh` script, skip to [running](#Running)
Install basic dependencies
```
sudo apt-get install libssl-dev curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm python3-distutils
@ -98,20 +91,21 @@ cd ..
```
or manually by creating the files referenced in that script with the specified content.
### Building plugins for Android
### Build plugins
#### Building plugins for Android
> Warning: This will take a long time, please be patient
```
cd scripts/android
./build_all.sh
```
### Building plugins for Linux
#### Building plugins for Linux
```
cd scripts/linux
./build_all.sh
```
### Building plugins for Windows
#### Building plugins for Windows
```
cd scripts/windows
./deps.sh
@ -140,10 +134,28 @@ flutter run linux
Visual Studio is required for Windows development with the Flutter SDK. Download it at https://visualstudio.microsoft.com/downloads/ and install the "Desktop development with C++" workload, including all of its default components.
### Building libraries in WSL2
Set up Ubuntu 20.04 in WSL2. Follow the entire Linux host section to get set up and build windows `dll` libraries. Copy the resulting `dll`s to their respective positions on the Windows host:
Set up Ubuntu 20.04 in WSL2. Follow the entire Linux host section in the WSL2 Ubuntu 20.04 host to get set up to build. You will also need to install Rust and MXE dependencies on the WSL2 Ubuntu 20.04 host:
- [Install Rust](https://rustup.rs/)
```sh
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
- Install MXE by running `stack_wallet/scripts/windows/deps.sh`
```sh
./stack_wallet/scripts/windows/deps.sh
```
The WSL2 host may optionally be navigated to the `stack_wallet` repository on the Windows host in order to build the plugins in-place and skip the next section in which you copy the `dll`s from WSL2 to Windows. Then build windows `dll` libraries by running the following script on the WSL2 Ubuntu 20.04 host:
- `stack_wallet/scripts/windows/build_all.sh`
Copy the resulting `dll`s to their respective positions on the Windows host:
- `stack_wallet/crypto_plugins/flutter_libepiccash/scripts/windows/build/libepic_cash_wallet.dll`
- `stack_wallet/crypto_plugins/flutter_liblelantus/scripts/windows/build/libmobileliblelantus.dll`
<!--
- `stack_wallet/crypto_plugins/flutter_libmonero/scripts/windows/build/libcw_monero.dll`
- `stack_wallet/crypto_plugins/flutter_libmonero/scripts/windows/build/libcw_wownero.dll`
-->
<!-- TODO: script the copying or installation of libraries from WSL2 to the parent Windows host -->
### Install Flutter on Windows host

View file

@ -1,6 +1,4 @@
PODS:
- "app_settings (3.0.0+1)":
- Flutter
- barcode_scan2 (0.0.1):
- Flutter
- MTBBarcodeScanner
@ -112,7 +110,7 @@ PODS:
- Flutter
- flutter_native_splash (0.0.1):
- Flutter
- flutter_secure_storage (3.3.1):
- flutter_secure_storage (6.0.0):
- Flutter
- integration_test (0.0.1):
- Flutter
@ -149,7 +147,6 @@ PODS:
- Flutter
DEPENDENCIES:
- app_settings (from `.symlinks/plugins/app_settings/ios`)
- barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- cw_monero (from `.symlinks/plugins/cw_monero/ios`)
@ -169,10 +166,10 @@ DEPENDENCIES:
- lelantus (from `.symlinks/plugins/lelantus/ios`)
- local_auth (from `.symlinks/plugins/local_auth/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- stack_wallet_backup (from `.symlinks/plugins/stack_wallet_backup/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
@ -188,8 +185,6 @@ SPEC REPOS:
- SwiftyGif
EXTERNAL SOURCES:
app_settings:
:path: ".symlinks/plugins/app_settings/ios"
barcode_scan2:
:path: ".symlinks/plugins/barcode_scan2/ios"
connectivity_plus:
@ -229,13 +224,13 @@ EXTERNAL SOURCES:
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/ios"
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/ios"
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
stack_wallet_backup:
:path: ".symlinks/plugins/stack_wallet_backup/ios"
url_launcher_ios:
@ -244,13 +239,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock/ios"
SPEC CHECKSUMS:
app_settings: d103828c9f5d515c4df9ee754dabd443f7cedcf3
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
cw_monero: 9816991daff0e3ad0a8be140e31933b5526babd4
cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
cw_wownero: ac53899fa5c6ff46b3fb490aa3b7ca36301fa832
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
devicelocale: b22617f40038496deffba44747101255cee005b0
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
@ -260,23 +254,23 @@ SPEC CHECKSUMS:
flutter_libmonero: da68a616b73dd0374a8419c684fa6b6df2c44ffe
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
integration_test: 13825b8a9334a850581300559b8839134b124670
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
lelantus: 417f0221260013dfc052cae9cf4b741b6479edba
local_auth: 1740f55d7af0a2e2a8684ce225fe79d8931e808c
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
stack_wallet_backup: 5b8563aba5d8ffbf2ce1944331ff7294a0ec7c03
SwiftProtobuf: 6ef3f0e422ef90d6605ca20b21a94f6c1324d6b3
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
url_launcher_ios: fb12c43172927bb5cf75aeebd073f883801f1993
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
PODFILE CHECKSUM: 57c8aed26fba39d3ec9424816221f294a07c58eb

View file

@ -242,6 +242,7 @@
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
@ -301,7 +302,6 @@
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
"${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework",
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
"${BUILT_PRODUCTS_DIR}/app_settings/app_settings.framework",
"${BUILT_PRODUCTS_DIR}/barcode_scan2/barcode_scan2.framework",
"${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework",
"${BUILT_PRODUCTS_DIR}/cw_monero/cw_monero.framework",
@ -335,7 +335,6 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/app_settings.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/barcode_scan2.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cw_monero.framework",
@ -505,7 +504,10 @@
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
@ -692,7 +694,10 @@
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
@ -771,7 +776,10 @@
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",

View file

@ -13,7 +13,6 @@ import 'dart:isolate';
import 'package:cw_core/wallet_info.dart' as xmr;
import 'package:hive/hive.dart';
import 'package:mutex/mutex.dart';
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/notification_model.dart';
@ -23,8 +22,13 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
class DB {
// legacy (required for migrations)
@Deprecated("Left over for migration from old versions of Stack Wallet")
static const String boxNameAddressBook = "addressBook";
static const String boxNameDebugInfo = "debugInfoBox";
static const String boxNameTrades = "exchangeTransactionsBox";
// in use
// TODO: migrate
static const String boxNameNodeModels = "nodeModels";
static const String boxNamePrimaryNodes = "primaryNodes";
static const String boxNameAllWalletsData = "wallets";
@ -32,33 +36,31 @@ class DB {
static const String boxNameWatchedTransactions =
"watchedTxNotificationModels";
static const String boxNameWatchedTrades = "watchedTradesNotificationModels";
static const String boxNameTrades = "exchangeTransactionsBox";
static const String boxNameTradesV2 = "exchangeTradesBox";
static const String boxNameTradeNotes = "tradeNotesBox";
static const String boxNameTradeLookup = "tradeToTxidLookUpBox";
static const String boxNameFavoriteWallets = "favoriteWallets";
static const String boxNamePrefs = "prefs";
static const String boxNameWalletsToDeleteOnStart = "walletsToDeleteOnStart";
static const String boxNamePriceCache = "priceAPIPrice24hCache";
static const String boxNameDBInfo = "dbInfo";
// static const String boxNameTheme = "theme";
static const String boxNameDesktopData = "desktopData";
static const String boxNameBuys = "buysBox";
String boxNameTxCache({required Coin coin}) => "${coin.name}_txCache";
String boxNameSetCache({required Coin coin}) =>
// in use (keep for now)
static const String boxNameDBInfo = "dbInfo";
static const String boxNamePrefs = "prefs";
String _boxNameTxCache({required Coin coin}) => "${coin.name}_txCache";
// firo only
String _boxNameSetCache({required Coin coin}) =>
"${coin.name}_anonymitySetCache";
String boxNameUsedSerialsCache({required Coin coin}) =>
String _boxNameUsedSerialsCache({required Coin coin}) =>
"${coin.name}_usedSerialsCache";
Box<String>? _boxDebugInfo;
Box<NodeModel>? _boxNodeModels;
Box<NodeModel>? _boxPrimaryNodes;
Box<dynamic>? _boxAllWalletsData;
Box<NotificationModel>? _boxNotifications;
Box<NotificationModel>? _boxWatchedTransactions;
Box<NotificationModel>? _boxWatchedTrades;
Box<ExchangeTransaction>? _boxTrades;
Box<Trade>? _boxTradesV2;
Box<String>? _boxTradeNotes;
Box<String>? _boxFavoriteWallets;
@ -66,7 +68,7 @@ class DB {
Box<dynamic>? _boxPrefs;
Box<TradeWalletLookup>? _boxTradeLookup;
Box<dynamic>? _boxDBInfo;
Box<String>? _boxDesktopData;
// Box<String>? _boxDesktopData;
final Map<String, Box<dynamic>> _walletBoxes = {};
@ -109,8 +111,6 @@ class DB {
_boxPrefs = await Hive.openBox<dynamic>(boxNamePrefs);
}
_boxDebugInfo = await Hive.openBox<String>(boxNameDebugInfo);
if (Hive.isBoxOpen(boxNameNodeModels)) {
_boxNodeModels = Hive.box<NodeModel>(boxNameNodeModels);
} else {
@ -129,19 +129,12 @@ class DB {
_boxAllWalletsData = await Hive.openBox<dynamic>(boxNameAllWalletsData);
}
if (Hive.isBoxOpen(boxNameDesktopData)) {
_boxDesktopData = Hive.box<String>(boxNameDesktopData);
} else {
_boxDesktopData = await Hive.openBox<String>(boxNameDesktopData);
}
_boxNotifications =
await Hive.openBox<NotificationModel>(boxNameNotifications);
_boxWatchedTransactions =
await Hive.openBox<NotificationModel>(boxNameWatchedTransactions);
_boxWatchedTrades =
await Hive.openBox<NotificationModel>(boxNameWatchedTrades);
_boxTrades = await Hive.openBox<ExchangeTransaction>(boxNameTrades);
_boxTradesV2 = await Hive.openBox<Trade>(boxNameTradesV2);
_boxTradeNotes = await Hive.openBox<String>(boxNameTradeNotes);
_boxTradeLookup = await Hive.openBox<TradeWalletLookup>(boxNameTradeLookup);
@ -152,7 +145,6 @@ class DB {
await Future.wait([
Hive.openBox<dynamic>(boxNamePriceCache),
_loadWalletBoxes(),
_loadSharedCoinCacheBoxes(),
]);
}
@ -184,14 +176,39 @@ class DB {
}
}
Future<void> _loadSharedCoinCacheBoxes() async {
for (final coin in Coin.values) {
_txCacheBoxes[coin] =
await Hive.openBox<dynamic>(boxNameTxCache(coin: coin));
_setCacheBoxes[coin] =
await Hive.openBox<dynamic>(boxNameSetCache(coin: coin));
_usedSerialsCacheBoxes[coin] =
await Hive.openBox<dynamic>(boxNameUsedSerialsCache(coin: coin));
Future<Box<dynamic>> getTxCacheBox({required Coin coin}) async {
return _txCacheBoxes[coin] ??=
await Hive.openBox<dynamic>(_boxNameTxCache(coin: coin));
}
Future<void> closeTxCacheBox({required Coin coin}) async {
await _txCacheBoxes[coin]?.close();
}
Future<Box<dynamic>> getAnonymitySetCacheBox({required Coin coin}) async {
return _setCacheBoxes[coin] ??=
await Hive.openBox<dynamic>(_boxNameSetCache(coin: coin));
}
Future<void> closeAnonymitySetCacheBox({required Coin coin}) async {
await _setCacheBoxes[coin]?.close();
}
Future<Box<dynamic>> getUsedSerialsCacheBox({required Coin coin}) async {
return _usedSerialsCacheBoxes[coin] ??=
await Hive.openBox<dynamic>(_boxNameUsedSerialsCache(coin: coin));
}
Future<void> closeUsedSerialsCacheBox({required Coin coin}) async {
await _usedSerialsCacheBoxes[coin]?.close();
}
/// Clear all cached transactions for the specified coin
Future<void> clearSharedTransactionCache({required Coin coin}) async {
await deleteAll<dynamic>(boxName: _boxNameTxCache(coin: coin));
if (coin == Coin.firo) {
await deleteAll<dynamic>(boxName: _boxNameSetCache(coin: coin));
await deleteAll<dynamic>(boxName: _boxNameUsedSerialsCache(coin: coin));
}
}
@ -253,6 +270,36 @@ class DB {
Future<void> deleteBoxFromDisk({required String boxName}) async =>
await mutex.protect(() async => await Hive.deleteBoxFromDisk(boxName));
///////////////////////////////////////////////////////////////////////////
Future<bool> deleteEverything() async {
try {
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameAddressBook);
await DB.instance.deleteBoxFromDisk(boxName: "debugInfoBox");
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameNodeModels);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePrimaryNodes);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameAllWalletsData);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameNotifications);
await DB.instance
.deleteBoxFromDisk(boxName: DB.boxNameWatchedTransactions);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameWatchedTrades);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTrades);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTradesV2);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTradeNotes);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTradeLookup);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameFavoriteWallets);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePrefs);
await DB.instance
.deleteBoxFromDisk(boxName: DB.boxNameWalletsToDeleteOnStart);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePriceCache);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameDBInfo);
await DB.instance.deleteBoxFromDisk(boxName: "theme");
return true;
} catch (e, s) {
Logging.instance.log("$e $s", level: LogLevel.Error);
return false;
}
}
}
abstract class DBKeys {

View file

@ -66,7 +66,7 @@ class MainDB {
// contact entries
List<ContactEntry> getContactEntries() {
return isar.contactEntrys.where().findAllSync();
return isar.contactEntrys.where().sortByName().findAllSync();
}
Future<bool> deleteContactEntry({required String id}) {

View file

@ -46,7 +46,7 @@ class EthTokenTxExtraDTO {
),
gas: _amountFromJsonNum(map['gas']),
gasPrice: _amountFromJsonNum(map['gasPrice']),
nonce: map['nonce'] as int,
nonce: map['nonce'] as int?,
input: map['input'] as String,
gasCost: _amountFromJsonNum(map['gasCost']),
gasUsed: _amountFromJsonNum(map['gasUsed']),
@ -63,7 +63,7 @@ class EthTokenTxExtraDTO {
final Amount gas;
final Amount gasPrice;
final String input;
final int nonce;
final int? nonce;
final Amount gasCost;
final Amount gasUsed;
@ -116,13 +116,13 @@ class EthTokenTxExtraDTO {
map['timestamp'] = timestamp;
map['from'] = from;
map['to'] = to;
map['value'] = value;
map['gas'] = gas;
map['gasPrice'] = gasPrice;
map['value'] = value.toJsonString();
map['gas'] = gas.toJsonString();
map['gasPrice'] = gasPrice.toJsonString();
map['input'] = input;
map['nonce'] = nonce;
map['gasCost'] = gasCost;
map['gasUsed'] = gasUsed;
map['gasCost'] = gasCost.toJsonString();
map['gasUsed'] = gasUsed.toJsonString();
return map;
}

View file

@ -127,16 +127,16 @@ class EthTxDTO {
map['timestamp'] = timestamp;
map['from'] = from;
map['to'] = to;
map['value'] = value;
map['gas'] = gas;
map['gasPrice'] = gasPrice;
map['maxFeePerGas'] = maxFeePerGas;
map['maxPriorityFeePerGas'] = maxPriorityFeePerGas;
map['value'] = value.toString();
map['gas'] = gas.toString();
map['gasPrice'] = gasPrice.toString();
map['maxFeePerGas'] = maxFeePerGas.toString();
map['maxPriorityFeePerGas'] = maxPriorityFeePerGas.toString();
map['isError'] = isError;
map['hasToken'] = hasToken;
map['compressedTx'] = compressedTx;
map['gasCost'] = gasCost;
map['gasUsed'] = gasUsed;
map['gasCost'] = gasCost.toString();
map['gasUsed'] = gasUsed.toString();
return map;
}

View file

@ -38,9 +38,8 @@ class CachedElectrumX {
required Coin coin,
}) async {
try {
final cachedSet = DB.instance.get<dynamic>(
boxName: DB.instance.boxNameSetCache(coin: coin),
key: groupId) as Map?;
final box = await DB.instance.getAnonymitySetCacheBox(coin: coin);
final cachedSet = box.get(groupId) as Map?;
Map<String, dynamic> set;
@ -71,7 +70,7 @@ class CachedElectrumX {
: newSet["blockHash"];
for (int i = (newSet["coins"] as List).length - 1; i >= 0; i--) {
dynamic newCoin = newSet["coins"][i];
List translatedCoin = [];
List<dynamic> translatedCoin = [];
translatedCoin.add(!isHexadecimal(newCoin[0] as String)
? base64ToHex(newCoin[0] as String)
: newCoin[0]);
@ -91,10 +90,7 @@ class CachedElectrumX {
set["coins"].insert(0, translatedCoin);
}
// save set to db
await DB.instance.put<dynamic>(
boxName: DB.instance.boxNameSetCache(coin: coin),
key: groupId,
value: set);
await box.put(groupId, set);
Logging.instance.log(
"Updated current anonymity set for ${coin.name} with group ID $groupId",
level: LogLevel.Info,
@ -130,8 +126,9 @@ class CachedElectrumX {
bool verbose = true,
}) async {
try {
final cachedTx = DB.instance.get<dynamic>(
boxName: DB.instance.boxNameTxCache(coin: coin), key: txHash) as Map?;
final box = await DB.instance.getTxCacheBox(coin: coin);
final cachedTx = box.get(txHash) as Map?;
if (cachedTx == null) {
final Map<String, dynamic> result = await electrumXClient
.getTransaction(txHash: txHash, verbose: verbose);
@ -141,10 +138,7 @@ class CachedElectrumX {
if (result["confirmations"] != null &&
result["confirmations"] as int > minCacheConfirms) {
await DB.instance.put<dynamic>(
boxName: DB.instance.boxNameTxCache(coin: coin),
key: txHash,
value: result);
await box.put(txHash, result);
}
Logging.instance.log("using fetched result", level: LogLevel.Info);
@ -166,9 +160,9 @@ class CachedElectrumX {
int startNumber = 0,
}) async {
try {
final _list = DB.instance.get<dynamic>(
boxName: DB.instance.boxNameUsedSerialsCache(coin: coin),
key: "serials") as List?;
final box = await DB.instance.getUsedSerialsCacheBox(coin: coin);
final _list = box.get("serials") as List?;
List<String> cachedSerials =
_list == null ? [] : List<String>.from(_list);
@ -188,10 +182,9 @@ class CachedElectrumX {
}
cachedSerials.addAll(newSerials);
await DB.instance.put<dynamic>(
boxName: DB.instance.boxNameUsedSerialsCache(coin: coin),
key: "serials",
value: cachedSerials,
await box.put(
"serials",
cachedSerials,
);
return cachedSerials;
@ -205,11 +198,6 @@ class CachedElectrumX {
/// Clear all cached transactions for the specified coin
Future<void> clearSharedTransactionCache({required Coin coin}) async {
await DB.instance
.deleteAll<dynamic>(boxName: DB.instance.boxNameTxCache(coin: coin));
await DB.instance
.deleteAll<dynamic>(boxName: DB.instance.boxNameSetCache(coin: coin));
await DB.instance.deleteAll<dynamic>(
boxName: DB.instance.boxNameUsedSerialsCache(coin: coin));
await DB.instance.closeAnonymitySetCacheBox(coin: coin);
}
}

View file

@ -146,7 +146,7 @@ class ElectrumX {
throw response.exception!;
}
if (response.data["error"] != null) {
if (response.data is Map && response.data["error"] != null) {
if (response.data["error"]
.toString()
.contains("No such mempool or blockchain transaction")) {
@ -160,7 +160,7 @@ class ElectrumX {
"JSONRPC response\n"
" command: $command\n"
" args: $args\n"
" error: $response.data",
" error: ${response.data}",
);
}

View file

@ -167,6 +167,8 @@ void main() async {
await Hive.openBox<dynamic>(DB.boxNamePrefs);
await Prefs.instance.init();
await StackFileSystem.initThemesDir();
// Desktop migrate handled elsewhere (currently desktop_login_view.dart)
if (!Util.isDesktop) {
int dbVersion = DB.instance.get<dynamic>(
@ -312,7 +314,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
}
ref.read(applicationThemesDirectoryPathProvider.notifier).state =
(await StackFileSystem.applicationThemesDirectory()).path;
StackFileSystem.themesDir!.path;
_notificationsService = ref.read(notificationsProvider);
_nodeService = ref.read(nodeServiceChangeNotifierProvider);
@ -407,7 +409,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
WidgetsBinding.instance.addPostFrameCallback((_) async {
//Add themes path to provider
ref.read(applicationThemesDirectoryPathProvider.notifier).state =
(await StackFileSystem.applicationThemesDirectory()).path;
StackFileSystem.themesDir!.path;
ref.read(themeProvider.state).state = ref.read(pThemeService).getTheme(
themeId: themeId,
@ -463,6 +465,12 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
super.dispose();
}
@override
void didChangeLocales(List<Locale>? locales) {
ref.read(localeServiceChangeNotifierProvider).loadLocale();
super.didChangeLocales(locales);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) async {
debugPrint("didChangeAppLifecycleState: ${state.name}");

View file

@ -33,6 +33,22 @@ class ContactEntry {
@Index(unique: true, replace: true)
late final String customId;
@ignore
List<ContactAddressEntry> get addressesSorted {
final List<ContactAddressEntry> sorted = [];
for (final coin in Coin.values) {
final slice = addresses.where((e) => e.coin == coin).toList();
if (slice.isNotEmpty) {
slice.sort(
(a, b) => (a.other ?? a.label).compareTo(b.other ?? b.label),
);
sorted.addAll(slice);
}
}
return sorted;
}
ContactEntry copyWith({
bool shouldCopyEmojiWithNull = false,
String? emojiChar,

View file

@ -9,6 +9,7 @@
*/
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:isar/isar.dart';
@ -17,6 +18,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/extensions/impl/box_shadow.dart';
import 'package:stackwallet/utilities/extensions/impl/gradient.dart';
import 'package:stackwallet/utilities/extensions/impl/string.dart';
import 'package:stackwallet/utilities/stack_file_system.dart';
part 'stack_theme.g.dart';
@ -1508,7 +1510,6 @@ class StackTheme {
factory StackTheme.fromJson({
required Map<String, dynamic> json,
required String applicationThemesDirectoryPath,
}) {
final version = json["version"] as int? ?? 1;
@ -1517,21 +1518,18 @@ class StackTheme {
..assetsV1 = version == 1
? ThemeAssets.fromJson(
json: Map<String, dynamic>.from(json["assets"] as Map),
applicationThemesDirectoryPath: applicationThemesDirectoryPath,
themeId: json["id"] as String,
)
: null
..assetsV2 = version == 2
? ThemeAssetsV2.fromJson(
json: Map<String, dynamic>.from(json["assets"] as Map),
applicationThemesDirectoryPath: applicationThemesDirectoryPath,
themeId: json["id"] as String,
)
: null
..assetsV3 = version >= 3
? ThemeAssetsV3.fromJson(
json: Map<String, dynamic>.from(json["assets"] as Map),
applicationThemesDirectoryPath: applicationThemesDirectoryPath,
themeId: json["id"] as String,
)
: null
@ -1954,117 +1952,81 @@ class ThemeAssets implements IThemeAssets {
factory ThemeAssets.fromJson({
required Map<String, dynamic> json,
required String applicationThemesDirectoryPath,
required String themeId,
}) {
return ThemeAssets()
..bellNew =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bell_new"] as String}"
..buy =
"$applicationThemesDirectoryPath/$themeId/assets/${json["buy"] as String}"
..exchange =
"$applicationThemesDirectoryPath/$themeId/assets/${json["exchange"] as String}"
..bellNew = "$themeId/assets/${json["bell_new"] as String}"
..buy = "$themeId/assets/${json["buy"] as String}"
..exchange = "$themeId/assets/${json["exchange"] as String}"
..personaIncognito =
"$applicationThemesDirectoryPath/$themeId/assets/${json["persona_incognito"] as String}"
..personaEasy =
"$applicationThemesDirectoryPath/$themeId/assets/${json["persona_easy"] as String}"
..stack =
"$applicationThemesDirectoryPath/$themeId/assets/${json["stack"] as String}"
..stackIcon =
"$applicationThemesDirectoryPath/$themeId/assets/${json["stack_icon"] as String}"
..receive =
"$applicationThemesDirectoryPath/$themeId/assets/${json["receive"] as String}"
..receivePending =
"$applicationThemesDirectoryPath/$themeId/assets/${json["receive_pending"] as String}"
"$themeId/assets/${json["persona_incognito"] as String}"
..personaEasy = "$themeId/assets/${json["persona_easy"] as String}"
..stack = "$themeId/assets/${json["stack"] as String}"
..stackIcon = "$themeId/assets/${json["stack_icon"] as String}"
..receive = "$themeId/assets/${json["receive"] as String}"
..receivePending = "$themeId/assets/${json["receive_pending"] as String}"
..receiveCancelled =
"$applicationThemesDirectoryPath/$themeId/assets/${json["receive_cancelled"] as String}"
..send =
"$applicationThemesDirectoryPath/$themeId/assets/${json["send"] as String}"
..sendPending =
"$applicationThemesDirectoryPath/$themeId/assets/${json["send_pending"] as String}"
..sendCancelled =
"$applicationThemesDirectoryPath/$themeId/assets/${json["send_cancelled"] as String}"
..themeSelector =
"$applicationThemesDirectoryPath/$themeId/assets/${json["theme_selector"] as String}"
..themePreview =
"$applicationThemesDirectoryPath/$themeId/assets/${json["theme_preview"] as String}"
..txExchange =
"$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange"] as String}"
"$themeId/assets/${json["receive_cancelled"] as String}"
..send = "$themeId/assets/${json["send"] as String}"
..sendPending = "$themeId/assets/${json["send_pending"] as String}"
..sendCancelled = "$themeId/assets/${json["send_cancelled"] as String}"
..themeSelector = "$themeId/assets/${json["theme_selector"] as String}"
..themePreview = "$themeId/assets/${json["theme_preview"] as String}"
..txExchange = "$themeId/assets/${json["tx_exchange"] as String}"
..txExchangePending =
"$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange_pending"] as String}"
"$themeId/assets/${json["tx_exchange_pending"] as String}"
..txExchangeFailed =
"$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange_failed"] as String}"
..bitcoin =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoin"] as String}"
..litecoin =
"$applicationThemesDirectoryPath/$themeId/assets/${json["litecoin"] as String}"
..bitcoincash =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoincash"] as String}"
..dogecoin =
"$applicationThemesDirectoryPath/$themeId/assets/${json["dogecoin"] as String}"
..epicCash =
"$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash"] as String}"
..ethereum =
"$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum"] as String}"
..firo =
"$applicationThemesDirectoryPath/$themeId/assets/${json["firo"] as String}"
..monero =
"$applicationThemesDirectoryPath/$themeId/assets/${json["monero"] as String}"
..wownero =
"$applicationThemesDirectoryPath/$themeId/assets/${json["wownero"] as String}"
..namecoin =
"$applicationThemesDirectoryPath/$themeId/assets/${json["namecoin"] as String}"
..particl =
"$applicationThemesDirectoryPath/$themeId/assets/${json["particl"] as String}"
..bitcoinImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoin_image"] as String}"
"$themeId/assets/${json["tx_exchange_failed"] as String}"
..bitcoin = "$themeId/assets/${json["bitcoin"] as String}"
..litecoin = "$themeId/assets/${json["litecoin"] as String}"
..bitcoincash = "$themeId/assets/${json["bitcoincash"] as String}"
..dogecoin = "$themeId/assets/${json["dogecoin"] as String}"
..epicCash = "$themeId/assets/${json["epicCash"] as String}"
..ethereum = "$themeId/assets/${json["ethereum"] as String}"
..firo = "$themeId/assets/${json["firo"] as String}"
..monero = "$themeId/assets/${json["monero"] as String}"
..wownero = "$themeId/assets/${json["wownero"] as String}"
..namecoin = "$themeId/assets/${json["namecoin"] as String}"
..particl = "$themeId/assets/${json["particl"] as String}"
..bitcoinImage = "$themeId/assets/${json["bitcoin_image"] as String}"
..bitcoincashImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoincash_image"] as String}"
..dogecoinImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["dogecoin_image"] as String}"
..epicCashImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash_image"] as String}"
..ethereumImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum_image"] as String}"
..firoImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["firo_image"] as String}"
..litecoinImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["litecoin_image"] as String}"
..moneroImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["monero_image"] as String}"
..wowneroImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["wownero_image"] as String}"
..namecoinImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["namecoin_image"] as String}"
..particlImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["particl_image"] as String}"
"$themeId/assets/${json["bitcoincash_image"] as String}"
..dogecoinImage = "$themeId/assets/${json["dogecoin_image"] as String}"
..epicCashImage = "$themeId/assets/${json["epicCash_image"] as String}"
..ethereumImage = "$themeId/assets/${json["ethereum_image"] as String}"
..firoImage = "$themeId/assets/${json["firo_image"] as String}"
..litecoinImage = "$themeId/assets/${json["litecoin_image"] as String}"
..moneroImage = "$themeId/assets/${json["monero_image"] as String}"
..wowneroImage = "$themeId/assets/${json["wownero_image"] as String}"
..namecoinImage = "$themeId/assets/${json["namecoin_image"] as String}"
..particlImage = "$themeId/assets/${json["particl_image"] as String}"
..bitcoinImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoin_image_secondary"] as String}"
"$themeId/assets/${json["bitcoin_image_secondary"] as String}"
..bitcoincashImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoincash_image_secondary"] as String}"
"$themeId/assets/${json["bitcoincash_image_secondary"] as String}"
..dogecoinImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["dogecoin_image_secondary"] as String}"
"$themeId/assets/${json["dogecoin_image_secondary"] as String}"
..epicCashImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash_image_secondary"] as String}"
"$themeId/assets/${json["epicCash_image_secondary"] as String}"
..ethereumImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum_image_secondary"] as String}"
"$themeId/assets/${json["ethereum_image_secondary"] as String}"
..firoImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["firo_image_secondary"] as String}"
"$themeId/assets/${json["firo_image_secondary"] as String}"
..litecoinImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["litecoin_image_secondary"] as String}"
"$themeId/assets/${json["litecoin_image_secondary"] as String}"
..moneroImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["monero_image_secondary"] as String}"
"$themeId/assets/${json["monero_image_secondary"] as String}"
..wowneroImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["wownero_image_secondary"] as String}"
"$themeId/assets/${json["wownero_image_secondary"] as String}"
..namecoinImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["namecoin_image_secondary"] as String}"
"$themeId/assets/${json["namecoin_image_secondary"] as String}"
..particlImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["particl_image_secondary"] as String}"
"$themeId/assets/${json["particl_image_secondary"] as String}"
..loadingGif = json["loading_gif"] is String
? "$applicationThemesDirectoryPath/$themeId/assets/${json["loading_gif"] as String}"
? "$themeId/assets/${json["loading_gif"] as String}"
: null
..background = json["background"] is String
? "$applicationThemesDirectoryPath/$themeId/assets/${json["background"] as String}"
? "$themeId/assets/${json["background"] as String}"
: null;
}
}
@ -2146,65 +2108,50 @@ class ThemeAssetsV2 implements IThemeAssets {
factory ThemeAssetsV2.fromJson({
required Map<String, dynamic> json,
required String applicationThemesDirectoryPath,
required String themeId,
}) {
return ThemeAssetsV2()
..bellNew =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bell_new"] as String}"
..buy =
"$applicationThemesDirectoryPath/$themeId/assets/${json["buy"] as String}"
..exchange =
"$applicationThemesDirectoryPath/$themeId/assets/${json["exchange"] as String}"
..bellNew = "$themeId/assets/${json["bell_new"] as String}"
..buy = "$themeId/assets/${json["buy"] as String}"
..exchange = "$themeId/assets/${json["exchange"] as String}"
..personaIncognito =
"$applicationThemesDirectoryPath/$themeId/assets/${json["persona_incognito"] as String}"
..personaEasy =
"$applicationThemesDirectoryPath/$themeId/assets/${json["persona_easy"] as String}"
..stack =
"$applicationThemesDirectoryPath/$themeId/assets/${json["stack"] as String}"
..stackIcon =
"$applicationThemesDirectoryPath/$themeId/assets/${json["stack_icon"] as String}"
..receive =
"$applicationThemesDirectoryPath/$themeId/assets/${json["receive"] as String}"
..receivePending =
"$applicationThemesDirectoryPath/$themeId/assets/${json["receive_pending"] as String}"
"$themeId/assets/${json["persona_incognito"] as String}"
..personaEasy = "$themeId/assets/${json["persona_easy"] as String}"
..stack = "$themeId/assets/${json["stack"] as String}"
..stackIcon = "$themeId/assets/${json["stack_icon"] as String}"
..receive = "$themeId/assets/${json["receive"] as String}"
..receivePending = "$themeId/assets/${json["receive_pending"] as String}"
..receiveCancelled =
"$applicationThemesDirectoryPath/$themeId/assets/${json["receive_cancelled"] as String}"
..send =
"$applicationThemesDirectoryPath/$themeId/assets/${json["send"] as String}"
..sendPending =
"$applicationThemesDirectoryPath/$themeId/assets/${json["send_pending"] as String}"
..sendCancelled =
"$applicationThemesDirectoryPath/$themeId/assets/${json["send_cancelled"] as String}"
..themeSelector =
"$applicationThemesDirectoryPath/$themeId/assets/${json["theme_selector"] as String}"
..themePreview =
"$applicationThemesDirectoryPath/$themeId/assets/${json["theme_preview"] as String}"
..txExchange =
"$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange"] as String}"
"$themeId/assets/${json["receive_cancelled"] as String}"
..send = "$themeId/assets/${json["send"] as String}"
..sendPending = "$themeId/assets/${json["send_pending"] as String}"
..sendCancelled = "$themeId/assets/${json["send_cancelled"] as String}"
..themeSelector = "$themeId/assets/${json["theme_selector"] as String}"
..themePreview = "$themeId/assets/${json["theme_preview"] as String}"
..txExchange = "$themeId/assets/${json["tx_exchange"] as String}"
..txExchangePending =
"$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange_pending"] as String}"
"$themeId/assets/${json["tx_exchange_pending"] as String}"
..txExchangeFailed =
"$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange_failed"] as String}"
"$themeId/assets/${json["tx_exchange_failed"] as String}"
..coinPlaceholder =
"$applicationThemesDirectoryPath/$themeId/assets/${json["coin_placeholder"] as String}"
"$themeId/assets/${json["coin_placeholder"] as String}"
..coinIconsString = createCoinAssetsString(
"$applicationThemesDirectoryPath/$themeId/assets",
"$themeId/assets",
Map<String, dynamic>.from(json["coins"]["icons"] as Map),
)
..coinImagesString = createCoinAssetsString(
"$applicationThemesDirectoryPath/$themeId/assets",
"$themeId/assets",
Map<String, dynamic>.from(json["coins"]["images"] as Map),
)
..coinSecondaryImagesString = createCoinAssetsString(
"$applicationThemesDirectoryPath/$themeId/assets",
"$themeId/assets",
Map<String, dynamic>.from(json["coins"]["secondaries"] as Map),
)
..loadingGif = json["loading_gif"] is String
? "$applicationThemesDirectoryPath/$themeId/assets/${json["loading_gif"] as String}"
? "$themeId/assets/${json["loading_gif"] as String}"
: null
..background = json["background"] is String
? "$applicationThemesDirectoryPath/$themeId/assets/${json["background"] as String}"
? "$themeId/assets/${json["background"] as String}"
: null;
}
@ -2265,53 +2212,135 @@ class ThemeAssetsV2 implements IThemeAssets {
@Embedded(inheritance: false)
class ThemeAssetsV3 implements IThemeAssets {
@Name("bellNew")
late final String bellNewRelative;
@override
late final String bellNew;
@override
late final String buy;
@override
late final String exchange;
@override
late final String personaIncognito;
@override
late final String personaEasy;
@override
late final String stack;
@override
late final String stackIcon;
@override
late final String receive;
@override
late final String receivePending;
@override
late final String receiveCancelled;
@override
late final String send;
@override
late final String sendPending;
@override
late final String sendCancelled;
@override
late final String themeSelector;
@override
late final String themePreview;
@override
late final String txExchange;
@override
late final String txExchangePending;
@override
late final String txExchangeFailed;
@override
late final String? loadingGif;
@override
late final String? background;
@ignore
String get bellNew => prependIfNeeded(bellNewRelative);
late final String coinPlaceholder;
@Name("buy")
late final String buyRelative;
@override
@ignore
String get buy => prependIfNeeded(buyRelative);
@Name("exchange")
late final String exchangeRelative;
@override
@ignore
String get exchange => prependIfNeeded(exchangeRelative);
@Name("personaIncognito")
late final String personaIncognitoRelative;
@override
@ignore
String get personaIncognito => prependIfNeeded(personaIncognitoRelative);
@Name("personaEasy")
late final String personaEasyRelative;
@override
@ignore
String get personaEasy => prependIfNeeded(personaEasyRelative);
@Name("stack")
late final String stackRelative;
@override
@ignore
String get stack => prependIfNeeded(stackRelative);
@Name("stackIcon")
late final String stackIconRelative;
@override
@ignore
String get stackIcon => prependIfNeeded(stackIconRelative);
@Name("receive")
late final String receiveRelative;
@override
@ignore
String get receive => prependIfNeeded(receiveRelative);
@Name("receivePending")
late final String receivePendingRelative;
@override
@ignore
String get receivePending => prependIfNeeded(receivePendingRelative);
@Name("receiveCancelled")
late final String receiveCancelledRelative;
@override
@ignore
String get receiveCancelled => prependIfNeeded(receiveCancelledRelative);
@Name("send")
late final String sendRelative;
@override
@ignore
String get send => prependIfNeeded(sendRelative);
@Name("sendPending")
late final String sendPendingRelative;
@override
@ignore
String get sendPending => prependIfNeeded(sendPendingRelative);
@Name("sendCancelled")
late final String sendCancelledRelative;
@override
@ignore
String get sendCancelled => prependIfNeeded(sendCancelledRelative);
@Name("themeSelector")
late final String themeSelectorRelative;
@override
@ignore
String get themeSelector => prependIfNeeded(themeSelectorRelative);
@Name("themePreview")
late final String themePreviewRelative;
@override
@ignore
String get themePreview => prependIfNeeded(themePreviewRelative);
@Name("txExchange")
late final String txExchangeRelative;
@override
@ignore
String get txExchange => prependIfNeeded(txExchangeRelative);
@Name("txExchangePending")
late final String txExchangePendingRelative;
@override
@ignore
String get txExchangePending => prependIfNeeded(txExchangePendingRelative);
@Name("txExchangeFailed")
late final String txExchangeFailedRelative;
@override
@ignore
String get txExchangeFailed => prependIfNeeded(txExchangeFailedRelative);
@Name("loadingGif")
late final String? loadingGifRelative;
@override
@ignore
String? get loadingGif =>
loadingGifRelative != null ? prependIfNeeded(loadingGifRelative!) : null;
@Name("background")
late final String? backgroundRelative;
@override
@ignore
String? get background =>
backgroundRelative != null ? prependIfNeeded(backgroundRelative!) : null;
@Name("coinPlaceholder")
late final String coinPlaceholderRelative;
@ignore
String get coinPlaceholder => prependIfNeeded(coinPlaceholderRelative);
// Added some future proof params in case we want to add anything else
// This should provide some buffer in stead of creating assetsV4 etc
@Name("otherStringParam1")
late final String? dummy1;
@Name("otherStringParam2")
late final String? dummy2;
@Name("otherStringParam3")
@ -2357,81 +2386,110 @@ class ThemeAssetsV3 implements IThemeAssets {
Map<Coin, String>? _coinCardImages;
late final String? coinCardImagesString;
@ignore
Map<Coin, String>? get coinCardFavoritesImages =>
_coinCardFavoritesImages ??= coinCardFavoritesImagesString == null
? null
: parseCoinAssetsString(
coinCardFavoritesImagesString!,
placeHolder: coinPlaceholder,
);
@ignore
Map<Coin, String>? _coinCardFavoritesImages;
@Name("otherStringParam1")
late final String? coinCardFavoritesImagesString;
ThemeAssetsV3();
factory ThemeAssetsV3.fromJson({
required Map<String, dynamic> json,
required String applicationThemesDirectoryPath,
required String themeId,
}) {
return ThemeAssetsV3()
..bellNew =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bell_new"] as String}"
..buy =
"$applicationThemesDirectoryPath/$themeId/assets/${json["buy"] as String}"
..exchange =
"$applicationThemesDirectoryPath/$themeId/assets/${json["exchange"] as String}"
..personaIncognito =
"$applicationThemesDirectoryPath/$themeId/assets/${json["persona_incognito"] as String}"
..personaEasy =
"$applicationThemesDirectoryPath/$themeId/assets/${json["persona_easy"] as String}"
..stack =
"$applicationThemesDirectoryPath/$themeId/assets/${json["stack"] as String}"
..stackIcon =
"$applicationThemesDirectoryPath/$themeId/assets/${json["stack_icon"] as String}"
..receive =
"$applicationThemesDirectoryPath/$themeId/assets/${json["receive"] as String}"
..receivePending =
"$applicationThemesDirectoryPath/$themeId/assets/${json["receive_pending"] as String}"
..receiveCancelled =
"$applicationThemesDirectoryPath/$themeId/assets/${json["receive_cancelled"] as String}"
..send =
"$applicationThemesDirectoryPath/$themeId/assets/${json["send"] as String}"
..sendPending =
"$applicationThemesDirectoryPath/$themeId/assets/${json["send_pending"] as String}"
..sendCancelled =
"$applicationThemesDirectoryPath/$themeId/assets/${json["send_cancelled"] as String}"
..themeSelector =
"$applicationThemesDirectoryPath/$themeId/assets/${json["theme_selector"] as String}"
..themePreview =
"$applicationThemesDirectoryPath/$themeId/assets/${json["theme_preview"] as String}"
..txExchange =
"$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange"] as String}"
..txExchangePending =
"$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange_pending"] as String}"
..txExchangeFailed =
"$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange_failed"] as String}"
..coinPlaceholder =
"$applicationThemesDirectoryPath/$themeId/assets/${json["coin_placeholder"] as String}"
..bellNewRelative = "$themeId/assets/${json["bell_new"] as String}"
..buyRelative = "$themeId/assets/${json["buy"] as String}"
..exchangeRelative = "$themeId/assets/${json["exchange"] as String}"
..personaIncognitoRelative =
"$themeId/assets/${json["persona_incognito"] as String}"
..personaEasyRelative =
"$themeId/assets/${json["persona_easy"] as String}"
..stackRelative = "$themeId/assets/${json["stack"] as String}"
..stackIconRelative = "$themeId/assets/${json["stack_icon"] as String}"
..receiveRelative = "$themeId/assets/${json["receive"] as String}"
..receivePendingRelative =
"$themeId/assets/${json["receive_pending"] as String}"
..receiveCancelledRelative =
"$themeId/assets/${json["receive_cancelled"] as String}"
..sendRelative = "$themeId/assets/${json["send"] as String}"
..sendPendingRelative =
"$themeId/assets/${json["send_pending"] as String}"
..sendCancelledRelative =
"$themeId/assets/${json["send_cancelled"] as String}"
..themeSelectorRelative =
"$themeId/assets/${json["theme_selector"] as String}"
..themePreviewRelative =
"$themeId/assets/${json["theme_preview"] as String}"
..txExchangeRelative = "$themeId/assets/${json["tx_exchange"] as String}"
..txExchangePendingRelative =
"$themeId/assets/${json["tx_exchange_pending"] as String}"
..txExchangeFailedRelative =
"$themeId/assets/${json["tx_exchange_failed"] as String}"
..coinPlaceholderRelative =
"$themeId/assets/${json["coin_placeholder"] as String}"
..coinIconsString = createCoinAssetsString(
"$applicationThemesDirectoryPath/$themeId/assets",
"$themeId/assets",
Map<String, dynamic>.from(json["coins"]["icons"] as Map),
)
..coinImagesString = createCoinAssetsString(
"$applicationThemesDirectoryPath/$themeId/assets",
"$themeId/assets",
Map<String, dynamic>.from(json["coins"]["images"] as Map),
)
..coinSecondaryImagesString = createCoinAssetsString(
"$applicationThemesDirectoryPath/$themeId/assets",
"$themeId/assets",
Map<String, dynamic>.from(json["coins"]["secondaries"] as Map),
)
..coinCardImagesString = json["coins"]["cards"] is Map
? createCoinAssetsString(
"$applicationThemesDirectoryPath/$themeId/assets",
"$themeId/assets",
Map<String, dynamic>.from(json["coins"]["cards"] as Map),
)
: null
..loadingGif = json["loading_gif"] is String
? "$applicationThemesDirectoryPath/$themeId/assets/${json["loading_gif"] as String}"
..coinCardFavoritesImagesString = json["coins"]["favoriteCards"] is Map
? createCoinAssetsString(
"$themeId/assets",
Map<String, dynamic>.from(json["coins"]["favoriteCards"] as Map),
)
: null
..background = json["background"] is String
? "$applicationThemesDirectoryPath/$themeId/assets/${json["background"] as String}"
..loadingGifRelative = json["loading_gif"] is String
? "$themeId/assets/${json["loading_gif"] as String}"
: null
..backgroundRelative = json["background"] is String
? "$themeId/assets/${json["background"] as String}"
: null
..dummy1 = null
..dummy2 = null
..dummy3 = null;
}
static String prependIfNeeded(String relativePath) {
final path = StackFileSystem.themesDir!.path;
if (relativePath.startsWith(path)) {
return relativePath;
} else {
if (Platform.isIOS) {
const pattern = "/var/mobile/Containers/Data/Application/";
if (relativePath.startsWith(pattern)) {
final parts = relativePath.split("/Library/themes/");
if (parts.isNotEmpty) {
return "$path/${parts.last}";
}
}
}
return "$path/$relativePath";
}
}
static String createCoinAssetsString(String path, Map<String, dynamic> json) {
final Map<String, dynamic> map = {};
for (final entry in json.entries) {
@ -2451,6 +2509,8 @@ class ThemeAssetsV3 implements IThemeAssets {
for (final coin in Coin.values) {
result[coin] = map[coin.name] as String? ?? placeHolder;
result[coin] = prependIfNeeded(result[coin]!);
}
return result;
@ -2484,6 +2544,7 @@ class ThemeAssetsV3 implements IThemeAssets {
'coinImages: $coinImages, '
'coinSecondaryImages: $coinSecondaryImages, '
'coinCardImages: $coinCardImages'
'coinCardFavoritesImages: $coinCardFavoritesImages'
')';
}
}

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,8 @@
*
*/
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:stackwallet/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart';
import 'package:stackwallet/themes/stack_colors.dart';
@ -32,6 +34,7 @@ class CreateWalletButtonGroup extends StatelessWidget {
crossAxisAlignment:
isDesktop ? CrossAxisAlignment.center : CrossAxisAlignment.stretch,
children: [
if (Platform.isAndroid || coin != Coin.wownero)
ConstrainedBox(
constraints: BoxConstraints(
minHeight: isDesktop ? 70 : 0,
@ -58,6 +61,7 @@ class CreateWalletButtonGroup extends StatelessWidget {
),
),
),
if (Platform.isAndroid || coin != Coin.wownero)
SizedBox(
height: isDesktop ? 16 : 12,
),

View file

@ -98,6 +98,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
final List<TextEditingController> _controllers = [];
final List<FormInputStatus> _inputStatuses = [];
final List<FocusNode> _focusNodes = [];
late final BarcodeScannerInterface scanner;
@ -151,6 +152,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
for (int i = 0; i < _seedWordCount; i++) {
_controllers.add(TextEditingController());
_inputStatuses.add(FormInputStatus.empty);
_focusNodes.add(FocusNode());
}
super.initState();
@ -819,30 +821,43 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
i * 4 + j - 1 == 1
? textSelectionControls
: null,
focusNode:
_focusNodes[i * 4 + j - 1],
onChanged: (value) {
final FormInputStatus
formInputStatus;
if (value.isEmpty) {
setState(() {
_inputStatuses[
i * 4 + j - 1] =
formInputStatus =
FormInputStatus.empty;
});
} else if (_isValidMnemonicWord(
value
.trim()
.toLowerCase())) {
setState(() {
_inputStatuses[
i * 4 + j - 1] =
formInputStatus =
FormInputStatus.valid;
});
} else {
setState(() {
_inputStatuses[
i * 4 + j - 1] =
FormInputStatus
.invalid;
});
formInputStatus =
FormInputStatus.invalid;
}
if (formInputStatus ==
FormInputStatus.valid) {
if (i * 4 + j <
_focusNodes.length) {
_focusNodes[i * 4 + j]
.requestFocus();
} else if (i * 4 + j ==
_focusNodes.length) {
_focusNodes[i * 4 + j - 1]
.unfocus();
}
}
setState(() {
_inputStatuses[i * 4 +
j -
1] = formInputStatus;
});
},
controller:
_controllers[i * 4 + j - 1],
@ -914,26 +929,45 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
selectionControls: i == 1
? textSelectionControls
: null,
focusNode: _focusNodes[i],
onChanged: (value) {
final FormInputStatus
formInputStatus;
if (value.isEmpty) {
setState(() {
_inputStatuses[i] =
formInputStatus =
FormInputStatus.empty;
});
} else if (_isValidMnemonicWord(
value
.trim()
.toLowerCase())) {
setState(() {
_inputStatuses[i] =
formInputStatus =
FormInputStatus.valid;
});
} else {
setState(() {
_inputStatuses[i] =
formInputStatus =
FormInputStatus.invalid;
}
if (formInputStatus ==
FormInputStatus
.invalid;
});
.valid &&
(i - 1) <
_focusNodes.length) {
Focus.of(context)
.requestFocus(
_focusNodes[i]);
}
if (formInputStatus ==
FormInputStatus.valid) {
if (i + 1 <
_focusNodes.length) {
_focusNodes[i + 1]
.requestFocus();
} else if (i + 1 ==
_focusNodes.length) {
_focusNodes[i].unfocus();
}
}
},
controller: _controllers[i],
@ -1034,24 +1068,34 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
AutovalidateMode.onUserInteraction,
selectionControls:
i == 1 ? textSelectionControls : null,
focusNode: _focusNodes[i - 1],
onChanged: (value) {
final FormInputStatus formInputStatus;
if (value.isEmpty) {
setState(() {
_inputStatuses[i - 1] =
formInputStatus =
FormInputStatus.empty;
});
} else if (_isValidMnemonicWord(
value.trim().toLowerCase())) {
setState(() {
_inputStatuses[i - 1] =
formInputStatus =
FormInputStatus.valid;
});
} else {
formInputStatus =
FormInputStatus.invalid;
}
if (formInputStatus ==
FormInputStatus.valid) {
if (i < _focusNodes.length) {
_focusNodes[i].requestFocus();
} else if (i == _focusNodes.length) {
_focusNodes[i - 1].unfocus();
}
}
setState(() {
_inputStatuses[i - 1] =
FormInputStatus.invalid;
formInputStatus;
});
}
},
controller: _controllers[i - 1],
style:

View file

@ -302,7 +302,7 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> {
child: Column(
children: [
...contacts
.where((element) => element.addresses
.where((element) => element.addressesSorted
.where((e) => ref.watch(addressBookFilterProvider
.select((value) => value.coins.contains(e.coin))))
.isNotEmpty)
@ -350,7 +350,7 @@ class _AddressBookViewState extends ConsumerState<AddressBookView> {
child: Column(
children: [
...contacts
.where((element) => element.addresses
.where((element) => element.addressesSorted
.where((e) => ref.watch(
addressBookFilterProvider.select((value) =>
value.coins.contains(e.coin))))

View file

@ -211,7 +211,8 @@ class _AddNewContactAddressViewState
const Duration(milliseconds: 75),
);
}
List<ContactAddressEntry> entries = contact.addresses;
List<ContactAddressEntry> entries =
contact.addresses.toList();
entries.add(ref
.read(addressEntryDataProvider(0))

View file

@ -341,7 +341,7 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> {
padding: const EdgeInsets.all(0),
child: Column(
children: [
..._contact.addresses.map(
..._contact.addressesSorted.map(
(e) => Padding(
padding: const EdgeInsets.all(12),
child: Row(

View file

@ -63,7 +63,7 @@ class ContactPopUp extends ConsumerWidget {
bool isExchangeFlow =
ref.watch(exchangeFlowIsActiveStateProvider.state).state;
final addresses = contact.addresses.where((e) {
final addresses = contact.addressesSorted.where((e) {
if (hasActiveWallet && !isExchangeFlow) {
return e.coin == active[0].coin;
} else {

View file

@ -9,9 +9,11 @@
*/
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/buy/response_objects/order.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/themes/theme_providers.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
@ -21,7 +23,7 @@ import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class BuyOrderDetailsView extends StatefulWidget {
class BuyOrderDetailsView extends ConsumerStatefulWidget {
const BuyOrderDetailsView({
Key? key,
required this.order,
@ -32,10 +34,11 @@ class BuyOrderDetailsView extends StatefulWidget {
static const String routeName = "/buyOrderDetails";
@override
State<BuyOrderDetailsView> createState() => _BuyOrderDetailsViewState();
ConsumerState<BuyOrderDetailsView> createState() =>
_BuyOrderDetailsViewState();
}
class _BuyOrderDetailsViewState extends State<BuyOrderDetailsView> {
class _BuyOrderDetailsViewState extends ConsumerState<BuyOrderDetailsView> {
final isDesktop = Util.isDesktop;
@override
@ -245,7 +248,9 @@ class _BuyOrderDetailsViewState extends State<BuyOrderDetailsView> {
width: 64,
height: 32,
child: SvgPicture.asset(
Assets.buy.simplexLogo(context),
Assets.buy.simplexLogo(
ref.watch(themeProvider).brightness,
),
),
),
],

View file

@ -11,11 +11,13 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:intl/intl.dart';
import 'package:stackwallet/models/buy/response_objects/quote.dart';
import 'package:stackwallet/pages/buy_view/sub_widgets/buy_warning_popup.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/themes/theme_providers.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
@ -25,7 +27,7 @@ import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class BuyQuotePreviewView extends StatefulWidget {
class BuyQuotePreviewView extends ConsumerStatefulWidget {
const BuyQuotePreviewView({
Key? key,
required this.quote,
@ -36,10 +38,11 @@ class BuyQuotePreviewView extends StatefulWidget {
static const String routeName = "/buyQuotePreview";
@override
State<BuyQuotePreviewView> createState() => _BuyQuotePreviewViewState();
ConsumerState<BuyQuotePreviewView> createState() =>
_BuyQuotePreviewViewState();
}
class _BuyQuotePreviewViewState extends State<BuyQuotePreviewView> {
class _BuyQuotePreviewViewState extends ConsumerState<BuyQuotePreviewView> {
final isDesktop = Util.isDesktop;
Future<void> _buyWarning() async {
@ -222,7 +225,9 @@ class _BuyQuotePreviewViewState extends State<BuyQuotePreviewView> {
width: 64,
height: 32,
child: SvgPicture.asset(
Assets.buy.simplexLogo(context),
Assets.buy.simplexLogo(
ref.watch(themeProvider).brightness,
),
),
),
],

View file

@ -11,6 +11,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/buy/response_objects/order.dart';
import 'package:stackwallet/models/buy/response_objects/quote.dart';
@ -18,6 +19,7 @@ import 'package:stackwallet/pages/buy_view/buy_order_details.dart';
import 'package:stackwallet/services/buy/buy_response.dart';
import 'package:stackwallet/services/buy/simplex/simplex_api.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/themes/theme_providers.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
@ -28,7 +30,7 @@ import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
class BuyWarningPopup extends StatefulWidget {
class BuyWarningPopup extends ConsumerStatefulWidget {
const BuyWarningPopup({
Key? key,
required this.quote,
@ -37,10 +39,10 @@ class BuyWarningPopup extends StatefulWidget {
final SimplexQuote quote;
final SimplexOrder? order;
@override
State<BuyWarningPopup> createState() => _BuyWarningPopupState();
ConsumerState<BuyWarningPopup> createState() => _BuyWarningPopupState();
}
class _BuyWarningPopupState extends State<BuyWarningPopup> {
class _BuyWarningPopupState extends ConsumerState<BuyWarningPopup> {
late final bool isDesktop;
SimplexOrder? order;
@ -236,7 +238,9 @@ class _BuyWarningPopupState extends State<BuyWarningPopup> {
width: 64,
height: 32,
child: SvgPicture.asset(
Assets.buy.simplexLogo(context),
Assets.buy.simplexLogo(
ref.watch(themeProvider).brightness,
),
),
),
],
@ -291,7 +295,9 @@ class _BuyWarningPopupState extends State<BuyWarningPopup> {
width: 64,
height: 32,
child: SvgPicture.asset(
Assets.buy.simplexLogo(context),
Assets.buy.simplexLogo(
ref.watch(themeProvider).brightness,
),
),
),
);

View file

@ -35,6 +35,7 @@ import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount_unit.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -160,26 +161,15 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
if (value == null) {
return null;
}
try {
// wtf Dart?????
// This turns "99999999999999999999" into 100000000000000000000.0
// final numFromLocalised = NumberFormat.decimalPattern(
// ref.read(localeServiceChangeNotifierProvider).locale)
// .parse(value);
// return Decimal.tryParse(numFromLocalised.toString());
try {
return Decimal.parse(value);
} catch (_) {
try {
return Decimal.parse(value.replaceAll(",", "."));
} catch (_) {
rethrow;
}
}
} catch (_) {
return null;
}
return AmountUnit.normal
.tryParse(
value,
locale: ref.read(localeServiceChangeNotifierProvider).locale,
coin: Coin.bitcoin, // dummy value (not used due to override)
overrideWithDecimalPlacesFromString: true,
)
?.decimal;
}
Future<AggregateCurrency> _getAggregateCurrency(Currency currency) async {
@ -809,6 +799,14 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
// if (_swapLock) {
_sendController.text = ref.read(efSendAmountStringProvider);
// }
if (_sendFocusNode.hasFocus) {
_sendController.selection = TextSelection.fromPosition(
TextPosition(
offset: _sendController.text.length,
),
);
}
}
});
ref.listen(efSendAmountStringProvider, (previous, String next) {
@ -820,11 +818,19 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
? "-"
: ref.read(efReceiveAmountStringProvider);
// }
if (_receiveFocusNode.hasFocus) {
_receiveController.selection = TextSelection.fromPosition(
TextPosition(
offset: _receiveController.text.length,
),
);
}
}
});
ref.listen(efEstimateProvider.notifier, (previous, next) {
final estimate = (next as StateController<Estimate?>).state;
final estimate = (next).state;
if (ref.read(efReversedProvider)) {
updateSend(estimate);
} else {

View file

@ -22,6 +22,7 @@ import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_vi
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
@ -337,7 +338,11 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
child: Column(
children: [
QrImageView(
data: "${coin.uriScheme}:$receivingAddress",
data: AddressUtils.buildUriString(
coin,
receivingAddress,
{},
),
size: MediaQuery.of(context).size.width / 2,
foregroundColor: Theme.of(context)
.extension<StackColors>()!

View file

@ -468,15 +468,67 @@ class _ConfirmTransactionViewState
],
),
),
if (transactionInfo["fee"] is int &&
transactionInfo["vSize"] is int)
const SizedBox(
height: 12,
),
if (transactionInfo["fee"] is int &&
transactionInfo["vSize"] is int)
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"sats/vByte",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
"~${(transactionInfo["fee"] / transactionInfo["vSize"]).toInt()}",
style: STextStyles.itemSubtitle12(context),
),
],
),
),
if (coin == Coin.epicCash &&
(transactionInfo["onChainNote"] as String).isNotEmpty)
const SizedBox(
height: 12,
),
if (coin == Coin.epicCash &&
(transactionInfo["onChainNote"] as String).isNotEmpty)
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Note",
"On chain note",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
transactionInfo["onChainNote"] as String,
style: STextStyles.itemSubtitle12(context),
),
],
),
),
if ((transactionInfo["note"] as String).isNotEmpty)
const SizedBox(
height: 12,
),
if ((transactionInfo["note"] as String).isNotEmpty)
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
(coin == Coin.epicCash) ? "Local Note" : "Note",
style: STextStyles.smallMed12(context),
),
const SizedBox(
@ -906,6 +958,43 @@ class _ConfirmTransactionViewState
),
),
),
if (isDesktop &&
!widget.isPaynymTransaction &&
transactionInfo["fee"] is int &&
transactionInfo["vSize"] is int)
Padding(
padding: const EdgeInsets.only(
left: 32,
),
child: Text(
"sats/vByte",
style: STextStyles.desktopTextExtraExtraSmall(context),
),
),
if (isDesktop &&
!widget.isPaynymTransaction &&
transactionInfo["fee"] is int &&
transactionInfo["vSize"] is int)
Padding(
padding: const EdgeInsets.only(
top: 10,
left: 32,
right: 32,
),
child: RoundedContainer(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 18,
),
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
child: Text(
"~${(transactionInfo["fee"] / transactionInfo["vSize"]).toInt()}",
style: STextStyles.itemSubtitle(context),
),
),
),
if (!isDesktop) const Spacer(),
SizedBox(
height: isDesktop ? 23 : 12,

View file

@ -10,7 +10,6 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:bip47/bip47.dart';
import 'package:cw_core/monero_transaction_priority.dart';
@ -41,6 +40,7 @@ import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_unit.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
@ -56,6 +56,7 @@ import 'package:stackwallet/widgets/animated_text.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/fee_slider.dart';
import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
@ -100,12 +101,14 @@ class _SendViewState extends ConsumerState<SendView> {
late TextEditingController cryptoAmountController;
late TextEditingController baseAmountController;
late TextEditingController noteController;
late TextEditingController onChainNoteController;
late TextEditingController feeController;
late final SendViewAutoFillData? _data;
final _addressFocusNode = FocusNode();
final _noteFocusNode = FocusNode();
final _onChainNoteFocusNode = FocusNode();
final _cryptoFocus = FocusNode();
final _baseFocus = FocusNode();
@ -127,27 +130,11 @@ class _SendViewState extends ConsumerState<SendView> {
void _cryptoAmountChanged() async {
if (!_cryptoAmountChangeLock) {
String cryptoAmount = cryptoAmountController.text;
if (cryptoAmount.isNotEmpty &&
cryptoAmount != "." &&
cryptoAmount != ",") {
if (cryptoAmount.startsWith("~")) {
cryptoAmount = cryptoAmount.substring(1);
}
if (cryptoAmount.contains(" ")) {
cryptoAmount = cryptoAmount.split(" ").first;
}
// ensure we don't shift past minimum atomic value
final shift = min(ref.read(pAmountUnit(coin)).shift, coin.decimals);
_amountToSend = cryptoAmount.contains(",")
? Decimal.parse(cryptoAmount.replaceFirst(",", "."))
.shift(0 - shift)
.toAmount(fractionDigits: coin.decimals)
: Decimal.parse(cryptoAmount)
.shift(0 - shift)
.toAmount(fractionDigits: coin.decimals);
final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse(
cryptoAmountController.text,
);
if (cryptoAmount != null) {
_amountToSend = cryptoAmount;
if (_cachedAmountToSend != null &&
_cachedAmountToSend == _amountToSend) {
return;
@ -300,6 +287,8 @@ class _SendViewState extends ConsumerState<SendView> {
case FeeRateType.slow:
feeRate = feeObject.slow;
break;
default:
feeRate = -1;
}
Amount fee;
@ -315,6 +304,8 @@ class _SendViewState extends ConsumerState<SendView> {
case FeeRateType.slow:
specialMoneroId = MoneroTransactionPriority.slow;
break;
default:
throw ArgumentError("custom fee not available for monero");
}
fee = await manager.estimateFeeFor(amount, specialMoneroId.raw!);
@ -510,6 +501,7 @@ class _SendViewState extends ConsumerState<SendView> {
isSegwit: widget.accountLite!.segwit,
amount: amount,
args: {
"satsPerVByte": isCustomFee ? customFeeRate : null,
"feeRate": feeRate,
"UTXOs": (manager.hasCoinControlSupport &&
coinControlEnabled &&
@ -524,7 +516,10 @@ class _SendViewState extends ConsumerState<SendView> {
txDataFuture = (manager.wallet as FiroWallet).prepareSendPublic(
address: _address!,
amount: amount,
args: {"feeRate": ref.read(feeRateTypeStateProvider)},
args: {
"feeRate": ref.read(feeRateTypeStateProvider),
"satsPerVByte": isCustomFee ? customFeeRate : null,
},
);
} else {
txDataFuture = manager.prepareSend(
@ -532,6 +527,7 @@ class _SendViewState extends ConsumerState<SendView> {
amount: amount,
args: {
"feeRate": ref.read(feeRateTypeStateProvider),
"satsPerVByte": isCustomFee ? customFeeRate : null,
"UTXOs": (manager.hasCoinControlSupport &&
coinControlEnabled &&
selectedUTXOs.isNotEmpty)
@ -552,6 +548,7 @@ class _SendViewState extends ConsumerState<SendView> {
// pop building dialog
Navigator.of(context).pop();
txData["note"] = noteController.text;
txData["onChainNote"] = onChainNoteController.text;
if (isPaynymSend) {
txData["paynymAccountLite"] = widget.accountLite!;
} else {
@ -609,6 +606,10 @@ class _SendViewState extends ConsumerState<SendView> {
bool get isPaynymSend => widget.accountLite != null;
bool isCustomFee = false;
int customFeeRate = 1;
@override
void initState() {
coin = widget.coin;
@ -626,6 +627,7 @@ class _SendViewState extends ConsumerState<SendView> {
cryptoAmountController = TextEditingController();
baseAmountController = TextEditingController();
noteController = TextEditingController();
onChainNoteController = TextEditingController();
feeController = TextEditingController();
onCryptoAmountChanged = _cryptoAmountChanged;
@ -700,9 +702,11 @@ class _SendViewState extends ConsumerState<SendView> {
cryptoAmountController.dispose();
baseAmountController.dispose();
noteController.dispose();
onChainNoteController.dispose();
feeController.dispose();
_noteFocusNode.dispose();
_onChainNoteFocusNode.dispose();
_addressFocusNode.dispose();
_cryptoFocus.dispose();
_baseFocus.dispose();
@ -1553,13 +1557,21 @@ class _SendViewState extends ConsumerState<SendView> {
),
textAlign: TextAlign.right,
inputFormatters: [
AmountInputFormatter(
decimals: coin.decimals,
unit: ref.watch(pAmountUnit(coin)),
locale: locale,
),
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue,
newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
// TextInputFormatter.withFunction((oldValue,
// newValue) =>
// // RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// // RegExp(r'^\d{1,3}([,\.]\d+)?|[,\.\d]+$')
// getAmountRegex(locale, coin.decimals)
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
@ -1614,26 +1626,25 @@ class _SendViewState extends ConsumerState<SendView> {
),
textAlign: TextAlign.right,
inputFormatters: [
AmountInputFormatter(
decimals: 2,
locale: locale,
),
// regex to validate a fiat amount with 2 decimal places
TextInputFormatter.withFunction((oldValue,
newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
// TextInputFormatter.withFunction((oldValue,
// newValue) =>
// // RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
// getAmountRegex(locale, 2)
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
onChanged: (baseAmountString) {
if (baseAmountString.isNotEmpty &&
baseAmountString != "." &&
baseAmountString != ",") {
final Amount baseAmount =
baseAmountString.contains(",")
? Decimal.parse(baseAmountString
.replaceFirst(",", "."))
.toAmount(fractionDigits: 2)
: Decimal.parse(baseAmountString)
.toAmount(fractionDigits: 2);
final baseAmount = Amount.tryParseFiatString(
baseAmountString,
locale: locale,
);
if (baseAmount != null) {
final Decimal _price = ref
.read(priceAnd24hChangeNotifierProvider)
.getPrice(coin)
@ -1789,8 +1800,64 @@ class _SendViewState extends ConsumerState<SendView> {
const SizedBox(
height: 12,
),
if (coin == Coin.epicCash)
Text(
"Note (optional)",
"On chain Note (optional)",
style: STextStyles.smallMed12(context),
textAlign: TextAlign.left,
),
if (coin == Coin.epicCash)
const SizedBox(
height: 8,
),
if (coin == Coin.epicCash)
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
maxLength: 256,
controller: onChainNoteController,
focusNode: _onChainNoteFocusNode,
style: STextStyles.field(context),
onChanged: (_) => setState(() {}),
decoration: standardInputDecoration(
"Type something...",
_onChainNoteFocusNode,
context,
).copyWith(
suffixIcon: onChainNoteController.text.isNotEmpty
? Padding(
padding:
const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
onChainNoteController.text = "";
});
},
),
],
),
),
)
: null,
),
),
),
if (coin == Coin.epicCash)
const SizedBox(
height: 12,
),
Text(
(coin == Coin.epicCash) ? "Local Note (optional)"
: "Note (optional)",
style: STextStyles.smallMed12(context),
textAlign: TextAlign.left,
),
@ -1839,17 +1906,23 @@ class _SendViewState extends ConsumerState<SendView> {
const SizedBox(
height: 12,
),
if (coin != Coin.epicCash)
if (coin != Coin.epicCash &&
coin != Coin.nano &&
coin != Coin.banano)
Text(
"Transaction fee (estimated)",
style: STextStyles.smallMed12(context),
textAlign: TextAlign.left,
),
if (coin != Coin.epicCash)
if (coin != Coin.epicCash &&
coin != Coin.nano &&
coin != Coin.banano)
const SizedBox(
height: 8,
),
if (coin != Coin.epicCash)
if (coin != Coin.epicCash &&
coin != Coin.nano &&
coin != Coin.banano)
Stack(
children: [
TextField(
@ -1907,6 +1980,15 @@ class _SendViewState extends ConsumerState<SendView> {
fractionDigits: coin.decimals,
),
updateChosen: (String fee) {
if (fee == "custom") {
if (!isCustomFee) {
setState(() {
isCustomFee = true;
});
}
return;
}
_setCurrentFee(
fee,
true,
@ -1914,6 +1996,9 @@ class _SendViewState extends ConsumerState<SendView> {
setState(() {
_calculateFeesFuture =
Future(() => fee);
if (isCustomFee) {
isCustomFee = false;
}
});
},
),
@ -1937,11 +2022,11 @@ class _SendViewState extends ConsumerState<SendView> {
.done &&
snapshot.hasData) {
_setCurrentFee(
snapshot.data! as String,
snapshot.data!,
false,
);
return Text(
"~${snapshot.data! as String}",
"~${snapshot.data!}",
style: STextStyles
.itemSubtitle(
context),
@ -1993,12 +2078,13 @@ class _SendViewState extends ConsumerState<SendView> {
.done &&
snapshot.hasData) {
_setCurrentFee(
snapshot.data!
as String,
snapshot.data!,
false,
);
return Text(
"~${snapshot.data! as String}",
isCustomFee
? ""
: "~${snapshot.data!}",
style: STextStyles
.itemSubtitle(
context),
@ -2034,6 +2120,19 @@ class _SendViewState extends ConsumerState<SendView> {
)
],
),
if (isCustomFee)
Padding(
padding: const EdgeInsets.only(
bottom: 12,
top: 16,
),
child: FeeSlider(
coin: coin,
onSatVByteChanged: (rate) {
customFeeRate = rate;
},
),
),
const Spacer(),
const SizedBox(
height: 12,

View file

@ -161,6 +161,9 @@ class _TransactionFeeSelectionSheetState
}
}
return ref.read(feeSheetSessionCacheProvider).slow[amount]!;
default:
return Amount.zero;
}
}
@ -567,8 +570,6 @@ class _TransactionFeeSelectionSheetState
.watch(feeRateTypeStateProvider.state)
.state,
onChanged: (x) {
//todo: check if print needed
// debugPrint(x.toString());
ref
.read(feeRateTypeStateProvider.state)
.state = FeeRateType.slow;
@ -672,6 +673,79 @@ class _TransactionFeeSelectionSheetState
const SizedBox(
height: 24,
),
if (manager.coin.isElectrumXCoin)
GestureDetector(
onTap: () {
final state =
ref.read(feeRateTypeStateProvider.state).state;
if (state != FeeRateType.custom) {
ref.read(feeRateTypeStateProvider.state).state =
FeeRateType.custom;
}
widget.updateChosen("custom");
Navigator.of(context).pop();
},
child: Container(
color: Colors.transparent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
SizedBox(
width: 20,
height: 20,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: FeeRateType.custom,
groupValue: ref
.watch(feeRateTypeStateProvider.state)
.state,
onChanged: (x) {
ref
.read(
feeRateTypeStateProvider.state)
.state = FeeRateType.custom;
Navigator.of(context).pop();
},
),
),
],
),
const SizedBox(
width: 12,
),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
FeeRateType.custom.prettyName,
style:
STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
),
const SizedBox(
height: 2,
),
],
),
),
],
),
),
),
if (manager.coin.isElectrumXCoin)
const SizedBox(
height: 24,
),
],
);
},
@ -714,6 +788,8 @@ class _TransactionFeeSelectionSheetState
);
}
return null;
case FeeRateType.custom:
return null;
}
} catch (e, s) {
Logging.instance.log("$e $s", level: LogLevel.Warning);

View file

@ -31,6 +31,7 @@ import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
@ -218,16 +219,11 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
}
void _onFiatAmountFieldChanged(String baseAmountString) {
if (baseAmountString.isNotEmpty &&
baseAmountString != "." &&
baseAmountString != ",") {
final baseAmount = Amount.fromDecimal(
baseAmountString.contains(",")
? Decimal.parse(baseAmountString.replaceFirst(",", "."))
: Decimal.parse(baseAmountString),
fractionDigits: tokenContract.decimals,
final baseAmount = Amount.tryParseFiatString(
baseAmountString,
locale: ref.read(localeServiceChangeNotifierProvider).locale,
);
if (baseAmount != null) {
final _price = ref
.read(priceAnd24hChangeNotifierProvider)
.getTokenPrice(tokenContract.address)
@ -272,22 +268,12 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
void _cryptoAmountChanged() async {
if (!_cryptoAmountChangeLock) {
String cryptoAmount = cryptoAmountController.text;
if (cryptoAmount.isNotEmpty &&
cryptoAmount != "." &&
cryptoAmount != ",") {
if (cryptoAmount.startsWith("~")) {
cryptoAmount = cryptoAmount.substring(1);
}
if (cryptoAmount.contains(" ")) {
cryptoAmount = cryptoAmount.split(" ").first;
}
_amountToSend = Amount.fromDecimal(
cryptoAmount.contains(",")
? Decimal.parse(cryptoAmount.replaceFirst(",", "."))
: Decimal.parse(cryptoAmount),
fractionDigits: tokenContract.decimals);
final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse(
cryptoAmountController.text,
ethContract: tokenContract,
);
if (cryptoAmount != null) {
_amountToSend = cryptoAmount;
if (_cachedAmountToSend != null &&
_cachedAmountToSend == _amountToSend) {
return;
@ -374,6 +360,8 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
case FeeRateType.slow:
feeRate = feeObject.slow;
break;
default:
feeRate = -1;
}
final Amount fee = wallet.estimateFeeFor(feeRate);
@ -950,13 +938,18 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
),
textAlign: TextAlign.right,
inputFormatters: [
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue,
newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: tokenContract.decimals,
unit: ref.watch(pAmountUnit(coin)),
locale: locale,
),
// // regex to validate a crypto amount with 8 decimal places
// TextInputFormatter.withFunction((oldValue,
// newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
@ -1009,13 +1002,17 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
),
textAlign: TextAlign.right,
inputFormatters: [
// regex to validate a fiat amount with 2 decimal places
TextInputFormatter.withFunction((oldValue,
newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: 2,
locale: locale,
),
// // regex to validate a fiat amount with 2 decimal places
// TextInputFormatter.withFunction((oldValue,
// newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
onChanged: _onFiatAmountFieldChanged,
decoration: InputDecoration(
@ -1185,7 +1182,7 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
ConnectionState.done &&
snapshot.hasData) {
return Text(
"~${snapshot.data! as String}",
"~${snapshot.data!}",
style:
STextStyles.itemSubtitle(
context),

View file

@ -126,7 +126,7 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
}
Future<String> getThemeDirectorySize() async {
final themesDir = await StackFileSystem.applicationThemesDirectory();
final themesDir = StackFileSystem.themesDir!;
final themeDir = Directory("${themesDir.path}/${widget.data.id}");
int bytes = 0;
if (await themeDir.exists()) {

View file

@ -9,9 +9,9 @@
*/
import 'package:flutter/material.dart';
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/pages/intro_view.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/delete_everything.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
@ -36,7 +36,7 @@ class _DeleteAccountViewState extends State<DeleteAccountView> {
Future<void> onConfirmDeleteAccount() async {
// TODO delete everything then pop to intro view
await showDialog(
await showDialog<void>(
barrierDismissible: true,
context: context,
builder: (_) => StackDialog(
@ -61,12 +61,14 @@ class _DeleteAccountViewState extends State<DeleteAccountView> {
.extension<StackColors>()!
.getPrimaryEnabledButtonStyle(context),
onPressed: () async {
await deleteEverything();
await DB.instance.deleteEverything();
if (mounted) {
await Navigator.of(context).pushNamedAndRemoveUntil(
IntroView.routeName,
(route) => false,
);
}
},
child: Text(
"Delete",
@ -82,7 +84,7 @@ class _DeleteAccountViewState extends State<DeleteAccountView> {
return MasterScaffold(
isDesktop: isDesktop,
appBar: isDesktop
? DesktopAppBar(isCompactHeight: true)
? const DesktopAppBar(isCompactHeight: true)
: AppBar(
leading: AppBarBackButton(
onPressed: () async {

View file

@ -52,6 +52,7 @@ class WalletSummary extends StatelessWidget {
walletId: walletId,
width: constraints.maxWidth,
height: constraints.maxHeight,
isFavorite: false,
),
Positioned.fill(
child: Padding(

View file

@ -358,6 +358,8 @@ class _TransactionDetailsViewState
final currentHeight = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId).currentHeight));
print("THIS TRANSACTION IS $_transaction");
return ConditionalParent(
condition: !isDesktop,
builder: (child) => Background(
@ -471,7 +473,9 @@ class _TransactionDetailsViewState
),
SelectableText(
_transaction.isCancelled
? "Cancelled"
? coin == Coin.ethereum
? "Failed"
: "Cancelled"
: whatIsIt(
_transaction,
currentHeight,
@ -582,7 +586,9 @@ class _TransactionDetailsViewState
// child:
SelectableText(
_transaction.isCancelled
? "Cancelled"
? coin == Coin.ethereum
? "Failed"
: "Cancelled"
: whatIsIt(
_transaction,
currentHeight,
@ -774,12 +780,69 @@ class _TransactionDetailsViewState
],
),
),
if (coin == Coin.epicCash)
isDesktop
? const _Divider()
: const SizedBox(
height: 12,
),
if (coin == Coin.epicCash)
RoundedWhiteContainer(
padding: isDesktop
? const EdgeInsets.all(16)
: const EdgeInsets.all(12),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"On chain note",
style: isDesktop
? STextStyles
.desktopTextExtraExtraSmall(
context)
: STextStyles.itemSubtitle(
context),
),
const SizedBox(
height: 8,
),
SelectableText(
_transaction.otherData ?? "",
style: isDesktop
? STextStyles
.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<
StackColors>()!
.textDark,
)
: STextStyles.itemSubtitle12(
context),
),
],
),
),
if (isDesktop)
IconCopyButton(
data: _transaction.address.value!.value,
),
],
),
),
isDesktop
? const _Divider()
: const SizedBox(
height: 12,
),
RoundedWhiteContainer(
padding: isDesktop
? const EdgeInsets.all(16)
@ -792,7 +855,9 @@ class _TransactionDetailsViewState
MainAxisAlignment.spaceBetween,
children: [
Text(
"Note",
(coin == Coin.epicCash)
? "Local Note"
: "Note ",
style: isDesktop
? STextStyles
.desktopTextExtraExtraSmall(
@ -861,7 +926,9 @@ class _TransactionDetailsViewState
notesServiceChangeNotifierProvider(
walletId)
.select((value) => value.getNoteFor(
txid: _transaction.txid))),
txid: (coin == Coin.epicCash)
? _transaction.slateId!
: _transaction.txid))),
builder: (builderContext,
AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState ==

View file

@ -10,16 +10,17 @@
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_rounded_date_picker/flutter_rounded_date_picker.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/transaction_filter.dart';
import 'package:stackwallet/providers/global/locale_provider.dart';
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/themes/theme_providers.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -757,12 +758,20 @@ class _TransactionSearchViewState
decimal: true,
),
inputFormatters: [
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue, newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: widget.coin.decimals,
unit: ref.watch(pAmountUnit(widget.coin)),
locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
),
// // regex to validate a crypto amount with 8 decimal places
// TextInputFormatter.withFunction((oldValue, newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
style: isDesktop
? STextStyles.desktopTextExtraSmall(context).copyWith(

View file

@ -680,6 +680,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
padding: const EdgeInsets.symmetric(horizontal: 16),
child: WalletSummary(
walletId: walletId,
aspectRatio: 1.75,
initialSyncStatus: ref.watch(managerProvider
.select((value) => value.isRefreshing))
? WalletSyncStatus.syncing

View file

@ -149,6 +149,7 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
walletId: widget.walletId,
width: widget.width,
height: widget.height,
isFavorite: true,
),
child: Padding(
padding: const EdgeInsets.all(12.0),

View file

@ -8,6 +8,8 @@
*
*/
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -43,8 +45,10 @@ class DesktopAddressCard extends ConsumerWidget {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SvgPicture.asset(
SvgPicture.file(
File(
ref.watch(coinIconProvider(entry.coin)),
),
height: 32,
width: 32,
),

View file

@ -53,15 +53,6 @@ class DesktopContactDetails extends ConsumerStatefulWidget {
class _DesktopContactDetailsState extends ConsumerState<DesktopContactDetails> {
List<Tuple2<String, Transaction>> _cachedTransactions = [];
bool _contactHasAddress(String address, ContactEntry contact) {
for (final entry in contact.addresses) {
if (entry.address == address) {
return true;
}
}
return false;
}
Future<List<Tuple2<String, Transaction>>> _filteredTransactionsByContact(
List<Manager> managers,
) async {
@ -259,7 +250,9 @@ class _DesktopContactDetailsState extends ConsumerState<DesktopContactDetails> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
for (int i = 0; i < contact.addresses.length; i++)
for (int i = 0;
i < contact.addressesSorted.length;
i++)
Column(
mainAxisSize: MainAxisSize.min,
children: [
@ -273,7 +266,7 @@ class _DesktopContactDetailsState extends ConsumerState<DesktopContactDetails> {
Padding(
padding: const EdgeInsets.all(18),
child: DesktopAddressCard(
entry: contact.addresses[i],
entry: contact.addressesSorted[i],
contactId: contact.customId,
),
),

View file

@ -69,8 +69,8 @@ class _AddressBookAddressChooserState extends State<AddressBookAddressChooser> {
List<ContactEntry> filter(List<ContactEntry> contacts, String searchTerm) {
if (widget.coin != null) {
contacts.removeWhere(
(e) => e.addresses.where((a) => a.coin == widget.coin!).isEmpty);
contacts.removeWhere((e) =>
e.addressesSorted.where((a) => a.coin == widget.coin!).isEmpty);
}
contacts.retainWhere((e) => _matches(searchTerm, e));

View file

@ -78,7 +78,7 @@ class _ContactListItemState extends ConsumerState<ContactListItem> {
mainAxisSize: MainAxisSize.min,
children: [
// filter addresses by coin is provided before building address list
...contact.addresses
...contact.addressesSorted
.where((e) =>
filterByCoin != null ? e.coin == filterByCoin! : true)
.map(

View file

@ -179,6 +179,8 @@ class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.slow[amount]!;
default:
return Amount.zero;
}
}

View file

@ -21,6 +21,7 @@ import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
@ -236,7 +237,11 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
),
Center(
child: QrImageView(
data: "${coin.uriScheme}:$receivingAddress",
data: AddressUtils.buildUriString(
coin,
receivingAddress,
{},
),
size: 200,
foregroundColor:
Theme.of(context).extension<StackColors>()!.accentColorDark,

View file

@ -9,16 +9,16 @@
*/
import 'dart:async';
import 'dart:math';
import 'package:bip47/bip47.dart';
import 'package:cw_core/monero_transaction_priority.dart';
import 'package:decimal/decimal.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/models/contact_address_entry.dart';
import 'package:stackwallet/models/isar/models/contact_entry.dart';
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
@ -39,22 +39,27 @@ import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_unit.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/animated_text.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_fee_dialog.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/fee_slider.dart';
import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
@ -115,6 +120,17 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
bool get isPaynymSend => widget.accountLite != null;
bool isCustomFee = false;
int customFeeRate = 1;
(FeeRateType, String?, String?)? feeSelectionResult;
final stringsToLoopThrough = [
"Calculating",
"Calculating.",
"Calculating..",
"Calculating...",
];
Future<void> previewSend() async {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
@ -283,6 +299,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
isSegwit: widget.accountLite!.segwit,
amount: amount,
args: {
"satsPerVByte": isCustomFee ? customFeeRate : null,
"feeRate": feeRate,
"UTXOs": (manager.hasCoinControlSupport &&
coinControlEnabled &&
@ -299,6 +316,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
amount: amount,
args: {
"feeRate": ref.read(feeRateTypeStateProvider),
"satsPerVByte": isCustomFee ? customFeeRate : null,
"UTXOs": (manager.hasCoinControlSupport &&
coinControlEnabled &&
ref.read(desktopUseUTXOs).isNotEmpty)
@ -312,6 +330,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
amount: amount,
args: {
"feeRate": ref.read(feeRateTypeStateProvider),
"satsPerVByte": isCustomFee ? customFeeRate : null,
"UTXOs": (manager.hasCoinControlSupport &&
coinControlEnabled &&
ref.read(desktopUseUTXOs).isNotEmpty)
@ -442,27 +461,11 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
void _cryptoAmountChanged() async {
if (!_cryptoAmountChangeLock) {
String cryptoAmount = cryptoAmountController.text;
if (cryptoAmount.isNotEmpty &&
cryptoAmount != "." &&
cryptoAmount != ",") {
if (cryptoAmount.startsWith("~")) {
cryptoAmount = cryptoAmount.substring(1);
}
if (cryptoAmount.contains(" ")) {
cryptoAmount = cryptoAmount.split(" ").first;
}
// ensure we don't shift past minimum atomic value
final shift = min(ref.read(pAmountUnit(coin)).shift, coin.decimals);
_amountToSend = cryptoAmount.contains(",")
? Decimal.parse(cryptoAmount.replaceFirst(",", "."))
.shift(0 - shift)
.toAmount(fractionDigits: coin.decimals)
: Decimal.parse(cryptoAmount)
.shift(0 - shift)
.toAmount(fractionDigits: coin.decimals);
final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse(
cryptoAmountController.text,
);
if (cryptoAmount != null) {
_amountToSend = cryptoAmount;
if (_cachedAmountToSend != null &&
_cachedAmountToSend == _amountToSend) {
return;
@ -561,12 +564,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
);
} else {
return AnimatedText(
stringsToLoopThrough: const [
"Loading balance",
"Loading balance.",
"Loading balance..",
"Loading balance...",
],
stringsToLoopThrough: stringsToLoopThrough,
style: STextStyles.itemSubtitle(context),
);
}
@ -662,15 +660,12 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
}
void fiatTextFieldOnChanged(String baseAmountString) {
if (baseAmountString.isNotEmpty &&
baseAmountString != "." &&
baseAmountString != ",") {
final baseAmount = baseAmountString.contains(",")
? Decimal.parse(baseAmountString.replaceFirst(",", "."))
.toAmount(fractionDigits: 2)
: Decimal.parse(baseAmountString).toAmount(fractionDigits: 2);
var _price =
final baseAmount = Amount.tryParseFiatString(
baseAmountString,
locale: ref.read(localeServiceChangeNotifierProvider).locale,
);
if (baseAmount != null) {
final _price =
ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1;
if (_price == Decimal.zero) {
@ -1040,12 +1035,17 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
),
textAlign: TextAlign.right,
inputFormatters: [
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue, newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: coin.decimals,
unit: ref.watch(pAmountUnit(coin)),
locale: locale,
),
// // regex to validate a crypto amount with 8 decimal places
// TextInputFormatter.withFunction((oldValue, newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
onChanged: (newValue) {},
decoration: InputDecoration(
@ -1097,12 +1097,16 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
),
textAlign: TextAlign.right,
inputFormatters: [
// regex to validate a fiat amount with 2 decimal places
TextInputFormatter.withFunction((oldValue, newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: 2,
locale: locale,
),
// // regex to validate a fiat amount with 2 decimal places
// TextInputFormatter.withFunction((oldValue, newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
onChanged: fiatTextFieldOnChanged,
decoration: InputDecoration(
@ -1359,80 +1363,53 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
}
},
),
// const SizedBox(
// height: 20,
// ),
// Text(
// "Note (optional)",
// style: STextStyles.desktopTextExtraSmall(context).copyWith(
// color: Theme.of(context)
// .extension<StackColors>()!
// .textFieldActiveSearchIconRight,
// ),
// textAlign: TextAlign.left,
// ),
// const SizedBox(
// height: 10,
// ),
// ClipRRect(
// borderRadius: BorderRadius.circular(
// Constants.size.circularBorderRadius,
// ),
// child: TextField(
// minLines: 1,
// maxLines: 5,
// autocorrect: Util.isDesktop ? false : true,
// enableSuggestions: Util.isDesktop ? false : true,
// controller: noteController,
// focusNode: _noteFocusNode,
// style: STextStyles.desktopTextExtraSmall(context).copyWith(
// color: Theme.of(context)
// .extension<StackColors>()!
// .textFieldActiveText,
// height: 1.8,
// ),
// onChanged: (_) => setState(() {}),
// decoration: standardInputDecoration(
// "Type something...",
// _noteFocusNode,
// context,
// desktopMed: true,
// ).copyWith(
// contentPadding: const EdgeInsets.only(
// left: 16,
// top: 11,
// bottom: 12,
// right: 5,
// ),
// suffixIcon: noteController.text.isNotEmpty
// ? Padding(
// padding: const EdgeInsets.only(right: 0),
// child: UnconstrainedBox(
// child: Row(
// children: [
// TextFieldIconButton(
// child: const XIcon(),
// onTap: () async {
// setState(() {
// noteController.text = "";
// });
// },
// ),
// ],
// ),
// ),
// )
// : null,
// ),
// ),
// ),
if (!isPaynymSend)
const SizedBox(
height: 20,
),
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin)))
Text(
"Transaction fee (${coin == Coin.ethereum ? "max" : "estimated"})",
ConditionalParent(
condition: coin.isElectrumXCoin &&
!(((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state ==
"Private")),
builder: (child) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
child,
CustomTextButton(
text: "Edit",
onTap: () async {
feeSelectionResult = await showDialog<
(
FeeRateType,
String?,
String?,
)?>(
context: context,
builder: (_) => DesktopFeeDialog(
walletId: walletId,
),
);
if (feeSelectionResult != null) {
if (isCustomFee &&
feeSelectionResult!.$1 != FeeRateType.custom) {
isCustomFee = false;
} else if (!isCustomFee &&
feeSelectionResult!.$1 == FeeRateType.custom) {
isCustomFee = true;
}
}
setState(() {});
},
),
],
),
child: Text(
"Transaction fee"
"${isCustomFee ? "" : " (${coin == Coin.ethereum ? "max" : "estimated"})"}",
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
@ -1440,13 +1417,158 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
),
textAlign: TextAlign.left,
),
),
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin)))
const SizedBox(
height: 10,
),
if (!([Coin.nano, Coin.banano, Coin.epicCash].contains(coin)))
DesktopFeeDropDown(
if (!isCustomFee)
Padding(
padding: const EdgeInsets.all(10),
child: (feeSelectionResult?.$2 == null)
? FutureBuilder(
future: ref.watch(
walletsChangeNotifierProvider.select(
(value) => value.getManager(walletId).fees,
),
),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return DesktopFeeItem(
feeObject: snapshot.data,
feeRateType: FeeRateType.average,
walletId: walletId,
isButton: false,
feeFor: ({
required Amount amount,
required FeeRateType feeRateType,
required int feeRate,
required Coin coin,
}) async {
if (ref
.read(feeSheetSessionCacheProvider)
.average[amount] ==
null) {
final manager = ref
.read(walletsChangeNotifierProvider)
.getManager(walletId);
if (coin == Coin.monero ||
coin == Coin.wownero) {
final fee = await manager.estimateFeeFor(
amount,
MoneroTransactionPriority.regular.raw!);
ref
.read(feeSheetSessionCacheProvider)
.average[amount] = fee;
} else if ((coin == Coin.firo ||
coin == Coin.firoTestNet) &&
ref
.read(
publicPrivateBalanceStateProvider
.state)
.state !=
"Private") {
ref
.read(feeSheetSessionCacheProvider)
.average[amount] = await (manager.wallet
as FiroWallet)
.estimateFeeForPublic(amount, feeRate);
} else {
ref
.read(feeSheetSessionCacheProvider)
.average[amount] =
await manager.estimateFeeFor(
amount, feeRate);
}
}
return ref
.read(feeSheetSessionCacheProvider)
.average[amount]!;
},
isSelected: true,
);
} else {
return Row(
children: [
AnimatedText(
stringsToLoopThrough: stringsToLoopThrough,
style: STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
),
],
);
}
},
)
: (coin == Coin.firo || coin == Coin.firoTestNet) &&
ref
.watch(
publicPrivateBalanceStateProvider.state)
.state ==
"Private"
? Text(
"~${ref.watch(pAmountFormatter(coin)).format(
Amount(
rawValue: BigInt.parse("3794"),
fractionDigits: coin.decimals,
),
indicatePrecisionLoss: false,
)}",
style: STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
textAlign: TextAlign.left,
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
feeSelectionResult?.$2 ?? "",
style: STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
textAlign: TextAlign.left,
),
Text(
feeSelectionResult?.$3 ?? "",
style: STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
),
),
],
),
),
if (isCustomFee)
Padding(
padding: const EdgeInsets.only(
bottom: 12,
top: 16,
),
child: FeeSlider(
coin: coin,
onSatVByteChanged: (rate) {
customFeeRate = rate;
},
),
),
const SizedBox(
height: 36,

View file

@ -14,7 +14,7 @@ import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/contact_address_entry.dart';
import 'package:stackwallet/models/isar/models/contact_entry.dart';
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
@ -31,6 +31,7 @@ import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
@ -50,7 +51,7 @@ import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
const _kCryptoAmountRegex = r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
// const _kCryptoAmountRegex = r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
class DesktopTokenSend extends ConsumerStatefulWidget {
const DesktopTokenSend({
@ -717,15 +718,23 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
),
textAlign: TextAlign.right,
inputFormatters: [
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue, newValue) => RegExp(
_kCryptoAmountRegex.replaceAll(
"0,8",
"0,${tokenContract.decimals}",
AmountInputFormatter(
decimals: tokenContract.decimals,
unit: ref.watch(pAmountUnit(coin)),
locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
).hasMatch(newValue.text)
? newValue
: oldValue),
),
// regex to validate a crypto amount with 8 decimal places
// TextInputFormatter.withFunction((oldValue, newValue) => RegExp(
// _kCryptoAmountRegex.replaceAll(
// "0,8",
// "0,${tokenContract.decimals}",
// ),
// ).hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
onChanged: (newValue) {},
decoration: InputDecoration(
@ -777,12 +786,19 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
),
textAlign: TextAlign.right,
inputFormatters: [
// regex to validate a fiat amount with 2 decimal places
TextInputFormatter.withFunction((oldValue, newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: 2,
locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
),
// // regex to validate a fiat amount with 2 decimal places
// TextInputFormatter.withFunction((oldValue, newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
onChanged: fiatTextFieldOnChanged,
decoration: InputDecoration(

View file

@ -15,7 +15,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart';
@ -213,8 +212,8 @@ class _ForgottenPassphraseRestoreFromSWBState
await (ref.read(secureStoreProvider).store as DesktopSecureStore)
.close();
ref.refresh(secureStoreProvider);
await ref.read(storageCryptoHandlerProvider).deleteBox();
ref.refresh(storageCryptoHandlerProvider);
await Hive.deleteBoxFromDisk(DB.boxNameDesktopData);
await DB.instance.init();
if (mounted) {
Navigator.of(context)

View file

@ -83,10 +83,10 @@ class _DesktopSettingsViewState extends ConsumerState<DesktopSettingsView> {
Widget build(BuildContext context) {
return DesktopScaffold(
background: Theme.of(context).extension<StackColors>()!.background,
appBar: DesktopAppBar(
appBar: const DesktopAppBar(
isCompactHeight: true,
leading: Row(
children: const [
children: [
SizedBox(
width: 24,
height: 24,
@ -97,7 +97,10 @@ class _DesktopSettingsViewState extends ConsumerState<DesktopSettingsView> {
),
body: Row(
children: [
const SettingsMenu(),
const Padding(
padding: EdgeInsets.all(15.0),
child: SettingsMenu(),
),
Expanded(
child: contentViews[
ref.watch(selectedSettingsMenuItemStateProvider.state).state],

View file

@ -45,10 +45,10 @@ class _SettingsMenuState extends ConsumerState<SettingsMenu> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 15),
SizedBox(
width: 250,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (int i = 0; i < labels.length; i++)
Column(
@ -83,157 +83,7 @@ class _SettingsMenuState extends ConsumerState<SettingsMenu> {
.state = newValue,
),
],
)
// SettingsMenuItem(
// icon: SvgPicture.asset(
// Assets.svg.polygon,
// width: 11,
// height: 11,
// color: selectedMenuItem == 0
// ? Theme.of(context)
// .extension<StackColors>()!
// .accentColorBlue
// : Colors.transparent,
// ),
// label: "Backup and restore",
// value: 0,
// group: selectedMenuItem,
// onChanged: updateSelectedMenuItem,
// ),
// const SizedBox(
// height: 2,
// ),
// SettingsMenuItem(
// icon: SvgPicture.asset(
// Assets.svg.polygon,
// width: 11,
// height: 11,
// color: selectedMenuItem == 1
// ? Theme.of(context)
// .extension<StackColors>()!
// .accentColorBlue
// : Colors.transparent,
// ),
// label: "Security",
// value: 1,
// group: selectedMenuItem,
// onChanged: updateSelectedMenuItem,
// ),
// const SizedBox(
// height: 2,
// ),
// SettingsMenuItem(
// icon: SvgPicture.asset(
// Assets.svg.polygon,
// width: 11,
// height: 11,
// color: selectedMenuItem == 2
// ? Theme.of(context)
// .extension<StackColors>()!
// .accentColorBlue
// : Colors.transparent,
// ),
// label: "Currency",
// value: 2,
// group: selectedMenuItem,
// onChanged: updateSelectedMenuItem,
// ),
// const SizedBox(
// height: 2,
// ),
// SettingsMenuItem(
// icon: SvgPicture.asset(
// Assets.svg.polygon,
// width: 11,
// height: 11,
// color: selectedMenuItem == 3
// ? Theme.of(context)
// .extension<StackColors>()!
// .accentColorBlue
// : Colors.transparent,
// ),
// label: "Language",
// value: 3,
// group: selectedMenuItem,
// onChanged: updateSelectedMenuItem,
// ),
// const SizedBox(
// height: 2,
// ),
// SettingsMenuItem(
// icon: SvgPicture.asset(
// Assets.svg.polygon,
// width: 11,
// height: 11,
// color: selectedMenuItem == 4
// ? Theme.of(context)
// .extension<StackColors>()!
// .accentColorBlue
// : Colors.transparent,
// ),
// label: "Nodes",
// value: 4,
// group: selectedMenuItem,
// onChanged: updateSelectedMenuItem,
// ),
// const SizedBox(
// height: 2,
// ),
// SettingsMenuItem(
// icon: SvgPicture.asset(
// Assets.svg.polygon,
// width: 11,
// height: 11,
// color: selectedMenuItem == 5
// ? Theme.of(context)
// .extension<StackColors>()!
// .accentColorBlue
// : Colors.transparent,
// ),
// label: "Syncing preferences",
// value: 5,
// group: selectedMenuItem,
// onChanged: updateSelectedMenuItem,
// ),
// const SizedBox(
// height: 2,
// ),
// SettingsMenuItem(
// icon: SvgPicture.asset(
// Assets.svg.polygon,
// width: 11,
// height: 11,
// color: selectedMenuItem == 6
// ? Theme.of(context)
// .extension<StackColors>()!
// .accentColorBlue
// : Colors.transparent,
// ),
// label: "Appearance",
// value: 6,
// group: selectedMenuItem,
// onChanged: updateSelectedMenuItem,
// ),
// const SizedBox(
// height: 2,
// ),
// SettingsMenuItem(
// icon: SvgPicture.asset(
// Assets.svg.polygon,
// width: 11,
// height: 11,
// color: selectedMenuItem == 7
// ? Theme.of(context)
// .extension<StackColors>()!
// .accentColorBlue
// : Colors.transparent,
// ),
// label: "Advanced",
// value: 7,
// group: selectedMenuItem,
// onChanged: updateSelectedMenuItem,
// ),
),
],
),
),

View file

@ -219,44 +219,6 @@ class _AdvancedSettings extends ConsumerState<AdvancedSettings> {
thickness: 0.5,
),
),
Padding(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Debug info",
style: STextStyles.desktopTextExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark),
textAlign: TextAlign.left,
),
PrimaryButton(
buttonHeight: ButtonHeight.xs,
label: "Show logs",
width: 101,
onPressed: () async {
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return const DebugInfoDialog();
},
);
},
),
],
),
),
const Padding(
padding: EdgeInsets.all(10.0),
child: Divider(
thickness: 0.5,
),
),
Padding(
padding: const EdgeInsets.all(10),
child: Row(
@ -327,6 +289,44 @@ class _AdvancedSettings extends ConsumerState<AdvancedSettings> {
],
),
),
const Padding(
padding: EdgeInsets.all(10.0),
child: Divider(
thickness: 0.5,
),
),
Padding(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Debug info",
style: STextStyles.desktopTextExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark),
textAlign: TextAlign.left,
),
PrimaryButton(
buttonHeight: ButtonHeight.xs,
label: "Show logs",
width: 101,
onPressed: () async {
await showDialog<dynamic>(
context: context,
useSafeArea: false,
barrierDismissible: true,
builder: (context) {
return const DebugInfoDialog();
},
);
},
),
],
),
),
const SizedBox(
height: 10,
),

View file

@ -13,8 +13,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/exchange/active_pair.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/range.dart';
import 'package:stackwallet/providers/global/locale_provider.dart';
import 'package:stackwallet/services/exchange/exchange.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_unit.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
import 'package:tuple/tuple.dart';
@ -44,7 +48,22 @@ final efSendAmountStringProvider = StateProvider<String>((ref) {
if (refreshing && reversed) {
return "-";
} else {
return ref.watch(efSendAmountProvider)?.toString() ?? "";
final decimal = ref.watch(efSendAmountProvider);
String string = "";
if (decimal != null) {
final amount = Amount.fromDecimal(decimal, fractionDigits: decimal.scale);
final locale = ref.watch(localeServiceChangeNotifierProvider).locale;
string = AmountUnit.normal.displayAmount(
amount: amount,
locale: locale,
coin: Coin
.nano, // use nano just to ensure decimal.scale < Coin.value.decimals
withUnitName: false,
maxDecimalPlaces: decimal.scale,
);
}
return string;
}
});
final efReceiveAmountStringProvider = StateProvider<String>((ref) {
@ -54,7 +73,22 @@ final efReceiveAmountStringProvider = StateProvider<String>((ref) {
if (refreshing && reversed == false) {
return "-";
} else {
return ref.watch(efReceiveAmountProvider)?.toString() ?? "";
final decimal = ref.watch(efReceiveAmountProvider);
String string = "";
if (decimal != null) {
final amount = Amount.fromDecimal(decimal, fractionDigits: decimal.scale);
final locale = ref.watch(localeServiceChangeNotifierProvider).locale;
string = AmountUnit.normal.displayAmount(
amount: amount,
locale: locale,
coin: Coin
.nano, // use nano just to ensure decimal.scale < Coin.value.decimals
withUnitName: false,
maxDecimalPlaces: decimal.scale,
);
}
return string;
}
});

View file

@ -287,8 +287,8 @@ class BitcoinWallet extends CoinServiceAPI
@override
Future<int> get maxFee async {
final fee = (await fees).fast as String;
final satsFee =
Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
final satsFee = Decimal.parse(fee) *
Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
return satsFee.floor().toBigInt().toInt();
}
@ -1070,9 +1070,60 @@ class BitcoinWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -1087,6 +1138,8 @@ class BitcoinWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -2201,6 +2254,7 @@ class BitcoinWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -2308,11 +2362,14 @@ class BitcoinWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
if (satsPerVByte == null) {
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
@ -2321,6 +2378,7 @@ class BitcoinWallet extends CoinServiceAPI
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
}
}
final int amount = satoshiAmountToSend - feeForOneOutput;
dynamic txn = await buildTransaction(
@ -2373,12 +2431,16 @@ class BitcoinWallet extends CoinServiceAPI
}
// Assume 1 output, only for recipient and no change
final feeForOneOutput = estimateTxFee(
final feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
final feeForTwoOutputs = estimateTxFee(
final feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
@ -2576,6 +2638,7 @@ class BitcoinWallet extends CoinServiceAPI
return coinSelection(
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
satsPerVByte: satsPerVByte,
recipientAddress: recipientAddress,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,

View file

@ -955,9 +955,60 @@ class BitcoinCashWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -972,6 +1023,8 @@ class BitcoinCashWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -2187,6 +2240,7 @@ class BitcoinCashWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -2303,7 +2357,9 @@ class BitcoinCashWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
@ -2350,17 +2406,18 @@ class BitcoinCashWallet extends CoinServiceAPI
satoshisBeingUsed - satoshiAmountToSend - 1,
], // dust limit is the minimum amount a change output should be
))["vSize"] as int;
//todo: check if print needed
// debugPrint("vSizeForOneOutput $vSizeForOneOutput");
// debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts");
// Assume 1 output, only for recipient and no change
var feeForOneOutput = estimateTxFee(
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
var feeForTwoOutputs = estimateTxFee(
int feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
@ -2575,6 +2632,7 @@ class BitcoinCashWallet extends CoinServiceAPI
return coinSelection(
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
satsPerVByte: satsPerVByte,
recipientAddress: recipientAddress,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,

View file

@ -941,9 +941,60 @@ class DogecoinWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -958,6 +1009,8 @@ class DogecoinWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -2092,6 +2145,7 @@ class DogecoinWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -2199,7 +2253,9 @@ class DogecoinWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
@ -2248,12 +2304,16 @@ class DogecoinWallet extends CoinServiceAPI
debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts");
// Assume 1 output, only for recipient and no change
var feeForOneOutput = estimateTxFee(
var feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
var feeForTwoOutputs = estimateTxFee(
var feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
@ -2463,6 +2523,7 @@ class DogecoinWallet extends CoinServiceAPI
return coinSelection(
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
satsPerVByte: satsPerVByte,
recipientAddress: recipientAddress,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,
@ -2604,7 +2665,10 @@ class DogecoinWallet extends CoinServiceAPI
Logging.instance
.log("Starting buildTransaction ----------", level: LogLevel.Info);
final txb = TransactionBuilder(network: network);
final txb = TransactionBuilder(
network: network,
maximumFeeRate: 2500000, // 1000x default value in bitcoindart lib
);
txb.setVersion(1);
// Add transaction inputs

View file

@ -1409,6 +1409,7 @@ class ECashWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -1517,11 +1518,14 @@ class ECashWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
if (satsPerVByte == null) {
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
@ -1530,6 +1534,7 @@ class ECashWallet extends CoinServiceAPI
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
}
}
final int amount = satoshiAmountToSend - feeForOneOutput;
dynamic txn = await buildTransaction(
@ -1586,12 +1591,16 @@ class ECashWallet extends CoinServiceAPI
}
// Assume 1 output, only for recipient and no change
final feeForOneOutput = estimateTxFee(
final feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
final feeForTwoOutputs = estimateTxFee(
final feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
@ -1795,6 +1804,7 @@ class ECashWallet extends CoinServiceAPI
return coinSelection(
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
satsPerVByte: satsPerVByte,
recipientAddress: recipientAddress,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,
@ -1977,8 +1987,6 @@ class ECashWallet extends CoinServiceAPI
final tx = builder.build();
final txHex = tx.toHex();
final vSize = tx.virtualSize();
//todo: check if print needed
Logger.print("ecash raw hex: $txHex");
return {"hex": txHex, "vSize": vSize};
}
@ -2549,9 +2557,60 @@ class ECashWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -2566,6 +2625,9 @@ class ECashWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {

View file

@ -156,6 +156,7 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
final secretKeyIndex = arguments['secretKeyIndex'] as int?;
final epicboxConfig = arguments['epicboxConfig'] as String?;
final minimumConfirmations = arguments['minimumConfirmations'] as int?;
final onChainNote = arguments['onChainNote'] as String?;
Map<String, dynamic> result = {};
if (!(wallet == null ||
@ -165,7 +166,7 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
epicboxConfig == null ||
minimumConfirmations == null)) {
var res = await createTransaction(wallet, amount, address,
secretKeyIndex, epicboxConfig, minimumConfirmations);
secretKeyIndex, epicboxConfig, minimumConfirmations, onChainNote!);
result['result'] = res;
sendPort.send(result);
return;
@ -175,7 +176,7 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
final selectionStrategyIsAll =
arguments['selectionStrategyIsAll'] as int?;
final minimumConfirmations = arguments['minimumConfirmations'] as int?;
final message = arguments['message'] as String?;
final message = arguments['onChainNote'] as String?;
final amount = arguments['amount'] as int?;
final address = arguments['address'] as String?;
@ -459,6 +460,7 @@ class EpicCashWallet extends CoinServiceAPI
// TODO determine whether it is worth sending change to a change address.
dynamic message;
print("THIS TX DATA IS $txData");
String receiverAddress = txData['addresss'] as String;
@ -480,7 +482,7 @@ class EpicCashWallet extends CoinServiceAPI
"wallet": wallet!,
"selectionStrategyIsAll": selectionStrategyIsAll,
"minimumConfirmations": MINIMUM_CONFIRMATIONS,
"message": "",
"message": txData['onChainNote'],
"amount": (txData['recipientAmt'] as Amount).raw.toInt(),
"address": txData['addresss'] as String,
}, name: walletName);
@ -504,6 +506,7 @@ class EpicCashWallet extends CoinServiceAPI
"secretKeyIndex": 0,
"epicboxConfig": epicboxConfig.toString(),
"minimumConfirmations": MINIMUM_CONFIRMATIONS,
"onChainNote": txData['onChainNote'],
}, name: walletName);
message = await receivePort.first;
@ -1721,6 +1724,8 @@ class EpicCashWallet extends CoinServiceAPI
"";
String? commitId = slatesToCommits[slateId]?['commitId'] as String?;
tx['numberOfMessages'] = tx['messages']?['messages']?.length;
tx['onChainNote'] = tx['messages']?['messages']?[0]?['message'];
print("ON CHAIN MESSAGE IS ${tx['onChainNote']}");
int? height;
@ -1754,7 +1759,8 @@ class EpicCashWallet extends CoinServiceAPI
isLelantus: false,
slateId: slateId,
nonce: null,
otherData: tx["id"].toString(),
// otherData: tx["id"].toString(),
otherData: tx['onChainNote'].toString(),
inputs: [],
outputs: [],
numberOfMessages: ((tx["numberOfMessages"] == null) ? 0 : tx["numberOfMessages"]) as int,

View file

@ -265,17 +265,23 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
@override
Future<FeeObject> get fees => EthereumAPI.getFees();
//Full rescan is not needed for ETH since we have a balance
@override
Future<void> fullRescan(
int maxUnusedAddressGap, int maxNumberOfIndexesToCheck) {
// TODO: implement fullRescan
throw UnimplementedError();
int maxUnusedAddressGap,
int maxNumberOfIndexesToCheck,
) async {
await db.deleteWalletBlockchainData(walletId);
await _generateAndSaveAddress(
(await mnemonicString)!,
(await mnemonicPassphrase)!,
);
await updateBalance();
await _refreshTransactions(isRescan: true);
}
@override
Future<bool> generateNewAddress() {
// TODO: implement generateNewAddress - might not be needed for ETH
// not used for ETH
throw UnimplementedError();
}
@ -291,10 +297,10 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
level: LogLevel.Info,
);
//First get mnemonic so we can initialize credentials
String privateKey =
getPrivateKey((await mnemonicString)!, (await mnemonicPassphrase)!);
_credentials = web3.EthPrivateKey.fromHex(privateKey);
await _initCredentials(
(await mnemonicString)!,
(await mnemonicPassphrase)!,
);
if (getCachedId() == null) {
throw Exception(
@ -367,8 +373,24 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
value: "",
);
String privateKey = getPrivateKey(mnemonic, "");
await _generateAndSaveAddress(mnemonic, "");
Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info);
}
Future<void> _initCredentials(
String mnemonic,
String mnemonicPassphrase,
) async {
String privateKey = getPrivateKey(mnemonic, mnemonicPassphrase);
_credentials = web3.EthPrivateKey.fromHex(privateKey);
}
Future<void> _generateAndSaveAddress(
String mnemonic,
String mnemonicPassphrase,
) async {
await _initCredentials(mnemonic, mnemonicPassphrase);
final address = Address(
walletId: walletId,
@ -381,8 +403,6 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
);
await db.putAddress(address);
Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info);
}
bool _isConnected = false;
@ -649,7 +669,7 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
if (!needsRefresh) {
var allOwnAddresses = await _fetchAllOwnAddresses();
final response = await EthereumAPI.getEthTransactions(
allOwnAddresses.elementAt(0).value,
address: allOwnAddresses.elementAt(0).value,
);
if (response.value != null) {
final allTxs = response.value!;
@ -985,10 +1005,26 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
return isValidEthereumAddress(address);
}
Future<void> _refreshTransactions() async {
Future<void> _refreshTransactions({bool isRescan = false}) async {
String thisAddress = await currentReceivingAddress;
final response = await EthereumAPI.getEthTransactions(thisAddress);
int firstBlock = 0;
if (!isRescan) {
firstBlock =
await db.getTransactions(walletId).heightProperty().max() ?? 0;
if (firstBlock > 10) {
// add some buffer
firstBlock -= 10;
}
}
final response = await EthereumAPI.getEthTransactions(
address: thisAddress,
firstBlock: isRescan ? 0 : firstBlock,
includeTokens: true,
);
if (response.value == null) {
Logging.instance.log(
@ -999,6 +1035,11 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
return;
}
if (response.value!.isEmpty) {
// no new transactions found
return;
}
final txsResponse =
await EthereumAPI.getEthTransactionNonces(response.value!);
@ -1017,8 +1058,10 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
txFailed = true;
}
isIncoming = false;
} else {
} else if (checksumEthereumAddress(element.to) == thisAddress) {
isIncoming = true;
} else {
continue;
}
//Calculate fees (GasLimit * gasPrice)

View file

@ -1029,8 +1029,55 @@ class FiroWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final result = await coinSelection(
amount.raw.toInt(),
-1,
address,
isSendAll,
satsPerVByte: customSatsPerVByte,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -1045,6 +1092,8 @@ class FiroWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -1296,6 +1345,7 @@ class FiroWallet extends CoinServiceAPI
int selectedTxFeeRate,
String _recipientAddress,
bool isSendAll, {
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -1385,7 +1435,9 @@ class FiroWallet extends CoinServiceAPI
recipients: [_recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
@ -1429,17 +1481,18 @@ class FiroWallet extends CoinServiceAPI
satoshisBeingUsed - satoshiAmountToSend - 1,
], // dust limit is the minimum amount a change output should be
))["vSize"] as int;
//todo: check if print needed
debugPrint("vSizeForOneOutput $vSizeForOneOutput");
debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts");
// Assume 1 output, only for recipient and no change
var feeForOneOutput = estimateTxFee(
var feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
var feeForTwoOutputs = estimateTxFee(
var feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
@ -1641,9 +1694,15 @@ class FiroWallet extends CoinServiceAPI
level: LogLevel.Warning);
// try adding more outputs
if (spendableOutputs.length > inputsBeingConsumed) {
return coinSelection(satoshiAmountToSend, selectedTxFeeRate,
_recipientAddress, isSendAll,
additionalOutputs: additionalOutputs + 1, utxos: utxos);
return coinSelection(
satoshiAmountToSend,
selectedTxFeeRate,
_recipientAddress,
isSendAll,
additionalOutputs: additionalOutputs + 1,
satsPerVByte: satsPerVByte,
utxos: utxos,
);
}
return 2;
}

View file

@ -232,8 +232,8 @@ class LitecoinWallet extends CoinServiceAPI
@override
Future<int> get maxFee async {
final fee = (await fees).fast as String;
final satsFee =
Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
final satsFee = Decimal.parse(fee) *
Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
return satsFee.floor().toBigInt().toInt();
}
@ -1058,9 +1058,60 @@ class LitecoinWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -1075,6 +1126,8 @@ class LitecoinWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -2298,6 +2351,7 @@ class LitecoinWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -2403,11 +2457,14 @@ class LitecoinWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
if (satsPerVByte == null) {
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
@ -2416,6 +2473,7 @@ class LitecoinWallet extends CoinServiceAPI
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
}
}
final int amount = satoshiAmountToSend - feeForOneOutput;
dynamic txn = await buildTransaction(
@ -2455,12 +2513,16 @@ class LitecoinWallet extends CoinServiceAPI
))["vSize"] as int;
// Assume 1 output, only for recipient and no change
final feeForOneOutput = estimateTxFee(
final feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
final feeForTwoOutputs = estimateTxFee(
final feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
@ -2659,6 +2721,7 @@ class LitecoinWallet extends CoinServiceAPI
return coinSelection(
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
satsPerVByte: satsPerVByte,
recipientAddress: recipientAddress,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,

View file

@ -458,6 +458,8 @@ class MoneroWallet extends CoinServiceAPI with WalletCache, WalletDB {
case FeeRateType.slow:
feePriority = MoneroTransactionPriority.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
Future<PendingTransaction>? awaitPendingTransaction;
@ -864,6 +866,10 @@ class MoneroWallet extends CoinServiceAPI with WalletCache, WalletDB {
//
String address = walletBase!.getTransactionAddress(chain, index);
if (address.contains("111")) {
return await _generateAddressForChain(chain, index + 1);
}
return isar_models.Address(
walletId: walletId,
derivationIndex: index,

View file

@ -224,8 +224,8 @@ class NamecoinWallet extends CoinServiceAPI
@override
Future<int> get maxFee async {
final fee = (await fees).fast as String;
final satsFee =
Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
final satsFee = Decimal.parse(fee) *
Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
return satsFee.floor().toBigInt().toInt();
}
@ -1048,9 +1048,60 @@ class NamecoinWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -1065,6 +1116,8 @@ class NamecoinWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -2274,6 +2327,7 @@ class NamecoinWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -2379,11 +2433,14 @@ class NamecoinWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
if (satsPerVByte == null) {
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
@ -2392,6 +2449,7 @@ class NamecoinWallet extends CoinServiceAPI
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
}
}
final int amount = satoshiAmountToSend - feeForOneOutput;
dynamic txn = await buildTransaction(
@ -2431,12 +2489,16 @@ class NamecoinWallet extends CoinServiceAPI
))["vSize"] as int;
// Assume 1 output, only for recipient and no change
final feeForOneOutput = estimateTxFee(
final feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
final feeForTwoOutputs = estimateTxFee(
final feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
@ -2635,6 +2697,7 @@ class NamecoinWallet extends CoinServiceAPI
return coinSelection(
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
satsPerVByte: satsPerVByte,
recipientAddress: recipientAddress,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,

View file

@ -219,8 +219,8 @@ class ParticlWallet extends CoinServiceAPI
@override
Future<int> get maxFee async {
final fee = (await fees).fast as String;
final satsFee =
Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
final satsFee = Decimal.parse(fee) *
Decimal.fromInt(Constants.satsPerCoin(coin).toInt());
return satsFee.floor().toBigInt().toInt();
}
@ -975,9 +975,60 @@ class ParticlWallet extends CoinServiceAPI
}) async {
try {
final feeRateType = args?["feeRate"];
final customSatsPerVByte = args?["satsPerVByte"] as int?;
final feeRateAmount = args?["feeRateAmount"];
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
if (feeRateType is FeeRateType || feeRateAmount is int) {
if (customSatsPerVByte != null) {
// check for send all
bool isSendAll = false;
if (amount == balance.spendable) {
isSendAll = true;
}
final bool coinControl = utxos != null;
final result = await coinSelection(
satoshiAmountToSend: amount.raw.toInt(),
selectedTxFeeRate: -1,
satsPerVByte: customSatsPerVByte,
recipientAddress: address,
isSendAll: isSendAll,
utxos: utxos?.toList(),
coinControl: coinControl,
);
Logging.instance
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
if (result is int) {
switch (result) {
case 1:
throw Exception("Insufficient balance!");
case 2:
throw Exception("Insufficient funds to pay for transaction fee!");
default:
throw Exception("Transaction failed with error code $result");
}
} else {
final hex = result["hex"];
if (hex is String) {
final fee = result["fee"] as int;
final vSize = result["vSize"] as int;
Logging.instance.log("txHex: $hex", level: LogLevel.Info);
Logging.instance.log("fee: $fee", level: LogLevel.Info);
Logging.instance.log("vsize: $vSize", level: LogLevel.Info);
// fee should never be less than vSize sanity check
if (fee < vSize) {
throw Exception(
"Error in fee calculation: Transaction fee cannot be less than vSize");
}
return result as Map<String, dynamic>;
} else {
throw Exception("sent hex is not a String!!!");
}
}
} else if (feeRateType is FeeRateType || feeRateAmount is int) {
late final int rate;
if (feeRateType is FeeRateType) {
int fee = 0;
@ -992,6 +1043,8 @@ class ParticlWallet extends CoinServiceAPI
case FeeRateType.slow:
fee = feeObject.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
rate = fee;
} else {
@ -2441,6 +2494,7 @@ class ParticlWallet extends CoinServiceAPI
required String recipientAddress,
required bool coinControl,
required bool isSendAll,
int? satsPerVByte,
int additionalOutputs = 0,
List<isar_models.UTXO>? utxos,
}) async {
@ -2546,11 +2600,14 @@ class ParticlWallet extends CoinServiceAPI
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
int feeForOneOutput = estimateTxFee(
int feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
if (satsPerVByte == null) {
final int roughEstimate = roughFeeEstimate(
spendableOutputs.length,
1,
@ -2559,6 +2616,7 @@ class ParticlWallet extends CoinServiceAPI
if (feeForOneOutput < roughEstimate) {
feeForOneOutput = roughEstimate;
}
}
final int amount = satoshiAmountToSend - feeForOneOutput;
dynamic txn = await buildTransaction(
@ -2598,12 +2656,16 @@ class ParticlWallet extends CoinServiceAPI
))["vSize"] as int;
// Assume 1 output, only for recipient and no change
final feeForOneOutput = estimateTxFee(
final feeForOneOutput = satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
// Assume 2 outputs, one for recipient and one for change
final feeForTwoOutputs = estimateTxFee(
final feeForTwoOutputs = satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
vSize: vSizeForTwoOutPuts,
feeRatePerKB: selectedTxFeeRate,
);
@ -2803,6 +2865,7 @@ class ParticlWallet extends CoinServiceAPI
satoshiAmountToSend: satoshiAmountToSend,
selectedTxFeeRate: selectedTxFeeRate,
recipientAddress: recipientAddress,
satsPerVByte: satsPerVByte,
isSendAll: isSendAll,
additionalOutputs: additionalOutputs + 1,
utxos: utxos,

View file

@ -486,6 +486,8 @@ class WowneroWallet extends CoinServiceAPI with WalletCache, WalletDB {
case FeeRateType.slow:
feePriority = MoneroTransactionPriority.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
}
Future<PendingTransaction>? awaitPendingTransaction;

View file

@ -47,12 +47,15 @@ class EthereumResponse<T> {
abstract class EthereumAPI {
static String get stackBaseServer => DefaultNodes.ethereum.host;
static Future<EthereumResponse<List<EthTxDTO>>> getEthTransactions(
String address) async {
static Future<EthereumResponse<List<EthTxDTO>>> getEthTransactions({
required String address,
int firstBlock = 0,
bool includeTokens = false,
}) async {
try {
final response = await get(
Uri.parse(
"$stackBaseServer/export?addrs=$address",
"$stackBaseServer/export?addrs=$address&firstBlock=$firstBlock",
),
);
@ -65,7 +68,7 @@ abstract class EthereumAPI {
for (final map in list!) {
final txn = EthTxDTO.fromMap(Map<String, dynamic>.from(map as Map));
if (txn.hasToken == 0) {
if (txn.hasToken == 0 || includeTokens) {
txns.add(txn);
}
}
@ -74,9 +77,11 @@ abstract class EthereumAPI {
null,
);
} else {
throw EthApiException(
"getEthTransactions($address) response is empty but status code is "
"${response.statusCode}",
// nice that the api returns an empty body instead of being
// consistent and returning a json object with no transactions
return EthereumResponse(
[],
null,
);
}
} else {
@ -194,9 +199,11 @@ abstract class EthereumAPI {
null,
);
} else {
throw EthApiException(
"getEthTransactionNonces($txns) response is empty but status code is "
"${response.statusCode}",
// nice that the api returns an empty body instead of being
// consistent and returning a json object with no transactions
return EthereumResponse(
[],
null,
);
}
} else {
@ -250,13 +257,13 @@ abstract class EthereumAPI {
);
} else {
throw EthApiException(
"getEthTransaction($txids) response is empty but status code is "
"getEthTokenTransactionsByTxids($txids) response is empty but status code is "
"${response.statusCode}",
);
}
} else {
throw EthApiException(
"getEthTransaction($txids) failed with status code: "
"getEthTokenTransactionsByTxids($txids) failed with status code: "
"${response.statusCode}",
);
}
@ -267,7 +274,7 @@ abstract class EthereumAPI {
);
} catch (e, s) {
Logging.instance.log(
"getEthTransaction($txids): $e\n$s",
"getEthTokenTransactionsByTxids($txids): $e\n$s",
level: LogLevel.Error,
);
return EthereumResponse(
@ -305,9 +312,11 @@ abstract class EthereumAPI {
null,
);
} else {
throw EthApiException(
"getTokenTransactions($address, $tokenContractAddress) response is empty but status code is "
"${response.statusCode}",
// nice that the api returns an empty body instead of being
// consistent and returning a json object with no transactions
return EthereumResponse(
[],
null,
);
}
} else {

View file

@ -449,6 +449,15 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache {
);
if (response.value == null) {
if (response.exception != null &&
response.exception!.message
.contains("response is empty but status code is 200")) {
Logging.instance.log(
"No ${tokenContract.name} transfers found for $addressString",
level: LogLevel.Info,
);
return;
}
throw response.exception ??
Exception("Failed to fetch token transaction data");
}
@ -507,9 +516,13 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache {
} else if (toAddress == addressString) {
isIncoming = true;
} else {
throw Exception("Unknown token transaction found for "
"${ethWallet.walletName} ${ethWallet.walletId}: "
"${tuple.item1.toString()}");
// ignore for now I guess since anything here is not reflected in
// balance anyways
continue;
// throw Exception("Unknown token transaction found for "
// "${ethWallet.walletName} ${ethWallet.walletId}: "
// "${tuple.item1.toString()}");
}
final txn = Transaction(

View file

@ -135,6 +135,9 @@ class ExchangeDataLoadingService {
Future<void> loadAll() async {
if (!_locked) {
_locked = true;
if (_isar == null) {
await initDB();
}
Logging.instance.log(
"ExchangeDataLoadingService.loadAll starting...",
level: LogLevel.Info,

View file

@ -21,7 +21,7 @@ class LocaleService extends ChangeNotifier {
Future<void> loadLocale({bool notify = true}) async {
_locale = Platform.isWindows
? "en_US"
: await Devicelocale.currentLocale ?? "en_US";
: (await Devicelocale.currentAsLocale)?.toString() ?? "en_US";
if (notify) {
notifyListeners();
}

View file

@ -26,6 +26,7 @@ class NotificationApi {
priority: Priority.high,
ticker: 'ticker'),
iOS: IOSNotificationDetails(),
macOS: MacOSNotificationDetails(),
);
}
@ -34,8 +35,13 @@ class NotificationApi {
const iOS = IOSInitializationSettings();
const linux = LinuxInitializationSettings(
defaultActionName: "temporary_stack_wallet");
const settings =
InitializationSettings(android: android, iOS: iOS, linux: linux);
const macOS = MacOSInitializationSettings();
const settings = InitializationSettings(
android: android,
iOS: iOS,
linux: linux,
macOS: macOS,
);
await _notifications.initialize(
settings,
onSelectNotification: (payload) async {

View file

@ -80,7 +80,7 @@ class PriceAPI {
{required String baseCurrency}) async {
final now = DateTime.now();
if (_lastUsedBaseCurrency != baseCurrency ||
now.difference(_lastCalled).inSeconds > 0) {
now.difference(_lastCalled) > refreshIntervalDuration) {
_lastCalled = now;
_lastUsedBaseCurrency = baseCurrency;
} else {

View file

@ -84,16 +84,11 @@ class Wallets extends ChangeNotifier {
}
final List<Tuple2<Coin, List<ChangeNotifierProvider<Manager>>>> result = [];
for (final coin in map.keys) {
for (final coin in Coin.values) {
if (map[coin] != null) {
result.add(Tuple2(coin, map[coin]!));
}
// result.sort((a, b) => a.item1.prettyName.compareTo(b.item1.prettyName));
result.sort((a, b) => a.item1.prettyName == "Bitcoin"
? -1
: b.item1.prettyName == "Monero"
? 1
: a.item1.prettyName.compareTo(b.item1.prettyName));
}
return result;
}

View file

@ -22,3 +22,14 @@ final coinCardProvider = Provider.family<String?, Coin>((ref, coin) {
return null;
}
});
final coinCardFavoritesProvider = Provider.family<String?, Coin>((ref, coin) {
final assets = ref.watch(themeAssetsProvider);
if (assets is ThemeAssetsV3) {
return assets.coinCardFavoritesImages?[coin.mainNetVersion] ??
assets.coinCardImages?[coin.mainNetVersion];
} else {
return null;
}
});

View file

@ -30,8 +30,6 @@ final coinIconProvider = Provider.family<String, Coin>((ref, coin) {
case Coin.dogecoin:
case Coin.dogecoinTestNet:
return assets.dogecoin;
case Coin.eCash:
return assets.bitcoin;
case Coin.epicCash:
return assets.epicCash;
case Coin.firo:
@ -48,7 +46,7 @@ final coinIconProvider = Provider.family<String, Coin>((ref, coin) {
case Coin.ethereum:
return assets.ethereum;
default:
return assets.bitcoin;
return assets.stackIcon;
}
} else if (assets is ThemeAssetsV2) {
return (assets).coinIcons[coin.mainNetVersion]!;

View file

@ -40,7 +40,7 @@ class ThemeService {
void init(MainDB db) => _db ??= db;
Future<void> install({required Uint8List themeArchiveData}) async {
final themesDir = await StackFileSystem.applicationThemesDirectory();
final themesDir = StackFileSystem.themesDir!;
final archive = ZipDecoder().decodeBytes(themeArchiveData);
@ -55,7 +55,6 @@ class ThemeService {
final theme = StackTheme.fromJson(
json: Map<String, dynamic>.from(json),
applicationThemesDirectoryPath: themesDir.path,
);
try {
@ -88,7 +87,7 @@ class ThemeService {
}
Future<void> remove({required String themeId}) async {
final themesDir = await StackFileSystem.applicationThemesDirectory();
final themesDir = StackFileSystem.themesDir!;
final isarId = await db.isar.stackThemes
.where()
.themeIdEqualTo(themeId)
@ -98,7 +97,10 @@ class ThemeService {
await db.isar.writeTxn(() async {
await db.isar.stackThemes.delete(isarId);
});
await Directory("${themesDir.path}/$themeId").delete(recursive: true);
final dir = Directory("${themesDir.path}/$themeId");
if (dir.existsSync()) {
await dir.delete(recursive: true);
}
} else {
Logging.instance.log(
"Failed to delete theme $themeId",
@ -154,8 +156,10 @@ class ThemeService {
);
} else {
// check installed version
final theme = ThemeService.instance.getTheme(themeId: "dark");
if ((theme?.version ?? 1) < _currentDefaultThemeVersion) {
// final theme = ThemeService.instance.getTheme(themeId: "dark");
// Force update theme to add missing icons for now
// TODO: uncomment if statement in future when themes are version 4 or above
// if ((theme?.version ?? 1) < _currentDefaultThemeVersion) {
Logging.instance.log(
"Updating default dark theme...",
level: LogLevel.Info,
@ -167,7 +171,7 @@ class ThemeService {
"Updating default dark theme... finished",
level: LogLevel.Info,
);
}
// }
}
}
@ -182,7 +186,7 @@ class ThemeService {
return false;
}
final themesDir = await StackFileSystem.applicationThemesDirectory();
final themesDir = StackFileSystem.themesDir!;
final jsonFileExists =
await File("${themesDir.path}/$themeId/theme.json").exists();
final assetsDirExists =

View file

@ -10,11 +10,11 @@
import 'dart:convert';
import 'package:bitbox/bitbox.dart' as bitbox;
import 'package:bitcoindart/bitcoindart.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter_libepiccash/epic_cash.dart';
import 'package:nanodart/nanodart.dart';
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
import 'package:stackwallet/services/coins/ecash/ecash_wallet.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
@ -64,7 +64,27 @@ class AddressUtils {
case Coin.litecoin:
return Address.validateAddress(address, litecoin);
case Coin.bitcoincash:
return Address.validateAddress(address, bitcoincash);
try {
// 0 for bitcoincash: address scheme, 1 for legacy address
final format = bitbox.Address.detectFormat(address);
if (coin == Coin.bitcoincashTestnet) {
return true;
}
if (format == bitbox.Address.formatCashAddr) {
String addr = address;
if (addr.contains(":")) {
addr = addr.split(":").last;
}
return addr.startsWith("q");
} else {
return address.startsWith("1");
}
} catch (e) {
return false;
}
case Coin.dogecoin:
return Address.validateAddress(address, dogecoin);
case Coin.epicCash:
@ -94,7 +114,27 @@ class AddressUtils {
case Coin.litecoinTestNet:
return Address.validateAddress(address, litecointestnet);
case Coin.bitcoincashTestnet:
return Address.validateAddress(address, bitcoincashtestnet);
try {
// 0 for bitcoincash: address scheme, 1 for legacy address
final format = bitbox.Address.detectFormat(address);
if (coin == Coin.bitcoincashTestnet) {
return true;
}
if (format == bitbox.Address.formatCashAddr) {
String addr = address;
if (addr.contains(":")) {
addr = addr.split(":").last;
}
return addr.startsWith("q");
} else {
return address.startsWith("1");
}
} catch (e) {
return false;
}
case Coin.firoTestNet:
return Address.validateAddress(address, firoTestNetwork);
case Coin.dogecoinTestNet:
@ -126,7 +166,17 @@ class AddressUtils {
String address,
Map<String, String> params,
) {
String uriString = "${coin.uriScheme}:$address";
// TODO: other sanitation as well ?
String sanitizedAddress = address;
if (coin == Coin.bitcoincash ||
coin == Coin.bitcoincashTestnet ||
coin == Coin.eCash) {
final prefix = "${coin.uriScheme}:";
if (address.startsWith(prefix)) {
sanitizedAddress = address.replaceFirst(prefix, "");
}
}
String uriString = "${coin.uriScheme}:$sanitizedAddress";
if (params.isNotEmpty) {
uriString += Uri(queryParameters: params).toString();
}

View file

@ -11,8 +11,7 @@
import 'dart:convert';
import 'package:decimal/decimal.dart';
import 'package:intl/number_symbols.dart';
import 'package:intl/number_symbols_data.dart';
import 'package:stackwallet/utilities/util.dart';
class Amount {
Amount({
@ -32,6 +31,38 @@ class Amount {
: assert(fractionDigits >= 0),
_value = amount.shift(fractionDigits).toBigInt();
static Amount? tryParseFiatString(
String value, {
required String locale,
}) {
final parts = value.split(" ");
if (parts.first.isEmpty) {
return null;
}
String str = parts.first;
if (str.startsWith(RegExp(r'[+-]'))) {
str = str.substring(1);
}
if (str.isEmpty) {
return null;
}
// get number symbols for decimal place and group separator
final numberSymbols = Util.getSymbolsFor(locale: locale);
final groupSeparator = numberSymbols?.GROUP_SEP ?? ",";
final decimalSeparator = numberSymbols?.DECIMAL_SEP ?? ".";
str = str.replaceAll(groupSeparator, "");
final decimalString = str.replaceFirst(decimalSeparator, ".");
return Decimal.tryParse(decimalString)?.toAmount(fractionDigits: 2);
}
// ===========================================================================
// ======= Instance properties ===============================================
@ -67,15 +98,22 @@ class Amount {
}) {
final wholeNumber = decimal.truncate();
final String separator =
(numberFormatSymbols[locale] as NumberSymbols?)?.DECIMAL_SEP ??
(numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?)
?.DECIMAL_SEP ??
".";
// get number symbols for decimal place and group separator
final numberSymbols = Util.getSymbolsFor(locale: locale);
final String separator = numberSymbols?.DECIMAL_SEP ?? ".";
final fraction = decimal - wholeNumber;
return "${wholeNumber.toStringAsFixed(0)}$separator${fraction.toStringAsFixed(2).substring(2)}";
String wholeNumberString = wholeNumber.toStringAsFixed(0);
// insert group separator
final regex = RegExp(r'\B(?=(\d{3})+(?!\d))');
wholeNumberString = wholeNumberString.replaceAllMapped(
regex,
(m) => "${m.group(0)}${numberSymbols?.GROUP_SEP ?? ","}",
);
return "$wholeNumberString$separator${fraction.toStringAsFixed(2).substring(2)}";
}
// String localizedStringAsFixed({
// required String locale,

View file

@ -22,11 +22,13 @@ final pMaxDecimals = Provider.family<int, Coin>(
);
final pAmountFormatter = Provider.family<AmountFormatter, Coin>((ref, coin) {
final locale = ref.watch(
localeServiceChangeNotifierProvider.select((value) => value.locale),
);
return AmountFormatter(
unit: ref.watch(pAmountUnit(coin)),
locale: ref.watch(
localeServiceChangeNotifierProvider.select((value) => value.locale),
),
locale: locale,
coin: coin,
maxDecimals: ref.watch(pMaxDecimals(coin)),
);
@ -63,4 +65,16 @@ class AmountFormatter {
tokenContract: ethContract,
);
}
Amount? tryParse(
String string, {
EthContract? ethContract,
}) {
return unit.tryParse(
string,
locale: locale,
coin: coin,
tokenContract: ethContract,
);
}
}

View file

@ -0,0 +1,95 @@
import 'dart:math';
import 'package:flutter/services.dart';
import 'package:stackwallet/utilities/amount/amount_unit.dart';
import 'package:stackwallet/utilities/util.dart';
class AmountInputFormatter extends TextInputFormatter {
final int decimals;
final String locale;
final AmountUnit? unit;
AmountInputFormatter({
required this.decimals,
required this.locale,
this.unit,
});
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
// get number symbols for decimal place and group separator
final numberSymbols = Util.getSymbolsFor(locale: locale);
final decimalSeparator = numberSymbols?.DECIMAL_SEP ?? ".";
final groupSeparator = numberSymbols?.GROUP_SEP ?? ",";
String newText = newValue.text.replaceAll(groupSeparator, "");
final selectionIndexFromTheRight =
newValue.text.length - newValue.selection.end;
String? fraction;
if (newText.contains(decimalSeparator)) {
final parts = newText.split(decimalSeparator);
if (parts.length > 2) {
return oldValue;
}
final fractionDigits =
unit == null ? decimals : max(decimals - unit!.shift, 0);
if (newText.startsWith(decimalSeparator)) {
if (newText.length - 1 > fractionDigits) {
newText = newText.substring(0, fractionDigits + 1);
}
return TextEditingValue(
text: newText,
selection: TextSelection.collapsed(
offset: newText.length - selectionIndexFromTheRight,
),
);
}
newText = parts.first;
if (parts.length == 2) {
fraction = parts.last;
} else {
fraction = "";
}
if (fraction.length > fractionDigits) {
fraction = fraction.substring(0, fractionDigits);
}
}
String newString;
final val = BigInt.tryParse(newText);
if (val == null || val < BigInt.one) {
newString = newText;
} else {
// insert group separator
final regex = RegExp(r'\B(?=(\d{3})+(?!\d))');
newString = newText.replaceAllMapped(
regex,
(m) => "${m.group(0)}${numberSymbols?.GROUP_SEP ?? ","}",
);
}
if (fraction != null) {
newString += decimalSeparator;
if (fraction.isNotEmpty) {
newString += fraction;
}
}
return TextEditingValue(
text: newString,
selection: TextSelection.collapsed(
offset: newString.length - selectionIndexFromTheRight,
),
);
}
}

View file

@ -11,11 +11,10 @@
import 'dart:math' as math;
import 'package:decimal/decimal.dart';
import 'package:intl/number_symbols.dart';
import 'package:intl/number_symbols_data.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/util.dart';
// preserve index order as index is used to store value in preferences
enum AmountUnit {
@ -164,6 +163,53 @@ extension AmountUnitExt on AmountUnit {
}
}
Amount? tryParse(
String value, {
required String locale,
required Coin coin,
EthContract? tokenContract,
bool overrideWithDecimalPlacesFromString = false,
}) {
final precisionLost = value.startsWith("~");
final parts = (precisionLost ? value.substring(1) : value).split(" ");
if (parts.first.isEmpty) {
return null;
}
String str = parts.first;
if (str.startsWith(RegExp(r'[+-]'))) {
str = str.substring(1);
}
if (str.isEmpty) {
return null;
}
// get number symbols for decimal place and group separator
final numberSymbols = Util.getSymbolsFor(locale: locale);
final groupSeparator = numberSymbols?.GROUP_SEP ?? ",";
final decimalSeparator = numberSymbols?.DECIMAL_SEP ?? ".";
str = str.replaceAll(groupSeparator, "");
final decimalString = str.replaceFirst(decimalSeparator, ".");
final Decimal? decimal = Decimal.tryParse(decimalString);
if (decimal == null) {
return null;
}
final decimalPlaces = overrideWithDecimalPlacesFromString
? decimal.scale
: tokenContract?.decimals ?? coin.decimals;
final realShift = math.min(shift, decimalPlaces);
return decimal.shift(0 - realShift).toAmount(fractionDigits: decimalPlaces);
}
String displayAmount({
required Amount amount,
required String locale,
@ -191,6 +237,16 @@ extension AmountUnitExt on AmountUnit {
// start building the return value with just the whole value
String returnValue = wholeNumber.toString();
// get number symbols for decimal place and group separator
final numberSymbols = Util.getSymbolsFor(locale: locale);
// insert group separator
final regex = RegExp(r'\B(?=(\d{3})+(?!\d))');
returnValue = returnValue.replaceAllMapped(
regex,
(m) => "${m.group(0)}${numberSymbols?.GROUP_SEP ?? ","}",
);
// if true and withUnitName is true, we will show "~" prepended on amount
bool didLosePrecision = false;
@ -239,11 +295,7 @@ extension AmountUnitExt on AmountUnit {
}
// get decimal separator based on locale
final String separator =
(numberFormatSymbols[locale] as NumberSymbols?)?.DECIMAL_SEP ??
(numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?)
?.DECIMAL_SEP ??
".";
final String separator = numberSymbols?.DECIMAL_SEP ?? ".";
// append separator and fractional amount
returnValue += "$separator$remainder";

View file

@ -65,8 +65,8 @@ class _EXCHANGE {
class _BUY {
const _BUY();
String simplexLogo(BuildContext context) {
switch (MediaQuery.of(context).platformBrightness) {
String simplexLogo(Brightness themeBrightness) {
switch (themeBrightness) {
case Brightness.dark:
return "assets/svg/buy/Simplex-Nuvei-Logo-light.svg";

View file

@ -178,10 +178,8 @@ class DbVersionMigrator with WalletDB {
case 3:
// clear possible broken firo cache
await DB.instance.deleteBoxFromDisk(
boxName: DB.instance.boxNameSetCache(coin: Coin.firo));
await DB.instance.deleteBoxFromDisk(
boxName: DB.instance.boxNameUsedSerialsCache(coin: Coin.firo));
await DB.instance.clearSharedTransactionCache(coin: Coin.firo);
// update version
await DB.instance.put<dynamic>(

View file

@ -1,40 +0,0 @@
/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
* All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26
*
*/
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/utilities/logger.dart';
Future<bool> deleteEverything() async {
try {
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameAddressBook);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameDebugInfo);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameNodeModels);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePrimaryNodes);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameAllWalletsData);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameNotifications);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameWatchedTransactions);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameWatchedTrades);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTrades);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTradesV2);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTradeNotes);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTradeLookup);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameFavoriteWallets);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePrefs);
await DB.instance
.deleteBoxFromDisk(boxName: DB.boxNameWalletsToDeleteOnStart);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePriceCache);
await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameDBInfo);
await DB.instance.deleteBoxFromDisk(boxName: "theme");
return true;
} catch (e, s) {
Logging.instance.log("$e $s", level: LogLevel.Error);
return false;
}
}

View file

@ -10,9 +10,9 @@
import 'package:hive/hive.dart';
import 'package:stack_wallet_backup/secure_storage.dart';
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/utilities/logger.dart';
const String kBoxNameDesktopData = "desktopData";
const String _kKeyBlobKey = "swbKeyBlobKeyStringID";
const String _kKeyBlobVersionKey = "swbKeyBlobVersionKeyStringID";
@ -62,14 +62,8 @@ class DPS {
kLatestBlobVersion,
);
final box = await Hive.openBox<String>(DB.boxNameDesktopData);
await DB.instance.put<String>(
boxName: DB.boxNameDesktopData,
key: _kKeyBlobKey,
value: await _handler!.getKeyBlob(),
);
await _put(key: _kKeyBlobKey, value: await _handler!.getKeyBlob());
await _updateStoredKeyBlobVersion(kLatestBlobVersion);
await box.close();
} catch (e, s) {
Logging.instance.log(
"${_getMessageFromException(e)}\n$s",
@ -85,19 +79,13 @@ class DPS {
"DPS: attempted to re initialize with existing passphrase");
}
final box = await Hive.openBox<String>(DB.boxNameDesktopData);
final keyBlob = DB.instance.get<String>(
boxName: DB.boxNameDesktopData,
key: _kKeyBlobKey,
);
await box.close();
try {
final keyBlob = await _get(key: _kKeyBlobKey);
if (keyBlob == null) {
throw Exception(
"DPS: failed to find keyBlob while attempting to initialize with existing passphrase");
}
try {
final blobVersion = await _getStoredKeyBlobVersion();
_handler = await StorageCryptoHandler.fromExisting(
passphrase,
@ -107,14 +95,8 @@ class DPS {
if (blobVersion < kLatestBlobVersion) {
// update blob
await _handler!.resetPassphrase(passphrase, kLatestBlobVersion);
final box = await Hive.openBox<String>(DB.boxNameDesktopData);
await DB.instance.put<String>(
boxName: DB.boxNameDesktopData,
key: _kKeyBlobKey,
value: await _handler!.getKeyBlob(),
);
await _put(key: _kKeyBlobKey, value: await _handler!.getKeyBlob());
await _updateStoredKeyBlobVersion(kLatestBlobVersion);
await box.close();
}
} catch (e, s) {
Logging.instance.log(
@ -126,19 +108,13 @@ class DPS {
}
Future<bool> verifyPassphrase(String passphrase) async {
final box = await Hive.openBox<String>(DB.boxNameDesktopData);
final keyBlob = DB.instance.get<String>(
boxName: DB.boxNameDesktopData,
key: _kKeyBlobKey,
);
await box.close();
try {
final keyBlob = await _get(key: _kKeyBlobKey);
if (keyBlob == null) {
// no passphrase key blob found so any passphrase is technically bad
return false;
}
try {
final blobVersion = await _getStoredKeyBlobVersion();
await StorageCryptoHandler.fromExisting(passphrase, keyBlob, blobVersion);
// existing passphrase matches key blob
@ -157,12 +133,8 @@ class DPS {
String passphraseOld,
String passphraseNew,
) async {
final box = await Hive.openBox<String>(DB.boxNameDesktopData);
final keyBlob = DB.instance.get<String>(
boxName: DB.boxNameDesktopData,
key: _kKeyBlobKey,
);
await box.close();
try {
final keyBlob = await _get(key: _kKeyBlobKey);
if (keyBlob == null) {
// no passphrase key blob found so any passphrase is technically bad
@ -174,18 +146,12 @@ class DPS {
}
final blobVersion = await _getStoredKeyBlobVersion();
try {
await _handler!.resetPassphrase(passphraseNew, blobVersion);
final box = await Hive.openBox<String>(DB.boxNameDesktopData);
await DB.instance.put<String>(
boxName: DB.boxNameDesktopData,
await _put(
key: _kKeyBlobKey,
value: await _handler!.getKeyBlob(),
);
await _updateStoredKeyBlobVersion(blobVersion);
await box.close();
// successfully updated passphrase
return true;
@ -199,28 +165,54 @@ class DPS {
}
Future<bool> hasPassword() async {
final keyBlob = DB.instance.get<String>(
boxName: DB.boxNameDesktopData,
key: _kKeyBlobKey,
);
final keyBlob = await _get(key: _kKeyBlobKey);
return keyBlob != null;
}
Future<int> _getStoredKeyBlobVersion() async {
final box = await Hive.openBox<String>(DB.boxNameDesktopData);
final keyBlobVersionString = DB.instance.get<String>(
boxName: DB.boxNameDesktopData,
key: _kKeyBlobVersionKey,
);
await box.close();
final keyBlobVersionString = await _get(key: _kKeyBlobVersionKey);
return int.tryParse(keyBlobVersionString ?? "1") ?? 1;
}
Future<void> _updateStoredKeyBlobVersion(int version) async {
await DB.instance.put<String>(
boxName: DB.boxNameDesktopData,
key: _kKeyBlobVersionKey,
value: version.toString(),
await _put(key: _kKeyBlobVersionKey, value: version.toString());
}
Future<void> _put({required String key, required String value}) async {
Box<String>? box;
try {
box = await Hive.openBox<String>(kBoxNameDesktopData);
await box.put(key, value);
} catch (e, s) {
Logging.instance.log(
"DPS failed put($key): $e\n$s",
level: LogLevel.Fatal,
);
} finally {
await box?.close();
}
}
Future<String?> _get({required String key}) async {
String? value;
Box<String>? box;
try {
box = await Hive.openBox<String>(kBoxNameDesktopData);
value = box.get(key);
} catch (e, s) {
Logging.instance.log(
"DPS failed get($key): $e\n$s",
level: LogLevel.Fatal,
);
} finally {
await box?.close();
}
return value;
}
/// Dangerous. Used in one place and should not be called anywhere else.
@Deprecated("Don't use this if at all possible")
Future<void> deleteBox() async {
await Hive.deleteBoxFromDisk(kBoxNameDesktopData);
}
}

View file

@ -8,7 +8,7 @@
*
*/
enum FeeRateType { fast, average, slow }
enum FeeRateType { fast, average, slow, custom }
extension FeeRateTypeExt on FeeRateType {
String get prettyName {
@ -19,6 +19,8 @@ extension FeeRateTypeExt on FeeRateType {
return "Average";
case FeeRateType.slow:
return "Slow";
case FeeRateType.custom:
return "Custom";
}
}
}

View file

@ -10,35 +10,6 @@
import 'package:flutter/material.dart';
// todo: delete this map (example)
final map = {
"name": "Dark",
"coinColors": {
"bitcoin": "0xFF267352",
},
"assets": {
"circleLock": "svg/somerandomnamecreatedbythemecreator.svg",
},
"colors": {
"background": "0xFF848383",
},
"gradientBackground": {
"gradientType": "linear",
"begin": {
"x": 0.0,
"y": 1.0,
},
"end": {
"x": -1.0,
"y": 1.0,
},
"colors": [
"0xFF638227",
"0xFF632827",
]
}
};
extension BoxShadowExt on BoxShadow {
static BoxShadow fromJson(Map<String, dynamic> json) => BoxShadow(
color: Color(int.parse(json["color"] as String)),

View file

@ -27,8 +27,8 @@ abstract class StackFileSystem {
} else if (Platform.isWindows) {
appDirectory = await getApplicationSupportDirectory();
} else if (Platform.isMacOS) {
// currently run in ipad mode??
throw Exception("Unsupported platform");
appDirectory = await getLibraryDirectory();
appDirectory = Directory("${appDirectory.path}/stackwallet");
} else if (Platform.isIOS) {
// todo: check if we need different behaviour here
if (Util.isDesktop) {
@ -73,16 +73,15 @@ abstract class StackFileSystem {
}
}
static Future<Directory> applicationThemesDirectory() async {
static Future<void> initThemesDir() async {
final root = await applicationRootDirectory();
// if (Util.isDesktop) {
final dir = Directory("${root.path}/themes");
if (!dir.existsSync()) {
await dir.create();
}
return dir;
// } else {
// return root;
// }
themesDir = dir;
}
static Directory? themesDir;
}

View file

@ -14,11 +14,21 @@ import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:intl/number_symbols.dart';
import 'package:intl/number_symbols_data.dart';
abstract class Util {
static Directory? libraryPath;
static double? screenWidth;
static NumberSymbols? getSymbolsFor({required String locale}) {
return numberFormatSymbols[locale] as NumberSymbols? ??
numberFormatSymbols[locale.replaceAll("-", "_")] as NumberSymbols? ??
numberFormatSymbols[locale.substring(3).toLowerCase()]
as NumberSymbols? ??
numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?;
}
static bool get isDesktop {
// special check for running on linux based phones
if (Platform.isLinux && screenWidth != null && screenWidth! < 800) {

View file

@ -70,9 +70,10 @@ class _AddressBookCardState extends ConsumerState<AddressBookCard> {
final contact = _contact!;
final List<Coin> coins = [];
for (var element in contact.addresses) {
if (!coins.contains(element.coin)) {
coins.add(element.coin);
for (final coin in Coin.values) {
if (contact.addresses.where((e) => e.coin == coin).isNotEmpty) {
coins.add(coin);
}
}

View file

@ -25,11 +25,13 @@ class CoinCard extends ConsumerWidget {
required this.walletId,
required this.width,
required this.height,
required this.isFavorite,
});
final String walletId;
final double width;
final double height;
final bool isFavorite;
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -38,7 +40,7 @@ class CoinCard extends ConsumerWidget {
.select((value) => value.getManager(walletId).coin),
);
final bool hasCardImageBg = ref.watch(coinCardProvider(coin)) != null;
final bool hasCardImageBg = (isFavorite) ? ref.watch(coinCardFavoritesProvider(coin)) != null : ref.watch(coinCardProvider(coin)) != null;
return Stack(
children: [
@ -47,11 +49,16 @@ class CoinCard extends ConsumerWidget {
width: width,
height: height,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
image: DecorationImage(
fit: BoxFit.fill,
fit: BoxFit.cover,
image: FileImage(
File(
ref.watch(coinCardProvider(coin))!,
(isFavorite)
? ref.watch(coinCardFavoritesProvider(coin))!
: ref.watch(coinCardProvider(coin))!,
),
),
),

View file

@ -0,0 +1,446 @@
import 'package:cw_core/monero_transaction_priority.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/models.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/animated_text.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
class DesktopFeeDialog extends ConsumerStatefulWidget {
const DesktopFeeDialog({
Key? key,
required this.walletId,
this.isToken = false,
}) : super(key: key);
final String walletId;
final bool isToken;
@override
ConsumerState<DesktopFeeDialog> createState() => _DesktopFeeDialogState();
}
class _DesktopFeeDialogState extends ConsumerState<DesktopFeeDialog> {
late final String walletId;
FeeObject? feeObject;
FeeRateType feeRateType = FeeRateType.average;
Future<Amount> feeFor({
required Amount amount,
required FeeRateType feeRateType,
required int feeRate,
required Coin coin,
}) async {
switch (feeRateType) {
case FeeRateType.fast:
if (ref
.read(widget.isToken
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.fast[amount] ==
null) {
if (widget.isToken == false) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
if (coin == Coin.monero || coin == Coin.wownero) {
final fee = await manager.estimateFeeFor(
amount, MoneroTransactionPriority.fast.raw!);
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state !=
"Private") {
ref.read(feeSheetSessionCacheProvider).fast[amount] =
await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate);
} else {
ref.read(feeSheetSessionCacheProvider).fast[amount] =
await manager.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
ref.read(tokenFeeSessionCacheProvider).fast[amount] = fee;
}
}
return ref
.read(widget.isToken
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.fast[amount]!;
case FeeRateType.average:
if (ref
.read(widget.isToken
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.average[amount] ==
null) {
if (widget.isToken == false) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
if (coin == Coin.monero || coin == Coin.wownero) {
final fee = await manager.estimateFeeFor(
amount, MoneroTransactionPriority.regular.raw!);
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state !=
"Private") {
ref.read(feeSheetSessionCacheProvider).average[amount] =
await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate);
} else {
ref.read(feeSheetSessionCacheProvider).average[amount] =
await manager.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
ref.read(tokenFeeSessionCacheProvider).average[amount] = fee;
}
}
return ref
.read(widget.isToken
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.average[amount]!;
case FeeRateType.slow:
if (ref
.read(widget.isToken
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.slow[amount] ==
null) {
if (widget.isToken == false) {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId);
if (coin == Coin.monero || coin == Coin.wownero) {
final fee = await manager.estimateFeeFor(
amount, MoneroTransactionPriority.slow.raw!);
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state !=
"Private") {
ref.read(feeSheetSessionCacheProvider).slow[amount] =
await (manager.wallet as FiroWallet)
.estimateFeeForPublic(amount, feeRate);
} else {
ref.read(feeSheetSessionCacheProvider).slow[amount] =
await manager.estimateFeeFor(amount, feeRate);
}
} else {
final tokenWallet = ref.read(tokenServiceProvider)!;
final fee = tokenWallet.estimateFeeFor(feeRate);
ref.read(tokenFeeSessionCacheProvider).slow[amount] = fee;
}
}
return ref
.read(widget.isToken
? tokenFeeSessionCacheProvider
: feeSheetSessionCacheProvider)
.slow[amount]!;
default:
return Amount.zero;
}
}
@override
void initState() {
walletId = widget.walletId;
super.initState();
}
@override
Widget build(BuildContext context) {
return DesktopDialog(
maxWidth: 450,
maxHeight: double.infinity,
child: FutureBuilder(
future: ref.watch(
walletsChangeNotifierProvider.select(
(value) => value.getManager(walletId).fees,
),
),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
feeObject = snapshot.data!;
}
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 32),
child: Text(
"Choose fee",
style: STextStyles.desktopH3(context),
),
),
const DesktopDialogCloseButton(),
],
),
...FeeRateType.values.map(
(e) => Padding(
padding: const EdgeInsets.only(
left: 32,
right: 32,
bottom: 16,
),
child: DesktopFeeItem(
feeObject: feeObject,
feeRateType: e,
walletId: walletId,
feeFor: feeFor,
isSelected: false,
),
),
),
const SizedBox(
height: 16,
),
],
);
},
),
);
}
}
class DesktopFeeItem extends ConsumerStatefulWidget {
const DesktopFeeItem({
Key? key,
required this.feeObject,
required this.feeRateType,
required this.walletId,
required this.feeFor,
required this.isSelected,
this.isButton = true,
}) : super(key: key);
final FeeObject? feeObject;
final FeeRateType feeRateType;
final String walletId;
final Future<Amount> Function({
required Amount amount,
required FeeRateType feeRateType,
required int feeRate,
required Coin coin,
}) feeFor;
final bool isSelected;
final bool isButton;
@override
ConsumerState<DesktopFeeItem> createState() => _DesktopFeeItemState();
}
class _DesktopFeeItemState extends ConsumerState<DesktopFeeItem> {
String? feeString;
String? timeString;
static const stringsToLoopThrough = [
"Calculating",
"Calculating.",
"Calculating..",
"Calculating...",
];
String estimatedTimeToBeIncludedInNextBlock(
int targetBlockTime, int estimatedNumberOfBlocks) {
int time = targetBlockTime * estimatedNumberOfBlocks;
int hours = (time / 3600).floor();
if (hours > 1) {
return "~$hours hours";
} else if (hours == 1) {
return "~$hours hour";
}
// less than an hour
final string = (time / 60).toStringAsFixed(1);
if (string == "1.0") {
return "~1 minute";
} else {
if (string.endsWith(".0")) {
return "~${(time / 60).floor()} minutes";
}
return "~$string minutes";
}
}
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType : ${widget.feeRateType}");
return ConditionalParent(
condition: widget.isButton,
builder: (child) => MaterialButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onPressed: () {
Navigator.of(context).pop(
(
widget.feeRateType,
feeString,
timeString,
),
);
},
child: child,
),
child: Builder(
builder: (_) {
if (!widget.isButton) {
final coin = ref.watch(
walletsChangeNotifierProvider.select(
(value) => value.getManager(widget.walletId).coin,
),
);
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.watch(publicPrivateBalanceStateProvider.state).state ==
"Private") {
return Text(
"~${ref.watch(pAmountFormatter(coin)).format(
Amount(
rawValue: BigInt.parse("3794"),
fractionDigits: coin.decimals,
),
indicatePrecisionLoss: false,
)}",
style: STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
textAlign: TextAlign.left,
);
}
}
if (widget.feeRateType == FeeRateType.custom) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.feeRateType.prettyName,
style:
STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
textAlign: TextAlign.left,
),
],
);
}
final manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(widget.walletId)));
if (widget.feeObject == null) {
return AnimatedText(
stringsToLoopThrough: stringsToLoopThrough,
style: STextStyles.desktopTextExtraExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
);
} else {
return FutureBuilder(
future: widget.feeFor(
coin: manager.coin,
feeRateType: widget.feeRateType,
feeRate: widget.feeRateType == FeeRateType.fast
? widget.feeObject!.fast
: widget.feeRateType == FeeRateType.slow
? widget.feeObject!.slow
: widget.feeObject!.medium,
amount: ref.watch(sendAmountProvider.state).state,
),
builder: (_, AsyncSnapshot<Amount> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
feeString = "${widget.feeRateType.prettyName} "
"(~${ref.watch(pAmountFormatter(manager.coin)).format(
snapshot.data!,
indicatePrecisionLoss: false,
)})";
timeString = manager.coin == Coin.ethereum
? ""
: estimatedTimeToBeIncludedInNextBlock(
Constants.targetBlockTimeInSeconds(manager.coin),
widget.feeRateType == FeeRateType.fast
? widget.feeObject!.numberOfBlocksFast
: widget.feeRateType == FeeRateType.slow
? widget.feeObject!.numberOfBlocksSlow
: widget.feeObject!.numberOfBlocksAverage,
);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
feeString!,
style: STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
textAlign: TextAlign.left,
),
if (widget.feeObject != null)
Text(
timeString!,
style: STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
),
),
],
);
} else {
return AnimatedText(
stringsToLoopThrough: stringsToLoopThrough,
style: STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
),
);
}
},
);
}
},
),
);
}
}

View file

@ -0,0 +1,66 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
class FeeSlider extends StatefulWidget {
const FeeSlider({
super.key,
required this.onSatVByteChanged,
required this.coin,
});
final Coin coin;
final void Function(int) onSatVByteChanged;
@override
State<FeeSlider> createState() => _FeeSliderState();
}
class _FeeSliderState extends State<FeeSlider> {
static const double min = 1;
static const double max = 4;
double sliderValue = 0;
int rate = min.toInt();
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"sat/vByte",
style: STextStyles.smallMed12(context),
),
Text(
"$rate",
style: STextStyles.smallMed12(context),
),
],
),
Slider(
value: sliderValue,
onChanged: (value) {
setState(() {
sliderValue = value;
final number = pow(sliderValue * (max - min) + min, 4).toDouble();
switch (widget.coin) {
case Coin.dogecoin:
case Coin.dogecoinTestNet:
rate = (number * 1000).toInt();
default:
rate = number.toInt();
}
});
widget.onSatVByteChanged(rate);
},
),
],
);
}
}

View file

@ -9,17 +9,19 @@
*/
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/exchange/aggregate_currency.dart';
import 'package:stackwallet/pages/buy_view/sub_widgets/crypto_selection_view.dart';
import 'package:stackwallet/providers/global/locale_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
class ExchangeTextField extends StatefulWidget {
class ExchangeTextField extends ConsumerStatefulWidget {
const ExchangeTextField({
Key? key,
this.borderRadius = 0,
@ -55,10 +57,10 @@ class ExchangeTextField extends StatefulWidget {
final AggregateCurrency? currency;
@override
State<ExchangeTextField> createState() => _ExchangeTextFieldState();
ConsumerState<ExchangeTextField> createState() => _ExchangeTextFieldState();
}
class _ExchangeTextFieldState extends State<ExchangeTextField> {
class _ExchangeTextFieldState extends ConsumerState<ExchangeTextField> {
late final TextEditingController controller;
late final FocusNode focusNode;
late final TextStyle textStyle;
@ -130,12 +132,17 @@ class _ExchangeTextFieldState extends State<ExchangeTextField> {
),
),
inputFormatters: [
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue, newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: 8, // todo change this
locale: ref.watch(localeServiceChangeNotifierProvider
.select((value) => value.locale)),
),
// // regex to validate a crypto amount with 8 decimal places
// TextInputFormatter.withFunction((oldValue, newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
),
),

View file

@ -230,7 +230,9 @@ class _TransactionCardState extends ConsumerState<TransactionCard> {
fit: BoxFit.scaleDown,
child: Text(
_transaction.isCancelled
? "Cancelled"
? coin == Coin.ethereum
? "Failed"
: "Cancelled"
: whatIsIt(
_transaction.type,
coin,

View file

@ -26,6 +26,7 @@ import 'package:stackwallet/services/ethereum/ethereum_token_service.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
@ -136,17 +137,18 @@ class SimpleWalletCard extends ConsumerWidget {
context: desktopNavigatorState?.context ?? context,
opaqueBG: true,
message: "Loading ${contract.name}",
isDesktop: Util.isDesktop,
);
if (!success) {
// TODO: show error dialog here?
Logging.instance.log(
"Failed to load token wallet for $contract",
level: LogLevel.Error,
);
return;
}
if (desktopNavigatorState == null) {
// pop loading
nav.pop();
}
if (desktopNavigatorState != null) {
await desktopNavigatorState!.pushNamed(
DesktopTokenView.routeName,

View file

@ -6,6 +6,9 @@ import FlutterMacOS
import Foundation
import connectivity_plus
import cw_monero
import cw_shared_external
import cw_wownero
import desktop_drop
import device_info_plus
import devicelocale
@ -13,10 +16,10 @@ import flutter_libepiccash
import flutter_local_notifications
import flutter_secure_storage_macos
import isar_flutter_libs
import lelantus
import package_info_plus
import path_provider_foundation
import share_plus
import shared_preferences_foundation
import stack_wallet_backup
import url_launcher_macos
import wakelock_macos
@ -24,6 +27,9 @@ import window_size
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin"))
CwSharedExternalPlugin.register(with: registry.registrar(forPlugin: "CwSharedExternalPlugin"))
CwWowneroPlugin.register(with: registry.registrar(forPlugin: "CwWowneroPlugin"))
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin"))
@ -31,10 +37,10 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))
LelantusPlugin.register(with: registry.registrar(forPlugin: "LelantusPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
StackWalletBackupPlugin.register(with: registry.registrar(forPlugin: "StackWalletBackupPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin"))

View file

@ -1,4 +1,4 @@
platform :osx, '10.11'
platform :osx, '10.14'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@ -31,6 +31,9 @@ target 'Runner' do
use_modular_headers!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|

View file

@ -1,24 +1,76 @@
PODS:
- connectivity_plus_macos (0.0.1):
- connectivity_plus (0.0.1):
- FlutterMacOS
- ReachabilitySwift
- cw_monero (0.0.1):
- cw_monero/Boost (= 0.0.1)
- cw_monero/Monero (= 0.0.1)
- cw_monero/OpenSSL (= 0.0.1)
- cw_monero/Sodium (= 0.0.1)
- cw_monero/Unbound (= 0.0.1)
- FlutterMacOS
- cw_monero/Boost (0.0.1):
- FlutterMacOS
- cw_monero/Monero (0.0.1):
- FlutterMacOS
- cw_monero/OpenSSL (0.0.1):
- FlutterMacOS
- cw_monero/Sodium (0.0.1):
- FlutterMacOS
- cw_monero/Unbound (0.0.1):
- FlutterMacOS
- cw_shared_external (0.0.1):
- cw_shared_external/Boost (= 0.0.1)
- cw_shared_external/OpenSSL (= 0.0.1)
- cw_shared_external/Sodium (= 0.0.1)
- FlutterMacOS
- cw_shared_external/Boost (0.0.1):
- FlutterMacOS
- cw_shared_external/OpenSSL (0.0.1):
- FlutterMacOS
- cw_shared_external/Sodium (0.0.1):
- FlutterMacOS
- cw_wownero (0.0.1):
- cw_wownero/Boost (= 0.0.1)
- cw_wownero/OpenSSL (= 0.0.1)
- cw_wownero/Sodium (= 0.0.1)
- cw_wownero/Unbound (= 0.0.1)
- cw_wownero/Wownero (= 0.0.1)
- FlutterMacOS
- cw_wownero/Boost (0.0.1):
- FlutterMacOS
- cw_wownero/OpenSSL (0.0.1):
- FlutterMacOS
- cw_wownero/Sodium (0.0.1):
- FlutterMacOS
- cw_wownero/Unbound (0.0.1):
- FlutterMacOS
- cw_wownero/Wownero (0.0.1):
- FlutterMacOS
- desktop_drop (0.0.1):
- FlutterMacOS
- device_info_plus (0.0.1):
- FlutterMacOS
- devicelocale (0.0.1):
- FlutterMacOS
- flutter_libepiccash (0.0.1):
- FlutterMacOS
- flutter_local_notifications (0.0.1):
- FlutterMacOS
- flutter_secure_storage_macos (3.3.1):
- flutter_secure_storage_macos (6.1.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- isar_flutter_libs (1.0.0):
- FlutterMacOS
- package_info_plus_macos (0.0.1):
- lelantus (0.0.1):
- FlutterMacOS
- path_provider_macos (0.0.1):
- package_info_plus (0.0.1):
- FlutterMacOS
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- ReachabilitySwift (5.0.0)
- share_plus_macos (0.0.1):
- FlutterMacOS
- shared_preferences_macos (0.0.1):
- share_plus (0.0.1):
- FlutterMacOS
- stack_wallet_backup (0.0.1):
- FlutterMacOS
@ -30,16 +82,22 @@ PODS:
- FlutterMacOS
DEPENDENCIES:
- connectivity_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus_macos/macos`)
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
- cw_monero (from `Flutter/ephemeral/.symlinks/plugins/cw_monero/macos`)
- cw_shared_external (from `Flutter/ephemeral/.symlinks/plugins/cw_shared_external/macos`)
- cw_wownero (from `Flutter/ephemeral/.symlinks/plugins/cw_wownero/macos`)
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- devicelocale (from `Flutter/ephemeral/.symlinks/plugins/devicelocale/macos`)
- flutter_libepiccash (from `Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos`)
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- isar_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos`)
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`)
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
- share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`)
- shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`)
- lelantus (from `Flutter/ephemeral/.symlinks/plugins/lelantus/macos`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- stack_wallet_backup (from `Flutter/ephemeral/.symlinks/plugins/stack_wallet_backup/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- wakelock_macos (from `Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos`)
@ -50,8 +108,20 @@ SPEC REPOS:
- ReachabilitySwift
EXTERNAL SOURCES:
connectivity_plus_macos:
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus_macos/macos
connectivity_plus:
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
cw_monero:
:path: Flutter/ephemeral/.symlinks/plugins/cw_monero/macos
cw_shared_external:
:path: Flutter/ephemeral/.symlinks/plugins/cw_shared_external/macos
cw_wownero:
:path: Flutter/ephemeral/.symlinks/plugins/cw_wownero/macos
desktop_drop:
:path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos
device_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
devicelocale:
:path: Flutter/ephemeral/.symlinks/plugins/devicelocale/macos
flutter_libepiccash:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos
flutter_local_notifications:
@ -62,14 +132,14 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral
isar_flutter_libs:
:path: Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos
package_info_plus_macos:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
path_provider_macos:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
share_plus_macos:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos
shared_preferences_macos:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos
lelantus:
:path: Flutter/ephemeral/.symlinks/plugins/lelantus/macos
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
share_plus:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
stack_wallet_backup:
:path: Flutter/ephemeral/.symlinks/plugins/stack_wallet_backup/macos
url_launcher_macos:
@ -80,22 +150,28 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/window_size/macos
SPEC CHECKSUMS:
connectivity_plus_macos: f6e86fd000e971d361e54b5afcadc8c8fa773308
flutter_libepiccash: b33f7396504712b513b8ff019a3f6f3bdae54cfb
connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747
cw_monero: a3442556ad3c06365c912735e4a23942a28692b1
cw_shared_external: 1f631d1132521baac5f4caed43176fa10d4e0d8b
cw_wownero: b4adb1e701fc363de27fa222fcaf4eff6f5fa63a
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225
flutter_libepiccash: 9113ac75dd325f8bcf00bc3ab583c7fc2780cf3c
flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4
flutter_secure_storage_macos: 6ceee8fbc7f484553ad17f79361b556259df89aa
FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811
isar_flutter_libs: 1948109973b6c2e46d6196b1537688a36a6edeac
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
isar_flutter_libs: 43385c99864c168fadba7c9adeddc5d38838ca6a
lelantus: 3dfbf92b1e66b3573494dfe3d6a21c4988b5361b
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
stack_wallet_backup: 6ebc60b1bdcf11cf1f1cbad9aa78332e1e15778c
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451
wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
COCOAPODS: 1.11.3

View file

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
@ -21,15 +21,27 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
36299DF6FDF6725B2B9C51D5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BB87EF657A3ADFB1CE3E959 /* Pods_Runner.framework */; };
B98151812A674022009D013C /* mobileliblelantus.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B98151802A674022009D013C /* mobileliblelantus.framework */; };
B98151822A67402A009D013C /* mobileliblelantus.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = B98151802A674022009D013C /* mobileliblelantus.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B98151842A674143009D013C /* libsqlite3.0.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B98151832A674143009D013C /* libsqlite3.0.tbd */; };
BFD0376C00E1FFD46376BB9D /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9206484E84CB0AD93E3E68CA /* Pods_RunnerTests.framework */; };
F653CA022D33E8B60E11A9F3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E6036BF01BF05EA773C76D22 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC10EC2044A3C60003C045;
remoteInfo = Runner;
};
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
@ -46,6 +58,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
B98151822A67402A009D013C /* mobileliblelantus.framework in Bundle Framework */,
);
name = "Bundle Framework";
runOnlyForDeploymentPostprocessing = 0;
@ -53,9 +66,14 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0FA914E59929120BA65E8403 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
174539D042E7AC2AB25A83EB /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
27CB73AACA5743180CC6CD50 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* Stack Wallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stack Wallet.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10ED2044A3C60003C045 /* Stack Wallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Stack Wallet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@ -67,26 +85,47 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
6BB87EF657A3ADFB1CE3E959 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
41EE721BF40B8DE895352A2C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
937DF254AD7EDA15AFE96BD9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
9206484E84CB0AD93E3E68CA /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
BC4589C48A71C3A1A477DD76 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
EA2D897BC13EBFB1DE697D5C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
ACB8E553D75AA4AC9A7656CE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
B98151802A674022009D013C /* mobileliblelantus.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = mobileliblelantus.framework; path = ../crypto_plugins/flutter_liblelantus/scripts/macos/mobileliblelantus/mobileliblelantus.framework; sourceTree = "<group>"; };
B98151832A674143009D013C /* libsqlite3.0.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.0.tbd; path = usr/lib/libsqlite3.0.tbd; sourceTree = SDKROOT; };
BF5E76865ACB46314AC27D8F /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
E6036BF01BF05EA773C76D22 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
331C80D2294CF70F00263BE5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
BFD0376C00E1FFD46376BB9D /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EA2044A3C60003C045 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
36299DF6FDF6725B2B9C51D5 /* Pods_Runner.framework in Frameworks */,
B98151842A674143009D013C /* libsqlite3.0.tbd in Frameworks */,
B98151812A674022009D013C /* mobileliblelantus.framework in Frameworks */,
F653CA022D33E8B60E11A9F3 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C80D6294CF71000263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
33BA886A226E78AF003329D5 /* Configs */ = {
isa = PBXGroup;
children = (
@ -103,9 +142,10 @@
children = (
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
9000119722579F22067B9BC0 /* Pods */,
F0D4A0626F78BE1EF2A1E0D6 /* Pods */,
);
sourceTree = "<group>";
};
@ -113,6 +153,7 @@
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* Stack Wallet.app */,
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@ -152,39 +193,63 @@
path = Runner;
sourceTree = "<group>";
};
9000119722579F22067B9BC0 /* Pods */ = {
isa = PBXGroup;
children = (
EA2D897BC13EBFB1DE697D5C /* Pods-Runner.debug.xcconfig */,
937DF254AD7EDA15AFE96BD9 /* Pods-Runner.release.xcconfig */,
BC4589C48A71C3A1A477DD76 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
6BB87EF657A3ADFB1CE3E959 /* Pods_Runner.framework */,
B98151832A674143009D013C /* libsqlite3.0.tbd */,
B98151802A674022009D013C /* mobileliblelantus.framework */,
E6036BF01BF05EA773C76D22 /* Pods_Runner.framework */,
9206484E84CB0AD93E3E68CA /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
F0D4A0626F78BE1EF2A1E0D6 /* Pods */ = {
isa = PBXGroup;
children = (
41EE721BF40B8DE895352A2C /* Pods-Runner.debug.xcconfig */,
0FA914E59929120BA65E8403 /* Pods-Runner.release.xcconfig */,
ACB8E553D75AA4AC9A7656CE /* Pods-Runner.profile.xcconfig */,
BF5E76865ACB46314AC27D8F /* Pods-RunnerTests.debug.xcconfig */,
174539D042E7AC2AB25A83EB /* Pods-RunnerTests.release.xcconfig */,
27CB73AACA5743180CC6CD50 /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
FF8CC09C83E12FC1C6EE6A8F /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C80DA294CF71000263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
33CC10EC2044A3C60003C045 /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
DF80A3E256A63BF2D2008937 /* [CP] Check Pods Manifest.lock */,
AAFD86C9DC38B4393BC9D8E0 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
8D7CC24E5AE846869656D4D1 /* [CP] Embed Pods Frameworks */,
529691D83C3BADE14E2EAC03 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -206,6 +271,10 @@
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 33CC10EC2044A3C60003C045;
};
33CC10EC2044A3C60003C045 = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
@ -236,12 +305,20 @@
projectRoot = "";
targets = (
33CC10EC2044A3C60003C045 /* Runner */,
331C80D4294CF70F00263BE5 /* RunnerTests */,
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C80D3294CF70F00263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EB2044A3C60003C045 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -256,6 +333,7 @@
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -291,7 +369,7 @@
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
8D7CC24E5AE846869656D4D1 /* [CP] Embed Pods Frameworks */ = {
529691D83C3BADE14E2EAC03 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -308,7 +386,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
DF80A3E256A63BF2D2008937 /* [CP] Check Pods Manifest.lock */ = {
AAFD86C9DC38B4393BC9D8E0 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -330,9 +408,39 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
FF8CC09C83E12FC1C6EE6A8F /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C80D1294CF70F00263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10E92044A3C60003C045 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -346,6 +454,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC10EC2044A3C60003C045 /* Runner */;
targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
};
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
@ -366,6 +479,51 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = BF5E76865ACB46314AC27D8F /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackWallet.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Stack Wallet.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/stack_wallet";
};
name = Debug;
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 174539D042E7AC2AB25A83EB /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackWallet.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Stack Wallet.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/stack_wallet";
};
name = Release;
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 27CB73AACA5743180CC6CD50 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackWallet.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Stack Wallet.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/stack_wallet";
};
name = Profile;
};
338D0CE9231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
@ -404,9 +562,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
@ -421,6 +581,30 @@
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/connectivity_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/cw_monero\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/cw_shared_external\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/cw_wownero\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/desktop_drop\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/device_info_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/devicelocale\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_libepiccash\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_local_notifications\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_secure_storage_macos\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/isar_flutter_libs\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/lelantus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/package_info_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/share_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/stack_wallet_backup\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_macos\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/wakelock_macos\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/window_size\"",
"\"${PROJECT_DIR}/../crypto_plugins/flutter_liblelantus/scripts/macos/mobileliblelantus\"",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -483,10 +667,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
@ -530,9 +715,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
@ -547,6 +734,30 @@
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/connectivity_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/cw_monero\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/cw_shared_external\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/cw_wownero\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/desktop_drop\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/device_info_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/devicelocale\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_libepiccash\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_local_notifications\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_secure_storage_macos\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/isar_flutter_libs\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/lelantus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/package_info_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/share_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/stack_wallet_backup\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_macos\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/wakelock_macos\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/window_size\"",
"\"${PROJECT_DIR}/../crypto_plugins/flutter_liblelantus/scripts/macos/mobileliblelantus\"",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -567,6 +778,30 @@
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/connectivity_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/cw_monero\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/cw_shared_external\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/cw_wownero\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/desktop_drop\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/device_info_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/devicelocale\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_libepiccash\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_local_notifications\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_secure_storage_macos\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/isar_flutter_libs\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/lelantus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/package_info_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/share_plus\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/stack_wallet_backup\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/url_launcher_macos\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/wakelock_macos\"",
"\"${PODS_CONFIGURATION_BUILD_DIR}/window_size\"",
"\"${PROJECT_DIR}/../crypto_plugins/flutter_liblelantus/scripts/macos/mobileliblelantus\"",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -596,6 +831,16 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C80DB294CF71000263BE5 /* Debug */,
331C80DC294CF71000263BE5 /* Release */,
331C80DD294CF71000263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View file

@ -37,6 +37,17 @@
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction

Some files were not shown because too many files have changed in this diff Show more