mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-16 17:27:39 +00:00
Merge remote-tracking branch 'origin_sw/staging' into wallets_refactor
# Conflicts: # crypto_plugins/flutter_libepiccash # lib/db/isar/main_db.dart # pubspec.lock # pubspec.yaml # test/services/coins/firo/firo_wallet_test.mocks.dart # test/widget_tests/transaction_card_test.mocks.dart
This commit is contained in:
commit
b88c073e22
115 changed files with 19088 additions and 5721 deletions
4
.github/workflows/test.yaml
vendored
4
.github/workflows/test.yaml
vendored
|
@ -8,12 +8,12 @@ jobs:
|
|||
- name: Prepare repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
flutter-version: '3.10.3'
|
||||
flutter-version: '3.10.6'
|
||||
channel: 'stable'
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.10.3'
|
||||
flutter-version: '3.10.6'
|
||||
channel: 'stable'
|
||||
- name: Setup | Rust
|
||||
uses: ATiltedTree/setup-rust@v1
|
||||
|
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -6,4 +6,4 @@
|
|||
url = https://github.com/cypherstack/flutter_libmonero.git
|
||||
[submodule "crypto_plugins/flutter_liblelantus"]
|
||||
path = crypto_plugins/flutter_liblelantus
|
||||
url = https://github.com/cypherstack/flutter_liblelantus.git
|
||||
url = https://github.com/cypherstack/flutter_liblelantus.git
|
||||
|
|
3
assets/svg/cashfusion-icon.svg
Normal file
3
assets/svg/cashfusion-icon.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.4 KiB |
3
assets/svg/fusing.svg
Normal file
3
assets/svg/fusing.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.768 12.7778C22.0427 9.72145 19.529 7.28212 16.4516 6.67196C14.9906 6.37118 13.4811 6.47323 12.0731 6.95663C12.3953 4.84042 13.7811 3.04089 15.7469 2.19569C16.3218 1.94862 16.1929 1.09999 15.575 1.04069C14.6349 0.944011 13.6895 1.01383 12.7765 1.22868C9.68399 1.95571 7.28204 4.46938 6.63321 7.54852C6.33243 9.00946 6.47423 10.5177 6.95548 11.9227C4.84012 11.6005 3.04059 10.2169 2.19755 8.24891C1.95048 7.67743 1.10176 7.80633 1.04265 8.42079C0.945952 9.3618 1.01579 10.3071 1.23066 11.2224C1.95598 14.2787 4.46965 16.7181 7.54707 17.3282C9.00801 17.629 10.5175 17.5269 11.9256 17.0435C11.6033 19.1598 10.2176 20.9593 8.25176 21.8045C7.67684 22.0515 7.80574 22.9002 8.42363 22.9595C9.36379 23.0562 10.3091 22.9863 11.2222 22.7715C14.2786 22.0462 16.7179 19.5325 17.328 16.4551C17.6288 14.9941 17.5268 13.4847 17.0434 12.0766C19.1596 12.3988 20.9591 13.7846 21.8043 15.7504C22.0514 16.3253 22.9 16.1964 22.9593 15.5785C23.0559 14.6384 22.9828 13.6931 22.768 12.7778ZM11.9613 14.0626C10.8613 14.0626 9.89884 13.1388 9.89884 12.0001C9.89884 10.8614 10.8227 9.93758 11.9613 9.93758C13.1 9.93758 14.0625 10.8614 14.0625 12.0001C14.0625 13.1388 13.1387 14.0626 11.9613 14.0626Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
3
assets/svg/peers.svg
Normal file
3
assets/svg/peers.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.7 12.0002C11.1303 12.0002 13.1 10.0302 13.1 7.6002C13.1 5.17023 11.1303 3.2002 8.7 3.2002C6.26969 3.2002 4.3 5.17023 4.3 7.6002C4.3 10.0302 6.26969 12.0002 8.7 12.0002ZM10.4428 13.6502H6.95719C3.66647 13.6502 1 16.3177 1 19.6074C1 20.2674 1.5335 20.8002 2.19144 20.8002H15.2072C15.8672 20.8002 16.4 20.2674 16.4 19.6074C16.4 16.3177 13.7325 13.6502 10.4428 13.6502ZM17.4691 14.2002H14.9305C16.51 15.4961 17.5 17.4349 17.5 19.6074C17.5 20.0474 17.3694 20.453 17.1562 20.8002H21.9C22.5084 20.8002 23 20.3052 23 19.6693C23 16.6614 20.5387 14.2002 17.4691 14.2002ZM15.85 12.0002C17.9778 12.0002 19.7 10.278 19.7 8.1502C19.7 6.02238 17.9778 4.3002 15.85 4.3002C14.9868 4.3002 14.1986 4.59427 13.5565 5.07398C13.9525 5.83435 14.2 6.68582 14.2 7.6002C14.2 8.8212 13.7899 9.94251 13.1141 10.8559C13.8116 11.5602 14.7775 12.0002 15.85 12.0002Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 966 B |
11
assets/svg/tx-cashfusion.svg
Normal file
11
assets/svg/tx-cashfusion.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.6 KiB |
3
assets/svg/up-from-line.svg
Normal file
3
assets/svg/up-from-line.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.875 16.8755H2.08633C1.36574 16.8755 0.75 17.4899 0.75 18.2118C0.75 18.9337 1.36574 19.6255 2.08633 19.6255H15.875C16.6355 19.6255 17.25 19.011 17.25 18.2505C17.25 17.4899 16.6355 16.8755 15.875 16.8755ZM3.15625 8.61687H6.25V14.1212C6.25 14.8813 6.86574 15.4979 7.625 15.4979H10.375C11.1343 15.4979 11.75 14.8813 11.75 14.1212V8.61687H14.8438C15.2553 8.61687 15.6279 8.3716 15.7912 7.99339C15.9537 7.61514 15.8765 7.1757 15.5938 6.8762L9.75006 0.684407C9.36068 0.271864 8.63975 0.271864 8.25002 0.684407L2.40627 6.8762C2.12363 7.17587 2.04641 7.61527 2.20887 7.99339C2.37207 8.37195 2.74461 8.61687 3.15625 8.61687Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 747 B |
|
@ -1 +1 @@
|
|||
Subproject commit be2e6bca47bb5d6e0aef1bf2ace2dd4fa7bc8038
|
||||
Subproject commit f677dec0b34d3f9fe8fce2bc8ff5c508c3f3bb9a
|
|
@ -1 +1 @@
|
|||
Subproject commit 1f8fde935bbb23585477b0cb884bee9be6d12a87
|
||||
Subproject commit 9cd241b5ea142e21c01dd7639b42603281c43287
|
|
@ -13,6 +13,7 @@ import 'package:flutter_native_splash/cli_commands.dart';
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/exceptions/main_db/main_db_exception.dart';
|
||||
import 'package:stackwallet/models/isar/models/block_explorer.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/contact_entry.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/models/isar/ordinal.dart';
|
||||
|
@ -278,6 +279,14 @@ class MainDB {
|
|||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> getUTXOs(String walletId) =>
|
||||
isar.utxos.where().walletIdEqualTo(walletId);
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> getUTXOsByAddress(
|
||||
String walletId, String address) =>
|
||||
isar.utxos
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.addressEqualTo(address);
|
||||
|
||||
Future<void> putUTXO(UTXO utxo) => isar.writeTxn(() async {
|
||||
await isar.utxos.put(utxo);
|
||||
});
|
||||
|
@ -413,6 +422,8 @@ class MainDB {
|
|||
//
|
||||
Future<void> deleteWalletBlockchainData(String walletId) async {
|
||||
final transactionCount = await getTransactions(walletId).count();
|
||||
final transactionCountV2 =
|
||||
await isar.transactionV2s.where().walletIdEqualTo(walletId).count();
|
||||
final addressCount = await getAddresses(walletId).count();
|
||||
final utxoCount = await getUTXOs(walletId).count();
|
||||
final lelantusCoinCount =
|
||||
|
@ -431,6 +442,18 @@ class MainDB {
|
|||
await isar.transactions.deleteAll(txnIds);
|
||||
}
|
||||
|
||||
// transactions V2
|
||||
for (int i = 0; i < transactionCountV2; i += paginateLimit) {
|
||||
final txnIds = await isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.offset(i)
|
||||
.limit(paginateLimit)
|
||||
.idProperty()
|
||||
.findAll();
|
||||
await isar.transactionV2s.deleteAll(txnIds);
|
||||
}
|
||||
|
||||
// addresses
|
||||
for (int i = 0; i < addressCount; i += paginateLimit) {
|
||||
final addressIds = await getAddresses(walletId)
|
||||
|
@ -535,6 +558,35 @@ class MainDB {
|
|||
}
|
||||
}
|
||||
|
||||
Future<List<int>> updateOrPutTransactionV2s(
|
||||
List<TransactionV2> transactions,
|
||||
) async {
|
||||
try {
|
||||
List<int> ids = [];
|
||||
await isar.writeTxn(() async {
|
||||
for (final tx in transactions) {
|
||||
final storedTx = await isar.transactionV2s
|
||||
.where()
|
||||
.txidWalletIdEqualTo(tx.txid, tx.walletId)
|
||||
.findFirst();
|
||||
|
||||
Id id;
|
||||
if (storedTx == null) {
|
||||
id = await isar.transactionV2s.put(tx);
|
||||
} else {
|
||||
tx.id = storedTx.id;
|
||||
await isar.transactionV2s.delete(storedTx.id);
|
||||
id = await isar.transactionV2s.put(tx);
|
||||
}
|
||||
ids.add(id);
|
||||
}
|
||||
});
|
||||
return ids;
|
||||
} catch (e) {
|
||||
throw MainDBException("failed updateOrPutAddresses: $transactions", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Ethereum =======================================================
|
||||
|
||||
// eth contracts
|
||||
|
|
|
@ -26,7 +26,6 @@ class EthTokenTxDto {
|
|||
required this.topics,
|
||||
required this.data,
|
||||
required this.articulatedLog,
|
||||
required this.compressedLog,
|
||||
required this.transactionHash,
|
||||
required this.transactionIndex,
|
||||
});
|
||||
|
@ -44,7 +43,6 @@ class EthTokenTxDto {
|
|||
map['articulatedLog'] as Map,
|
||||
),
|
||||
),
|
||||
compressedLog = map['compressedLog'] as String,
|
||||
transactionHash = map['transactionHash'] as String,
|
||||
transactionIndex = map['transactionIndex'] as int;
|
||||
|
||||
|
@ -54,7 +52,6 @@ class EthTokenTxDto {
|
|||
final List<String> topics;
|
||||
final String data;
|
||||
final ArticulatedLog? articulatedLog;
|
||||
final String compressedLog;
|
||||
final String transactionHash;
|
||||
final int transactionIndex;
|
||||
|
||||
|
@ -76,7 +73,6 @@ class EthTokenTxDto {
|
|||
topics: topics ?? this.topics,
|
||||
data: data ?? this.data,
|
||||
articulatedLog: articulatedLog ?? this.articulatedLog,
|
||||
compressedLog: compressedLog ?? this.compressedLog,
|
||||
transactionHash: transactionHash ?? this.transactionHash,
|
||||
transactionIndex: transactionIndex ?? this.transactionIndex,
|
||||
);
|
||||
|
@ -89,7 +85,6 @@ class EthTokenTxDto {
|
|||
map['topics'] = topics;
|
||||
map['data'] = data;
|
||||
map['articulatedLog'] = articulatedLog?.toMap();
|
||||
map['compressedLog'] = compressedLog;
|
||||
map['transactionHash'] = transactionHash;
|
||||
map['transactionIndex'] = transactionIndex;
|
||||
return map;
|
||||
|
|
|
@ -29,7 +29,6 @@ class EthTxDTO {
|
|||
required this.maxPriorityFeePerGas,
|
||||
required this.isError,
|
||||
required this.hasToken,
|
||||
required this.compressedTx,
|
||||
required this.gasCost,
|
||||
required this.gasUsed,
|
||||
});
|
||||
|
@ -42,16 +41,15 @@ class EthTxDTO {
|
|||
timestamp: map['timestamp'] as int,
|
||||
from: map['from'] as String,
|
||||
to: map['to'] as String,
|
||||
value: _amountFromJsonNum(map['value']),
|
||||
gas: _amountFromJsonNum(map['gas']),
|
||||
gasPrice: _amountFromJsonNum(map['gasPrice']),
|
||||
value: _amountFromJsonNum(map['value'])!,
|
||||
gas: _amountFromJsonNum(map['gas'])!,
|
||||
gasPrice: _amountFromJsonNum(map['gasPrice'])!,
|
||||
maxFeePerGas: _amountFromJsonNum(map['maxFeePerGas']),
|
||||
maxPriorityFeePerGas: _amountFromJsonNum(map['maxPriorityFeePerGas']),
|
||||
isError: map['isError'] as int,
|
||||
hasToken: map['hasToken'] as int,
|
||||
compressedTx: map['compressedTx'] as String,
|
||||
gasCost: _amountFromJsonNum(map['gasCost']),
|
||||
gasUsed: _amountFromJsonNum(map['gasUsed']),
|
||||
isError: map['isError'] as bool? ?? false,
|
||||
hasToken: map['hasToken'] as bool? ?? false,
|
||||
gasCost: _amountFromJsonNum(map['gasCost'])!,
|
||||
gasUsed: _amountFromJsonNum(map['gasUsed'])!,
|
||||
);
|
||||
|
||||
final String hash;
|
||||
|
@ -64,17 +62,19 @@ class EthTxDTO {
|
|||
final Amount value;
|
||||
final Amount gas;
|
||||
final Amount gasPrice;
|
||||
final Amount maxFeePerGas;
|
||||
final Amount maxPriorityFeePerGas;
|
||||
final int isError;
|
||||
final int hasToken;
|
||||
final String compressedTx;
|
||||
final Amount? maxFeePerGas;
|
||||
final Amount? maxPriorityFeePerGas;
|
||||
final bool isError;
|
||||
final bool hasToken;
|
||||
final Amount gasCost;
|
||||
final Amount gasUsed;
|
||||
|
||||
static Amount _amountFromJsonNum(dynamic json) {
|
||||
static Amount? _amountFromJsonNum(dynamic json) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
return Amount(
|
||||
rawValue: BigInt.from(json as num),
|
||||
rawValue: BigInt.parse(json.toString()),
|
||||
fractionDigits: Coin.ethereum.decimals,
|
||||
);
|
||||
}
|
||||
|
@ -92,8 +92,8 @@ class EthTxDTO {
|
|||
Amount? gasPrice,
|
||||
Amount? maxFeePerGas,
|
||||
Amount? maxPriorityFeePerGas,
|
||||
int? isError,
|
||||
int? hasToken,
|
||||
bool? isError,
|
||||
bool? hasToken,
|
||||
String? compressedTx,
|
||||
Amount? gasCost,
|
||||
Amount? gasUsed,
|
||||
|
@ -113,7 +113,6 @@ class EthTxDTO {
|
|||
maxPriorityFeePerGas: maxPriorityFeePerGas ?? this.maxPriorityFeePerGas,
|
||||
isError: isError ?? this.isError,
|
||||
hasToken: hasToken ?? this.hasToken,
|
||||
compressedTx: compressedTx ?? this.compressedTx,
|
||||
gasCost: gasCost ?? this.gasCost,
|
||||
gasUsed: gasUsed ?? this.gasUsed,
|
||||
);
|
||||
|
@ -134,7 +133,6 @@ class EthTxDTO {
|
|||
map['maxPriorityFeePerGas'] = maxPriorityFeePerGas.toString();
|
||||
map['isError'] = isError;
|
||||
map['hasToken'] = hasToken;
|
||||
map['compressedTx'] = compressedTx;
|
||||
map['gasCost'] = gasCost.toString();
|
||||
map['gasUsed'] = gasUsed.toString();
|
||||
return map;
|
||||
|
|
|
@ -141,10 +141,10 @@ class CachedElectrumX {
|
|||
await box.put(txHash, result);
|
||||
}
|
||||
|
||||
Logging.instance.log("using fetched result", level: LogLevel.Info);
|
||||
// Logging.instance.log("using fetched result", level: LogLevel.Info);
|
||||
return result;
|
||||
} else {
|
||||
Logging.instance.log("using cached result", level: LogLevel.Info);
|
||||
// Logging.instance.log("using cached result", level: LogLevel.Info);
|
||||
return Map<String, dynamic>.from(cachedTx);
|
||||
}
|
||||
} catch (e, s) {
|
||||
|
|
|
@ -387,7 +387,14 @@ class ElectrumX {
|
|||
throw jsonRpcResponse.exception!;
|
||||
}
|
||||
|
||||
final response = jsonRpcResponse.data as List;
|
||||
final List<dynamic> response;
|
||||
try {
|
||||
response = jsonRpcResponse.data as List;
|
||||
} catch (_) {
|
||||
throw Exception(
|
||||
"Expected json list but got a map: ${jsonRpcResponse.data}",
|
||||
);
|
||||
}
|
||||
|
||||
// check for errors, format and throw if there are any
|
||||
final List<String> errors = [];
|
||||
|
@ -726,6 +733,14 @@ class ElectrumX {
|
|||
return {"rawtx": response["result"] as String};
|
||||
}
|
||||
|
||||
if (response["result"] == null) {
|
||||
Logging.instance.log(
|
||||
"getTransaction($txHash) returned null response",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
throw 'getTransaction($txHash) returned null response';
|
||||
}
|
||||
|
||||
return Map<String, dynamic>.from(response["result"] as Map);
|
||||
} catch (e) {
|
||||
Logging.instance.log(
|
||||
|
|
187
lib/models/fusion_progress_ui_state.dart
Normal file
187
lib/models/fusion_progress_ui_state.dart
Normal file
|
@ -0,0 +1,187 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart';
|
||||
|
||||
class FusionProgressUIState extends ChangeNotifier {
|
||||
/// Whether we are able to connect to the server.
|
||||
bool _ableToConnect = false;
|
||||
|
||||
// _ableToConnect setter.
|
||||
set ableToConnect(bool ableToConnect) {
|
||||
_ableToConnect = ableToConnect;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool get done {
|
||||
if (!_ableToConnect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _done = (_connecting.status == CashFusionStatus.success) ||
|
||||
(_connecting.status == CashFusionStatus.failed);
|
||||
_done &= (_outputs.status == CashFusionStatus.success) ||
|
||||
(_outputs.status == CashFusionStatus.failed);
|
||||
_done &= (_peers.status == CashFusionStatus.success) ||
|
||||
(_peers.status == CashFusionStatus.failed);
|
||||
_done &= (_fusing.status == CashFusionStatus.success) ||
|
||||
(_fusing.status == CashFusionStatus.failed);
|
||||
_done &= (_complete.status == CashFusionStatus.success) ||
|
||||
(_complete.status == CashFusionStatus.failed);
|
||||
|
||||
_done &= (fusionState.status == CashFusionStatus.success) ||
|
||||
(fusionState.status == CashFusionStatus.failed);
|
||||
|
||||
return _done;
|
||||
}
|
||||
|
||||
bool get succeeded {
|
||||
if (!_ableToConnect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _succeeded = _connecting.status == CashFusionStatus.success;
|
||||
_succeeded &= _outputs.status == CashFusionStatus.success;
|
||||
_succeeded &= _peers.status == CashFusionStatus.success;
|
||||
_succeeded &= _fusing.status == CashFusionStatus.success;
|
||||
_succeeded &= _complete.status == CashFusionStatus.success;
|
||||
|
||||
_succeeded &= fusionState.status == CashFusionStatus.success;
|
||||
|
||||
return _succeeded;
|
||||
}
|
||||
|
||||
CashFusionState _connecting =
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null);
|
||||
CashFusionState get connecting => _connecting;
|
||||
void setConnecting(CashFusionState state, {bool shouldNotify = true}) {
|
||||
_connecting = state;
|
||||
_running = true;
|
||||
if (shouldNotify) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
CashFusionState _outputs =
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null);
|
||||
CashFusionState get outputs => _outputs;
|
||||
void setOutputs(CashFusionState state, {bool shouldNotify = true}) {
|
||||
_outputs = state;
|
||||
_running = true;
|
||||
if (shouldNotify) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
CashFusionState _peers =
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null);
|
||||
CashFusionState get peers => _peers;
|
||||
void setPeers(CashFusionState state, {bool shouldNotify = true}) {
|
||||
_peers = state;
|
||||
_running = true;
|
||||
if (shouldNotify) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
CashFusionState _fusing =
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null);
|
||||
CashFusionState get fusing => _fusing;
|
||||
void setFusing(CashFusionState state, {bool shouldNotify = true}) {
|
||||
_fusing = state;
|
||||
_running = true;
|
||||
if (shouldNotify) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
CashFusionState _complete =
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null);
|
||||
CashFusionState get complete => _complete;
|
||||
void setComplete(CashFusionState state, {bool shouldNotify = true}) {
|
||||
_complete = state;
|
||||
_running = true;
|
||||
if (shouldNotify) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
CashFusionState _fusionStatus =
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null);
|
||||
CashFusionState get fusionState => _fusionStatus;
|
||||
void setFusionState(CashFusionState state, {bool shouldNotify = true}) {
|
||||
_fusionStatus = state;
|
||||
_updateRunningState(state.status);
|
||||
if (shouldNotify) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// An int storing the number of successfully completed fusion rounds.
|
||||
int _fusionRoundsCompleted = 0;
|
||||
int get fusionRoundsCompleted => _fusionRoundsCompleted;
|
||||
set fusionRoundsCompleted(int fusionRoundsCompleted) {
|
||||
_fusionRoundsCompleted = fusionRoundsCompleted;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// A helper for incrementing the number of successfully completed fusion rounds.
|
||||
void incrementFusionRoundsCompleted() {
|
||||
_fusionRoundsCompleted++;
|
||||
_fusionRoundsFailed = 0; // Reset failed round count on success.
|
||||
_failed = false; // Reset failed flag on success.
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// An int storing the number of failed fusion rounds.
|
||||
int _fusionRoundsFailed = 0;
|
||||
int get fusionRoundsFailed => _fusionRoundsFailed;
|
||||
set fusionRoundsFailed(int fusionRoundsFailed) {
|
||||
_fusionRoundsFailed = fusionRoundsFailed;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// A helper for incrementing the number of failed fusion rounds.
|
||||
void incrementFusionRoundsFailed() {
|
||||
_fusionRoundsFailed++;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// A flag indicating that fusion has stopped because the maximum number of
|
||||
/// consecutive failed fusion rounds has been reached.
|
||||
///
|
||||
/// Set from the interface. I didn't want to have to configure
|
||||
///
|
||||
/// Used to be named maxConsecutiveFusionRoundsFailed.
|
||||
bool _failed = false;
|
||||
bool get failed => _failed;
|
||||
void setFailed(bool failed, {bool shouldNotify = true}) {
|
||||
_failed = failed;
|
||||
if (shouldNotify) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// A flag indicating that fusion is running.
|
||||
bool _running = false;
|
||||
bool get running => _running;
|
||||
void setRunning(bool running, {bool shouldNotify = true}) {
|
||||
_running = running;
|
||||
if (shouldNotify) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper method for setting the running flag.
|
||||
///
|
||||
/// Sets the running flag to true if the status is running. Sets the flag to
|
||||
/// false if succeeded or failed or the global failed flag is set.
|
||||
void _updateRunningState(CashFusionStatus status) {
|
||||
if (status == CashFusionStatus.running) {
|
||||
_running = true;
|
||||
} else if (((status == CashFusionStatus.success ||
|
||||
status == CashFusionStatus.failed) &&
|
||||
(done || succeeded)) ||
|
||||
_failed) {
|
||||
_running = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -70,6 +70,30 @@ class Address extends CryptoCurrencyAddress {
|
|||
subType == AddressSubType.paynymSend ||
|
||||
subType == AddressSubType.paynymReceive;
|
||||
|
||||
/// If called on an [Address] already stored in the DB be sure to update the
|
||||
/// [transactions] Isar Links if required
|
||||
Address copyWith({
|
||||
String? walletId,
|
||||
String? value,
|
||||
List<byte>? publicKey,
|
||||
int? derivationIndex,
|
||||
AddressType? type,
|
||||
AddressSubType? subType,
|
||||
DerivationPath? derivationPath,
|
||||
String? otherData,
|
||||
}) {
|
||||
return Address(
|
||||
walletId: walletId ?? this.walletId,
|
||||
value: value ?? this.value,
|
||||
publicKey: publicKey ?? this.publicKey,
|
||||
derivationIndex: derivationIndex ?? this.derivationIndex,
|
||||
type: type ?? this.type,
|
||||
subType: subType ?? this.subType,
|
||||
derivationPath: derivationPath ?? this.derivationPath,
|
||||
otherData: otherData ?? this.otherData,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => "{ "
|
||||
"id: $id, "
|
||||
|
|
|
@ -22,7 +22,6 @@ part 'transaction.g.dart';
|
|||
|
||||
@Collection()
|
||||
class Transaction {
|
||||
|
||||
Transaction({
|
||||
required this.walletId,
|
||||
required this.txid,
|
||||
|
@ -252,5 +251,6 @@ enum TransactionSubType {
|
|||
bip47Notification, // bip47 payment code notification transaction flag
|
||||
mint, // firo specific
|
||||
join, // firo specific
|
||||
ethToken; // eth token
|
||||
ethToken, // eth token
|
||||
cashFusion;
|
||||
}
|
||||
|
|
|
@ -364,6 +364,7 @@ const _TransactionsubTypeEnumValueMap = {
|
|||
'mint': 2,
|
||||
'join': 3,
|
||||
'ethToken': 4,
|
||||
'cashFusion': 5,
|
||||
};
|
||||
const _TransactionsubTypeValueEnumMap = {
|
||||
0: TransactionSubType.none,
|
||||
|
@ -371,6 +372,7 @@ const _TransactionsubTypeValueEnumMap = {
|
|||
2: TransactionSubType.mint,
|
||||
3: TransactionSubType.join,
|
||||
4: TransactionSubType.ethToken,
|
||||
5: TransactionSubType.cashFusion,
|
||||
};
|
||||
const _TransactiontypeEnumValueMap = {
|
||||
'outgoing': 0,
|
||||
|
|
124
lib/models/isar/models/blockchain_data/v2/input_v2.dart
Normal file
124
lib/models/isar/models/blockchain_data/v2/input_v2.dart
Normal file
|
@ -0,0 +1,124 @@
|
|||
import 'package:isar/isar.dart';
|
||||
|
||||
part 'input_v2.g.dart';
|
||||
|
||||
@Embedded()
|
||||
class OutpointV2 {
|
||||
late final String txid;
|
||||
late final int vout;
|
||||
|
||||
OutpointV2();
|
||||
|
||||
static OutpointV2 isarCantDoRequiredInDefaultConstructor({
|
||||
required String txid,
|
||||
required int vout,
|
||||
}) =>
|
||||
OutpointV2()
|
||||
..vout = vout
|
||||
..txid = txid;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'OutpointV2(\n'
|
||||
' txid: $txid,\n'
|
||||
' vout: $vout,\n'
|
||||
')';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is OutpointV2 && other.txid == txid && other.vout == vout;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return Object.hash(
|
||||
txid.hashCode,
|
||||
vout.hashCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Embedded()
|
||||
class InputV2 {
|
||||
late final String? scriptSigHex;
|
||||
late final int? sequence;
|
||||
late final OutpointV2? outpoint;
|
||||
late final List<String> addresses;
|
||||
late final String valueStringSats;
|
||||
|
||||
late final String? coinbase;
|
||||
|
||||
late final String? witness;
|
||||
late final String? innerRedeemScriptAsm;
|
||||
|
||||
late final bool walletOwns;
|
||||
|
||||
@ignore
|
||||
BigInt get value => BigInt.parse(valueStringSats);
|
||||
|
||||
InputV2();
|
||||
|
||||
static InputV2 isarCantDoRequiredInDefaultConstructor({
|
||||
required String? scriptSigHex,
|
||||
required int? sequence,
|
||||
required OutpointV2? outpoint,
|
||||
required List<String> addresses,
|
||||
required String valueStringSats,
|
||||
required String? witness,
|
||||
required String? innerRedeemScriptAsm,
|
||||
required String? coinbase,
|
||||
required bool walletOwns,
|
||||
}) =>
|
||||
InputV2()
|
||||
..scriptSigHex = scriptSigHex
|
||||
..sequence = sequence
|
||||
..outpoint = outpoint
|
||||
..addresses = List.unmodifiable(addresses)
|
||||
..valueStringSats = valueStringSats
|
||||
..witness = witness
|
||||
..innerRedeemScriptAsm = innerRedeemScriptAsm
|
||||
..coinbase = coinbase
|
||||
..walletOwns = walletOwns;
|
||||
|
||||
InputV2 copyWith({
|
||||
String? scriptSigHex,
|
||||
int? sequence,
|
||||
OutpointV2? outpoint,
|
||||
List<String>? addresses,
|
||||
String? valueStringSats,
|
||||
String? coinbase,
|
||||
String? witness,
|
||||
String? innerRedeemScriptAsm,
|
||||
bool? walletOwns,
|
||||
}) {
|
||||
return InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptSigHex: scriptSigHex ?? this.scriptSigHex,
|
||||
sequence: sequence ?? this.sequence,
|
||||
outpoint: outpoint ?? this.outpoint,
|
||||
addresses: addresses ?? this.addresses,
|
||||
valueStringSats: valueStringSats ?? this.valueStringSats,
|
||||
coinbase: coinbase ?? this.coinbase,
|
||||
witness: witness ?? this.witness,
|
||||
innerRedeemScriptAsm: innerRedeemScriptAsm ?? this.innerRedeemScriptAsm,
|
||||
walletOwns: walletOwns ?? this.walletOwns,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'InputV2(\n'
|
||||
' scriptSigHex: $scriptSigHex,\n'
|
||||
' sequence: $sequence,\n'
|
||||
' outpoint: $outpoint,\n'
|
||||
' addresses: $addresses,\n'
|
||||
' valueStringSats: $valueStringSats,\n'
|
||||
' coinbase: $coinbase,\n'
|
||||
' witness: $witness,\n'
|
||||
' innerRedeemScriptAsm: $innerRedeemScriptAsm,\n'
|
||||
' walletOwns: $walletOwns,\n'
|
||||
')';
|
||||
}
|
||||
}
|
1574
lib/models/isar/models/blockchain_data/v2/input_v2.g.dart
Normal file
1574
lib/models/isar/models/blockchain_data/v2/input_v2.g.dart
Normal file
File diff suppressed because it is too large
Load diff
121
lib/models/isar/models/blockchain_data/v2/output_v2.dart
Normal file
121
lib/models/isar/models/blockchain_data/v2/output_v2.dart
Normal file
|
@ -0,0 +1,121 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
part 'output_v2.g.dart';
|
||||
|
||||
@Embedded()
|
||||
class OutputV2 {
|
||||
late final String scriptPubKeyHex;
|
||||
late final String valueStringSats;
|
||||
late final List<String> addresses;
|
||||
|
||||
late final bool walletOwns;
|
||||
|
||||
@ignore
|
||||
BigInt get value => BigInt.parse(valueStringSats);
|
||||
|
||||
OutputV2();
|
||||
|
||||
static OutputV2 isarCantDoRequiredInDefaultConstructor({
|
||||
required String scriptPubKeyHex,
|
||||
required String valueStringSats,
|
||||
required List<String> addresses,
|
||||
required bool walletOwns,
|
||||
}) =>
|
||||
OutputV2()
|
||||
..scriptPubKeyHex = scriptPubKeyHex
|
||||
..valueStringSats = valueStringSats
|
||||
..walletOwns = walletOwns
|
||||
..addresses = List.unmodifiable(addresses);
|
||||
|
||||
OutputV2 copyWith({
|
||||
String? scriptPubKeyHex,
|
||||
String? valueStringSats,
|
||||
List<String>? addresses,
|
||||
bool? walletOwns,
|
||||
}) {
|
||||
return OutputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptPubKeyHex: scriptPubKeyHex ?? this.scriptPubKeyHex,
|
||||
valueStringSats: valueStringSats ?? this.valueStringSats,
|
||||
addresses: addresses ?? this.addresses,
|
||||
walletOwns: walletOwns ?? this.walletOwns,
|
||||
);
|
||||
}
|
||||
|
||||
static OutputV2 fromElectrumXJson(
|
||||
Map<String, dynamic> json, {
|
||||
required bool walletOwns,
|
||||
required int decimalPlaces,
|
||||
}) {
|
||||
try {
|
||||
List<String> addresses = [];
|
||||
|
||||
if (json["scriptPubKey"]?["addresses"] is List) {
|
||||
for (final e in json["scriptPubKey"]["addresses"] as List) {
|
||||
addresses.add(e as String);
|
||||
}
|
||||
} else if (json["scriptPubKey"]?["address"] is String) {
|
||||
addresses.add(json["scriptPubKey"]?["address"] as String);
|
||||
}
|
||||
|
||||
return OutputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptPubKeyHex: json["scriptPubKey"]["hex"] as String,
|
||||
valueStringSats: parseOutputAmountString(
|
||||
json["value"].toString(),
|
||||
decimalPlaces: decimalPlaces,
|
||||
),
|
||||
addresses: addresses,
|
||||
walletOwns: walletOwns,
|
||||
);
|
||||
} catch (e) {
|
||||
throw Exception("Failed to parse OutputV2 from $json");
|
||||
}
|
||||
}
|
||||
|
||||
static String parseOutputAmountString(
|
||||
String amount, {
|
||||
required int decimalPlaces,
|
||||
}) {
|
||||
final temp = Decimal.parse(amount);
|
||||
if (temp < Decimal.zero) {
|
||||
throw Exception("Negative value found");
|
||||
}
|
||||
|
||||
final String valueStringSats;
|
||||
if (temp.isInteger) {
|
||||
valueStringSats = temp.toString();
|
||||
} else {
|
||||
valueStringSats = temp.shift(decimalPlaces).toBigInt().toString();
|
||||
}
|
||||
|
||||
return valueStringSats;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'OutputV2(\n'
|
||||
' scriptPubKeyHex: $scriptPubKeyHex,\n'
|
||||
' value: $value,\n'
|
||||
' walletOwns: $walletOwns,\n'
|
||||
' addresses: $addresses,\n'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
bool _listEquals<T, U>(List<T> a, List<U> b) {
|
||||
if (T != U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
617
lib/models/isar/models/blockchain_data/v2/output_v2.g.dart
Normal file
617
lib/models/isar/models/blockchain_data/v2/output_v2.g.dart
Normal file
|
@ -0,0 +1,617 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'output_v2.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// IsarEmbeddedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||
|
||||
const OutputV2Schema = Schema(
|
||||
name: r'OutputV2',
|
||||
id: -6134367361914065515,
|
||||
properties: {
|
||||
r'addresses': PropertySchema(
|
||||
id: 0,
|
||||
name: r'addresses',
|
||||
type: IsarType.stringList,
|
||||
),
|
||||
r'scriptPubKeyHex': PropertySchema(
|
||||
id: 1,
|
||||
name: r'scriptPubKeyHex',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'valueStringSats': PropertySchema(
|
||||
id: 2,
|
||||
name: r'valueStringSats',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'walletOwns': PropertySchema(
|
||||
id: 3,
|
||||
name: r'walletOwns',
|
||||
type: IsarType.bool,
|
||||
)
|
||||
},
|
||||
estimateSize: _outputV2EstimateSize,
|
||||
serialize: _outputV2Serialize,
|
||||
deserialize: _outputV2Deserialize,
|
||||
deserializeProp: _outputV2DeserializeProp,
|
||||
);
|
||||
|
||||
int _outputV2EstimateSize(
|
||||
OutputV2 object,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
var bytesCount = offsets.last;
|
||||
bytesCount += 3 + object.addresses.length * 3;
|
||||
{
|
||||
for (var i = 0; i < object.addresses.length; i++) {
|
||||
final value = object.addresses[i];
|
||||
bytesCount += value.length * 3;
|
||||
}
|
||||
}
|
||||
bytesCount += 3 + object.scriptPubKeyHex.length * 3;
|
||||
bytesCount += 3 + object.valueStringSats.length * 3;
|
||||
return bytesCount;
|
||||
}
|
||||
|
||||
void _outputV2Serialize(
|
||||
OutputV2 object,
|
||||
IsarWriter writer,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
writer.writeStringList(offsets[0], object.addresses);
|
||||
writer.writeString(offsets[1], object.scriptPubKeyHex);
|
||||
writer.writeString(offsets[2], object.valueStringSats);
|
||||
writer.writeBool(offsets[3], object.walletOwns);
|
||||
}
|
||||
|
||||
OutputV2 _outputV2Deserialize(
|
||||
Id id,
|
||||
IsarReader reader,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
final object = OutputV2();
|
||||
object.addresses = reader.readStringList(offsets[0]) ?? [];
|
||||
object.scriptPubKeyHex = reader.readString(offsets[1]);
|
||||
object.valueStringSats = reader.readString(offsets[2]);
|
||||
object.walletOwns = reader.readBool(offsets[3]);
|
||||
return object;
|
||||
}
|
||||
|
||||
P _outputV2DeserializeProp<P>(
|
||||
IsarReader reader,
|
||||
int propertyId,
|
||||
int offset,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
switch (propertyId) {
|
||||
case 0:
|
||||
return (reader.readStringList(offset) ?? []) as P;
|
||||
case 1:
|
||||
return (reader.readString(offset)) as P;
|
||||
case 2:
|
||||
return (reader.readString(offset)) as P;
|
||||
case 3:
|
||||
return (reader.readBool(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
}
|
||||
}
|
||||
|
||||
extension OutputV2QueryFilter
|
||||
on QueryBuilder<OutputV2, OutputV2, QFilterCondition> {
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesElementEqualTo(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'addresses',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesElementGreaterThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'addresses',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesElementLessThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'addresses',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesElementBetween(
|
||||
String lower,
|
||||
String upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'addresses',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesElementStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'addresses',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesElementEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'addresses',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesElementContains(String value, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'addresses',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesElementMatches(String pattern, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'addresses',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesElementIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'addresses',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesElementIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'addresses',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesLengthEqualTo(int length) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'addresses',
|
||||
length,
|
||||
true,
|
||||
length,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition> addressesIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'addresses',
|
||||
0,
|
||||
true,
|
||||
0,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'addresses',
|
||||
0,
|
||||
false,
|
||||
999999,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesLengthLessThan(
|
||||
int length, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'addresses',
|
||||
0,
|
||||
true,
|
||||
length,
|
||||
include,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesLengthGreaterThan(
|
||||
int length, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'addresses',
|
||||
length,
|
||||
include,
|
||||
999999,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
addressesLengthBetween(
|
||||
int lower,
|
||||
int upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'addresses',
|
||||
lower,
|
||||
includeLower,
|
||||
upper,
|
||||
includeUpper,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
scriptPubKeyHexEqualTo(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'scriptPubKeyHex',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
scriptPubKeyHexGreaterThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'scriptPubKeyHex',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
scriptPubKeyHexLessThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'scriptPubKeyHex',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
scriptPubKeyHexBetween(
|
||||
String lower,
|
||||
String upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'scriptPubKeyHex',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
scriptPubKeyHexStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'scriptPubKeyHex',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
scriptPubKeyHexEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'scriptPubKeyHex',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
scriptPubKeyHexContains(String value, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'scriptPubKeyHex',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
scriptPubKeyHexMatches(String pattern, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'scriptPubKeyHex',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
scriptPubKeyHexIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'scriptPubKeyHex',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
scriptPubKeyHexIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'scriptPubKeyHex',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
valueStringSatsEqualTo(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'valueStringSats',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
valueStringSatsGreaterThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'valueStringSats',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
valueStringSatsLessThan(
|
||||
String value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'valueStringSats',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
valueStringSatsBetween(
|
||||
String lower,
|
||||
String upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'valueStringSats',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
valueStringSatsStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'valueStringSats',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
valueStringSatsEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'valueStringSats',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
valueStringSatsContains(String value, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'valueStringSats',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
valueStringSatsMatches(String pattern, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'valueStringSats',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
valueStringSatsIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'valueStringSats',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition>
|
||||
valueStringSatsIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'valueStringSats',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<OutputV2, OutputV2, QAfterFilterCondition> walletOwnsEqualTo(
|
||||
bool value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'walletOwns',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension OutputV2QueryObject
|
||||
on QueryBuilder<OutputV2, OutputV2, QFilterCondition> {}
|
120
lib/models/isar/models/blockchain_data/v2/transaction_v2.dart
Normal file
120
lib/models/isar/models/blockchain_data/v2/transaction_v2.dart
Normal file
|
@ -0,0 +1,120 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
part 'transaction_v2.g.dart';
|
||||
|
||||
@Collection()
|
||||
class TransactionV2 {
|
||||
Id id = Isar.autoIncrement;
|
||||
|
||||
@Index()
|
||||
final String walletId;
|
||||
|
||||
@Index(unique: true, composite: [CompositeIndex("walletId")])
|
||||
final String txid;
|
||||
|
||||
final String hash;
|
||||
|
||||
@Index()
|
||||
late final int timestamp;
|
||||
|
||||
final int? height;
|
||||
final String? blockHash;
|
||||
final int version;
|
||||
|
||||
final List<InputV2> inputs;
|
||||
final List<OutputV2> outputs;
|
||||
|
||||
@enumerated
|
||||
final TransactionType type;
|
||||
|
||||
@enumerated
|
||||
final TransactionSubType subType;
|
||||
|
||||
TransactionV2({
|
||||
required this.walletId,
|
||||
required this.blockHash,
|
||||
required this.hash,
|
||||
required this.txid,
|
||||
required this.timestamp,
|
||||
required this.height,
|
||||
required this.inputs,
|
||||
required this.outputs,
|
||||
required this.version,
|
||||
required this.type,
|
||||
required this.subType,
|
||||
});
|
||||
|
||||
int getConfirmations(int currentChainHeight) {
|
||||
if (height == null || height! <= 0) return 0;
|
||||
return max(0, currentChainHeight - (height! - 1));
|
||||
}
|
||||
|
||||
bool isConfirmed(int currentChainHeight, int minimumConfirms) {
|
||||
final confirmations = getConfirmations(currentChainHeight);
|
||||
return confirmations >= minimumConfirms;
|
||||
}
|
||||
|
||||
Amount getFee({required Coin coin}) {
|
||||
final inSum =
|
||||
inputs.map((e) => e.value).reduce((value, element) => value += element);
|
||||
final outSum = outputs
|
||||
.map((e) => e.value)
|
||||
.reduce((value, element) => value += element);
|
||||
|
||||
return Amount(rawValue: inSum - outSum, fractionDigits: coin.decimals);
|
||||
}
|
||||
|
||||
Amount getAmountReceivedThisWallet({required Coin coin}) {
|
||||
final outSum = outputs
|
||||
.where((e) => e.walletOwns)
|
||||
.fold(BigInt.zero, (p, e) => p + e.value);
|
||||
|
||||
return Amount(rawValue: outSum, fractionDigits: coin.decimals);
|
||||
}
|
||||
|
||||
Amount getAmountSentFromThisWallet({required Coin coin}) {
|
||||
final inSum = inputs
|
||||
.where((e) => e.walletOwns)
|
||||
.fold(BigInt.zero, (p, e) => p + e.value);
|
||||
|
||||
return Amount(rawValue: inSum, fractionDigits: coin.decimals);
|
||||
}
|
||||
|
||||
Set<String> associatedAddresses() => {
|
||||
...inputs.map((e) => e.addresses).expand((e) => e),
|
||||
...outputs.map((e) => e.addresses).expand((e) => e),
|
||||
};
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TransactionV2(\n'
|
||||
' walletId: $walletId,\n'
|
||||
' hash: $hash,\n'
|
||||
' txid: $txid,\n'
|
||||
' type: $type,\n'
|
||||
' subType: $subType,\n'
|
||||
' timestamp: $timestamp,\n'
|
||||
' height: $height,\n'
|
||||
' blockHash: $blockHash,\n'
|
||||
' version: $version,\n'
|
||||
' inputs: $inputs,\n'
|
||||
' outputs: $outputs,\n'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
enum TxDirection {
|
||||
outgoing,
|
||||
incoming;
|
||||
}
|
||||
|
||||
enum TxType {
|
||||
normal,
|
||||
}
|
2229
lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart
Normal file
2229
lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -28,4 +28,15 @@ class SigningData {
|
|||
Uint8List? output;
|
||||
ECPair? keyPair;
|
||||
Uint8List? redeemScript;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "SigningData{\n"
|
||||
" derivePathType: $derivePathType,\n"
|
||||
" utxo: $utxo,\n"
|
||||
" output: $output,\n"
|
||||
" keyPair: $keyPair,\n"
|
||||
" redeemScript: $redeemScript,\n"
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
|
446
lib/pages/cashfusion/cashfusion_view.dart
Normal file
446
lib/pages/cashfusion/cashfusion_view.dart
Normal file
|
@ -0,0 +1,446 @@
|
|||
/*
|
||||
* 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 sneurlax on 2023-07-26
|
||||
*
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_native_splash/cli_commands.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/pages/cashfusion/fusion_progress_view.dart';
|
||||
import 'package:stackwallet/pages/cashfusion/fusion_rounds_selection_sheet.dart';
|
||||
import 'package:stackwallet/providers/cash_fusion/fusion_progress_ui_state_provider.dart';
|
||||
import 'package:stackwallet/providers/global/prefs_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.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/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
|
||||
class CashFusionView extends ConsumerStatefulWidget {
|
||||
const CashFusionView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
static const routeName = "/cashFusionView";
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<CashFusionView> createState() => _CashFusionViewState();
|
||||
}
|
||||
|
||||
class _CashFusionViewState extends ConsumerState<CashFusionView> {
|
||||
late final TextEditingController serverController;
|
||||
late final FocusNode serverFocusNode;
|
||||
late final TextEditingController portController;
|
||||
late final FocusNode portFocusNode;
|
||||
late final TextEditingController fusionRoundController;
|
||||
late final FocusNode fusionRoundFocusNode;
|
||||
|
||||
bool _enableSSLCheckbox = false;
|
||||
bool _enableStartButton = false;
|
||||
|
||||
FusionOption _option = FusionOption.continuous;
|
||||
|
||||
Future<void> _startFusion() async {
|
||||
final fusionWallet = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as FusionWalletInterface;
|
||||
|
||||
try {
|
||||
fusionWallet.uiState = ref.read(
|
||||
fusionProgressUIStateProvider(widget.walletId),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!e.toString().contains(
|
||||
"FusionProgressUIState was already set for ${widget.walletId}")) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
final int rounds = _option == FusionOption.continuous
|
||||
? 0
|
||||
: int.parse(fusionRoundController.text);
|
||||
|
||||
final newInfo = FusionInfo(
|
||||
host: serverController.text,
|
||||
port: int.parse(portController.text),
|
||||
ssl: _enableSSLCheckbox,
|
||||
rounds: rounds,
|
||||
);
|
||||
|
||||
// update user prefs (persistent)
|
||||
ref.read(prefsChangeNotifierProvider).fusionServerInfo = newInfo;
|
||||
|
||||
unawaited(
|
||||
fusionWallet.fuse(
|
||||
fusionInfo: newInfo,
|
||||
),
|
||||
);
|
||||
|
||||
await Navigator.of(context).pushNamed(
|
||||
FusionProgressView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
serverController = TextEditingController();
|
||||
portController = TextEditingController();
|
||||
fusionRoundController = TextEditingController();
|
||||
|
||||
serverFocusNode = FocusNode();
|
||||
portFocusNode = FocusNode();
|
||||
fusionRoundFocusNode = FocusNode();
|
||||
|
||||
final info = ref.read(prefsChangeNotifierProvider).fusionServerInfo;
|
||||
serverController.text = info.host;
|
||||
portController.text = info.port.toString();
|
||||
_enableSSLCheckbox = info.ssl;
|
||||
_option = info.rounds == 0 ? FusionOption.continuous : FusionOption.custom;
|
||||
fusionRoundController.text = info.rounds.toString();
|
||||
|
||||
_enableStartButton =
|
||||
serverController.text.isNotEmpty && portController.text.isNotEmpty;
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
serverController.dispose();
|
||||
portController.dispose();
|
||||
fusionRoundController.dispose();
|
||||
|
||||
serverFocusNode.dispose();
|
||||
portFocusNode.dispose();
|
||||
fusionRoundFocusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
leading: const AppBarBackButton(),
|
||||
title: Text(
|
||||
"CashFusion",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
titleSpacing: 0,
|
||||
actions: [
|
||||
AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
size: 36,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circleQuestion,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: () async {
|
||||
//' TODO show about?
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: LayoutBuilder(
|
||||
builder: (builderContext, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"CashFusion allows you to anonymize your BCH coins.",
|
||||
style: STextStyles.w500_12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Server settings",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
),
|
||||
CustomTextButton(
|
||||
text: "Default",
|
||||
onTap: () {
|
||||
const def = FusionInfo.DEFAULTS;
|
||||
serverController.text = def.host;
|
||||
portController.text = def.port.toString();
|
||||
fusionRoundController.text =
|
||||
def.rounds.toString();
|
||||
_option = FusionOption.continuous;
|
||||
setState(() {
|
||||
_enableSSLCheckbox = def.ssl;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: serverController,
|
||||
focusNode: serverFocusNode,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_enableStartButton = value.isNotEmpty &&
|
||||
portController.text.isNotEmpty &&
|
||||
fusionRoundController.text.isNotEmpty;
|
||||
});
|
||||
},
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Server",
|
||||
serverFocusNode,
|
||||
context,
|
||||
desktopMed: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: portController,
|
||||
focusNode: portFocusNode,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly
|
||||
],
|
||||
keyboardType: TextInputType.number,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_enableStartButton = value.isNotEmpty &&
|
||||
serverController.text.isNotEmpty &&
|
||||
fusionRoundController.text.isNotEmpty;
|
||||
});
|
||||
},
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Port",
|
||||
portFocusNode,
|
||||
context,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_enableSSLCheckbox = !_enableSSLCheckbox;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Checkbox(
|
||||
materialTapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
value: _enableSSLCheckbox,
|
||||
onChanged: (newValue) {
|
||||
setState(
|
||||
() {
|
||||
_enableSSLCheckbox =
|
||||
!_enableSSLCheckbox;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
"Use SSL",
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
"Rounds of fusion",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
RoundedContainer(
|
||||
onPressed: () async {
|
||||
final option =
|
||||
await showModalBottomSheet<FusionOption?>(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
builder: (_) {
|
||||
return FusionRoundCountSelectSheet(
|
||||
currentOption: _option,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (option != null) {
|
||||
setState(() {
|
||||
_option = option;
|
||||
});
|
||||
}
|
||||
},
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveBG,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
_option.name.capitalize(),
|
||||
style: STextStyles.w500_12(context),
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_option == FusionOption.custom)
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
if (_option == FusionOption.custom)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: fusionRoundController,
|
||||
focusNode: fusionRoundFocusNode,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly
|
||||
],
|
||||
keyboardType: TextInputType.number,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_enableStartButton = value.isNotEmpty &&
|
||||
serverController.text.isNotEmpty &&
|
||||
portController.text.isNotEmpty;
|
||||
});
|
||||
},
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Number of fusions",
|
||||
fusionRoundFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
labelText: "Enter number of fusions.."),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const Spacer(),
|
||||
PrimaryButton(
|
||||
label: "Start",
|
||||
enabled: _enableStartButton,
|
||||
onPressed: _startFusion,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
248
lib/pages/cashfusion/fusion_progress_view.dart
Normal file
248
lib/pages/cashfusion/fusion_progress_view.dart
Normal file
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* 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 julian on 2023-10-16
|
||||
*
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_progress.dart';
|
||||
import 'package:stackwallet/providers/cash_fusion/fusion_progress_ui_state_provider.dart';
|
||||
import 'package:stackwallet/providers/global/prefs_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
|
||||
class FusionProgressView extends ConsumerStatefulWidget {
|
||||
const FusionProgressView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
static const routeName = "/cashFusionProgressView";
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<FusionProgressView> createState() => _FusionProgressViewState();
|
||||
}
|
||||
|
||||
class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
|
||||
Future<bool> _requestAndProcessCancel() async {
|
||||
final shouldCancel = await showDialog<bool?>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => StackDialog(
|
||||
title: "Cancel fusion?",
|
||||
leftButton: SecondaryButton(
|
||||
label: "No",
|
||||
buttonHeight: null,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
),
|
||||
rightButton: PrimaryButton(
|
||||
label: "Yes",
|
||||
buttonHeight: null,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (shouldCancel == true && mounted) {
|
||||
final fusionWallet = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as FusionWalletInterface;
|
||||
|
||||
await showLoading(
|
||||
whileFuture: Future.wait([
|
||||
fusionWallet.stop(),
|
||||
Future<void>.delayed(const Duration(seconds: 2)),
|
||||
]),
|
||||
context: context,
|
||||
isDesktop: Util.isDesktop,
|
||||
message: "Stopping fusion",
|
||||
);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool _succeeded =
|
||||
ref.watch(fusionProgressUIStateProvider(widget.walletId)).succeeded;
|
||||
|
||||
final bool _failed =
|
||||
ref.watch(fusionProgressUIStateProvider(widget.walletId)).failed;
|
||||
|
||||
final int _fusionRoundsCompleted = ref
|
||||
.watch(fusionProgressUIStateProvider(widget.walletId))
|
||||
.fusionRoundsCompleted;
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
return await _requestAndProcessCancel();
|
||||
},
|
||||
child: Background(
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (await _requestAndProcessCancel()) {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Fusion progress",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
titleSpacing: 0,
|
||||
),
|
||||
body: LayoutBuilder(
|
||||
builder: (builderContext, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (_fusionRoundsCompleted == 0)
|
||||
RoundedContainer(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.snackBarBackError,
|
||||
child: Text(
|
||||
"Do not close this window. If you exit, "
|
||||
"the process will be canceled.",
|
||||
style:
|
||||
STextStyles.smallMed14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.snackBarTextError,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
if (_fusionRoundsCompleted > 0)
|
||||
RoundedContainer(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.snackBarBackInfo,
|
||||
child: Text(
|
||||
"Fusion rounds completed: $_fusionRoundsCompleted",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.snackBarTextInfo,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
FusionProgress(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (_succeeded)
|
||||
PrimaryButton(
|
||||
label: "Fuse again",
|
||||
onPressed: _fuseAgain,
|
||||
),
|
||||
if (_succeeded)
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (_failed)
|
||||
PrimaryButton(
|
||||
label: "Try again",
|
||||
onPressed: _fuseAgain,
|
||||
),
|
||||
if (_failed)
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
SecondaryButton(
|
||||
label: "Cancel",
|
||||
onPressed: () async {
|
||||
if (await _requestAndProcessCancel()) {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Fuse again.
|
||||
void _fuseAgain() async {
|
||||
final fusionWallet = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as FusionWalletInterface;
|
||||
|
||||
final fusionInfo = ref.read(prefsChangeNotifierProvider).fusionServerInfo;
|
||||
|
||||
try {
|
||||
fusionWallet.uiState = ref.read(
|
||||
fusionProgressUIStateProvider(widget.walletId),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!e.toString().contains(
|
||||
"FusionProgressUIState was already set for ${widget.walletId}")) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
unawaited(fusionWallet.fuse(fusionInfo: fusionInfo));
|
||||
}
|
||||
}
|
168
lib/pages/cashfusion/fusion_rounds_selection_sheet.dart
Normal file
168
lib/pages/cashfusion/fusion_rounds_selection_sheet.dart
Normal file
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* 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 julian on 2023-10-16
|
||||
*
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_native_splash/cli_commands.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
|
||||
enum FusionOption {
|
||||
continuous,
|
||||
custom;
|
||||
}
|
||||
|
||||
class FusionRoundCountSelectSheet extends HookWidget {
|
||||
const FusionRoundCountSelectSheet({
|
||||
Key? key,
|
||||
required this.currentOption,
|
||||
}) : super(key: key);
|
||||
|
||||
final FusionOption currentOption;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final option = useState(currentOption);
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
Navigator.of(context).pop(option.value);
|
||||
return false;
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24,
|
||||
right: 24,
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
width: 60,
|
||||
height: 4,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 36,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Rounds of fusion",
|
||||
style: STextStyles.pageTitleH2(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
for (int i = 0; i < FusionOption.values.length; i++)
|
||||
Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
option.value = FusionOption.values[i];
|
||||
Navigator.of(context).pop(option.value);
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.start,
|
||||
// children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Radio(
|
||||
activeColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.radioButtonIconEnabled,
|
||||
value: FusionOption.values[i],
|
||||
groupValue: option.value,
|
||||
onChanged: (_) {
|
||||
option.value = FusionOption.values[i];
|
||||
Navigator.of(context).pop(option.value);
|
||||
},
|
||||
),
|
||||
),
|
||||
// ],
|
||||
// ),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
FusionOption.values[i].name.capitalize(),
|
||||
style: STextStyles.titleBold12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(
|
||||
FusionOption.values[i] ==
|
||||
FusionOption.continuous
|
||||
? "Keep fusing until manually stopped"
|
||||
: "Stop after a set number of fusions",
|
||||
style: STextStyles.itemSubtitle12(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -13,13 +13,19 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:stackwallet/db/isar/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/address_tag.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart';
|
||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/address_utils.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
|
@ -145,6 +151,8 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final coin = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId).coin));
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Background(
|
||||
|
@ -258,10 +266,16 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
|
|||
borderColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar,
|
||||
child: _AddressDetailsTxList(
|
||||
walletId: widget.walletId,
|
||||
address: address,
|
||||
),
|
||||
child: coin == Coin.bitcoincash ||
|
||||
coin == Coin.bitcoincashTestnet
|
||||
? _AddressDetailsTxV2List(
|
||||
walletId: widget.walletId,
|
||||
address: address,
|
||||
)
|
||||
: _AddressDetailsTxList(
|
||||
walletId: widget.walletId,
|
||||
address: address,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -377,10 +391,15 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
|
|||
height: 12,
|
||||
),
|
||||
if (!isDesktop)
|
||||
_AddressDetailsTxList(
|
||||
walletId: widget.walletId,
|
||||
address: address,
|
||||
),
|
||||
coin == Coin.bitcoincash || coin == Coin.bitcoincashTestnet
|
||||
? _AddressDetailsTxV2List(
|
||||
walletId: widget.walletId,
|
||||
address: address,
|
||||
)
|
||||
: _AddressDetailsTxList(
|
||||
walletId: widget.walletId,
|
||||
address: address,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -445,6 +464,66 @@ class _AddressDetailsTxList extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class _AddressDetailsTxV2List extends ConsumerWidget {
|
||||
const _AddressDetailsTxV2List({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.address,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
final Address address;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final query = ref
|
||||
.watch(mainDBProvider)
|
||||
.isar
|
||||
.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.inputsElement((q) => q.addressesElementContains(address.value))
|
||||
.or()
|
||||
.outputsElement((q) => q.addressesElementContains(address.value))
|
||||
.sortByTimestampDesc();
|
||||
|
||||
final count = query.countSync();
|
||||
|
||||
if (count > 0) {
|
||||
if (Util.isDesktop) {
|
||||
final txns = query.findAllSync();
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
primary: false,
|
||||
itemBuilder: (_, index) => TransactionCardV2(
|
||||
transaction: txns[index],
|
||||
),
|
||||
separatorBuilder: (_, __) => const _Div(height: 1),
|
||||
itemCount: count,
|
||||
);
|
||||
} else {
|
||||
return RoundedWhiteContainer(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: query
|
||||
.findAllSync()
|
||||
.map(
|
||||
(e) => TransactionCardV2(
|
||||
transaction: e,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const NoTransActionsFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Div extends StatelessWidget {
|
||||
const _Div({
|
||||
Key? key,
|
||||
|
|
|
@ -9,18 +9,27 @@
|
|||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bitbox/bitbox.dart' as bb;
|
||||
import 'package:bitcoindart/bitcoindart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:stackwallet/db/hive/db.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/providers/global/debug_service_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
|
||||
import 'package:stackwallet/services/mixins/electrum_x_parsing.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/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
|
@ -343,6 +352,181 @@ class HiddenSettings extends StatelessWidget {
|
|||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
try {
|
||||
final p = TT();
|
||||
|
||||
final n = ref
|
||||
.read(nodeServiceChangeNotifierProvider)
|
||||
.getPrimaryNodeFor(
|
||||
coin: Coin.bitcoincash)!;
|
||||
|
||||
final e = ElectrumX.from(
|
||||
node: ElectrumXNode(
|
||||
address: n.host,
|
||||
port: n.port,
|
||||
name: n.name,
|
||||
id: n.id,
|
||||
useSSL: n.useSSL,
|
||||
),
|
||||
prefs:
|
||||
ref.read(prefsChangeNotifierProvider),
|
||||
failovers: [],
|
||||
);
|
||||
|
||||
final ce =
|
||||
CachedElectrumX(electrumXClient: e);
|
||||
|
||||
final txids = [
|
||||
"", // cashTokenTxid
|
||||
"6a0444358bc41913c5b04a8dc06896053184b3641bc62502d18f954865b6ce1e", // normalTxid
|
||||
"67f13c375f9be897036cac77b7900dc74312c4ba6fe22f419f5cb21d4151678c", // fusionTxid
|
||||
"c0ac3f88b238a023d2a87226dc90c3b0f9abc3eeb227e2730087b0b95ee5b3f9", // slpTokenSendTxid
|
||||
"7a427a156fe70f83d3ccdd17e75804cc0df8c95c64ce04d256b3851385002a0b", // slpTokenGenesisTxid
|
||||
];
|
||||
|
||||
// final json =
|
||||
// await e.getTransaction(txHash: txids[1]);
|
||||
// await p.parseBchTx(json, "NORMAL TXID:");
|
||||
//
|
||||
// final json2 =
|
||||
// await e.getTransaction(txHash: txids[2]);
|
||||
// await p.parseBchTx(json2, "FUSION TXID:");
|
||||
//
|
||||
// // print("CASH TOKEN TXID:");
|
||||
// // final json3 =
|
||||
// // await e.getTransaction(txHash: txids[2]);
|
||||
// // await p.parseBchTx(json3);
|
||||
//
|
||||
await p.getTransaction(
|
||||
txids[3],
|
||||
Coin.bitcoincash,
|
||||
"lol",
|
||||
ce,
|
||||
"SLP TOKEN SEND TXID:");
|
||||
await p.getTransaction(
|
||||
"009d31380d2dbfb5c91500c861d55b531a8b762b0abb19353db884548dbac8b6",
|
||||
Coin.bitcoincash,
|
||||
"lol",
|
||||
ce,
|
||||
"COINBASE TXID:");
|
||||
|
||||
// final json5 =
|
||||
// await e.getTransaction(txHash: txids[4]);
|
||||
// await p.parseBchTx(
|
||||
// json5, "SLP TOKEN GENESIS TXID:");
|
||||
} catch (e, s) {
|
||||
print("$e\n$s");
|
||||
}
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Parse BCH tx test",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
try {
|
||||
final p = TT();
|
||||
|
||||
final n = ref
|
||||
.read(nodeServiceChangeNotifierProvider)
|
||||
.getPrimaryNodeFor(
|
||||
coin: Coin.bitcoincash)!;
|
||||
|
||||
final e = ElectrumX.from(
|
||||
node: ElectrumXNode(
|
||||
address: n.host,
|
||||
port: n.port,
|
||||
name: n.name,
|
||||
id: n.id,
|
||||
useSSL: n.useSSL,
|
||||
),
|
||||
prefs:
|
||||
ref.read(prefsChangeNotifierProvider),
|
||||
failovers: [],
|
||||
);
|
||||
|
||||
final address =
|
||||
"qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwyztz2p80d";
|
||||
|
||||
List<int> _base32Decode(String string) {
|
||||
final data = Uint8List(string.length);
|
||||
for (int i = 0; i < string.length; i++) {
|
||||
final value = string[i];
|
||||
if (!_CHARSET_INVERSE_INDEX
|
||||
.containsKey(value))
|
||||
throw FormatException(
|
||||
"Invalid character '$value'");
|
||||
data[i] =
|
||||
_CHARSET_INVERSE_INDEX[string[i]]!;
|
||||
}
|
||||
|
||||
return data.sublist(1);
|
||||
}
|
||||
|
||||
final dec = _base32Decode(address);
|
||||
|
||||
final pd = PaymentData(
|
||||
pubkey: Uint8List.fromList(dec));
|
||||
|
||||
final p2pkh =
|
||||
P2PKH(data: pd, network: bitcoincash);
|
||||
|
||||
// final addr = p2pkh.data.address!;
|
||||
|
||||
final addr = bb.Address.toLegacyAddress(
|
||||
"bitcoincash:qp352c2skpdxwzzd090mec3v37au5dmfwgwfw686sz",
|
||||
);
|
||||
|
||||
final scripthash =
|
||||
AddressUtils.convertToScriptHash(
|
||||
addr, bitcoincash);
|
||||
|
||||
final utxos =
|
||||
await e.getUTXOs(scripthash: scripthash);
|
||||
|
||||
Util.printJson(utxos, "UTXOS for $address");
|
||||
|
||||
final hist = await e.getTransaction(
|
||||
txHash: utxos.first["tx_hash"] as String,
|
||||
);
|
||||
|
||||
Util.printJson(hist, "HISTORY for $address");
|
||||
} catch (e, s) {
|
||||
print("$e\n$s");
|
||||
}
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"UTXOs",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// const SizedBox(
|
||||
// height: 12,
|
||||
// ),
|
||||
|
@ -384,3 +568,38 @@ class HiddenSettings extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const _CHARSET_INVERSE_INDEX = {
|
||||
'q': 0,
|
||||
'p': 1,
|
||||
'z': 2,
|
||||
'r': 3,
|
||||
'y': 4,
|
||||
'9': 5,
|
||||
'x': 6,
|
||||
'8': 7,
|
||||
'g': 8,
|
||||
'f': 9,
|
||||
'2': 10,
|
||||
't': 11,
|
||||
'v': 12,
|
||||
'd': 13,
|
||||
'w': 14,
|
||||
'0': 15,
|
||||
's': 16,
|
||||
'3': 17,
|
||||
'j': 18,
|
||||
'n': 19,
|
||||
'5': 20,
|
||||
'4': 21,
|
||||
'k': 22,
|
||||
'h': 23,
|
||||
'c': 24,
|
||||
'e': 25,
|
||||
'6': 26,
|
||||
'm': 27,
|
||||
'u': 28,
|
||||
'a': 29,
|
||||
'7': 30,
|
||||
'l': 31,
|
||||
};
|
||||
|
|
|
@ -225,8 +225,8 @@ class _TransactionsListState extends ConsumerState<TokenTransactionsList> {
|
|||
_hasLoaded = true;
|
||||
}
|
||||
if (!_hasLoaded) {
|
||||
return Column(
|
||||
children: const [
|
||||
return const Column(
|
||||
children: [
|
||||
Spacer(),
|
||||
Center(
|
||||
child: LoadingIndicator(
|
||||
|
|
|
@ -37,12 +37,14 @@ class TokenView extends ConsumerStatefulWidget {
|
|||
const TokenView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
this.popPrevious = false,
|
||||
this.eventBus,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/token";
|
||||
|
||||
final String walletId;
|
||||
final bool popPrevious;
|
||||
final EventBus? eventBus;
|
||||
|
||||
@override
|
||||
|
@ -69,157 +71,175 @@ class _TokenViewState extends ConsumerState<TokenView> {
|
|||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
EthTokenIcon(
|
||||
contractAddress: ref.watch(
|
||||
tokenServiceProvider.select(
|
||||
(value) => value!.tokenContract.address,
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
final nav = Navigator.of(context);
|
||||
if (widget.popPrevious) {
|
||||
nav.pop();
|
||||
}
|
||||
nav.pop();
|
||||
return false;
|
||||
},
|
||||
child: Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
final nav = Navigator.of(context);
|
||||
if (widget.popPrevious) {
|
||||
nav.pop();
|
||||
}
|
||||
nav.pop();
|
||||
},
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
EthTokenIcon(
|
||||
contractAddress: ref.watch(
|
||||
tokenServiceProvider.select(
|
||||
(value) => value!.tokenContract.address,
|
||||
),
|
||||
),
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
ref.watch(tokenServiceProvider
|
||||
.select((value) => value!.tokenContract.name)),
|
||||
style: STextStyles.navBarTitle(context),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
size: 24,
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 2),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.verticalEllipsis,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
ref.watch(tokenServiceProvider
|
||||
.select((value) => value!.tokenContract.name)),
|
||||
style: STextStyles.navBarTitle(context),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
onPressed: () {
|
||||
// todo: context menu
|
||||
Navigator.of(context).pushNamed(
|
||||
TokenContractDetailsView.routeName,
|
||||
arguments: Tuple2(
|
||||
ref.watch(tokenServiceProvider
|
||||
.select((value) => value!.tokenContract.address)),
|
||||
widget.walletId,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 2),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.verticalEllipsis,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: () {
|
||||
// todo: context menu
|
||||
Navigator.of(context).pushNamed(
|
||||
TokenContractDetailsView.routeName,
|
||||
arguments: Tuple2(
|
||||
ref.watch(tokenServiceProvider
|
||||
.select((value) => value!.tokenContract.address)),
|
||||
widget.walletId,
|
||||
),
|
||||
);
|
||||
},
|
||||
body: Container(
|
||||
color: Theme.of(context).extension<StackColors>()!.background,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Container(
|
||||
color: Theme.of(context).extension<StackColors>()!.background,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: TokenSummary(
|
||||
walletId: widget.walletId,
|
||||
initialSyncStatus: initialSyncStatus,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Transactions",
|
||||
style: STextStyles.itemSubtitle(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
),
|
||||
CustomTextButton(
|
||||
text: "See all",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
AllTransactionsView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
child: TokenSummary(
|
||||
walletId: widget.walletId,
|
||||
initialSyncStatus: initialSyncStatus,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Transactions",
|
||||
style: STextStyles.itemSubtitle(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
),
|
||||
bottom: Radius.circular(
|
||||
// TokenView.navBarHeight / 2.0,
|
||||
Constants.size.circularBorderRadius,
|
||||
CustomTextButton(
|
||||
text: "See all",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
AllTransactionsView.routeName,
|
||||
arguments: (
|
||||
walletId: widget.walletId,
|
||||
isTokens: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
bottom: Radius.circular(
|
||||
// TokenView.navBarHeight / 2.0,
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TokenTransactionsList(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TokenTransactionsList(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'dart:io';
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/models/isar/stack_theme.dart';
|
||||
import 'package:stackwallet/themes/theme_providers.dart';
|
||||
|
@ -27,15 +28,24 @@ class TxIcon extends ConsumerWidget {
|
|||
required this.coin,
|
||||
}) : super(key: key);
|
||||
|
||||
final Transaction transaction;
|
||||
final Object transaction;
|
||||
final int currentHeight;
|
||||
final Coin coin;
|
||||
|
||||
static const Size size = Size(32, 32);
|
||||
|
||||
String _getAssetName(
|
||||
bool isCancelled, bool isReceived, bool isPending, IThemeAssets assets) {
|
||||
if (!isReceived && transaction.subType == TransactionSubType.mint) {
|
||||
bool isCancelled,
|
||||
bool isReceived,
|
||||
bool isPending,
|
||||
TransactionSubType subType,
|
||||
IThemeAssets assets,
|
||||
) {
|
||||
if (subType == TransactionSubType.cashFusion) {
|
||||
return Assets.svg.txCashFusion;
|
||||
}
|
||||
|
||||
if (!isReceived && subType == TransactionSubType.mint) {
|
||||
if (isCancelled) {
|
||||
return Assets.svg.anonymizeFailed;
|
||||
}
|
||||
|
@ -66,37 +76,61 @@ class TxIcon extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final txIsReceived = transaction.type == TransactionType.incoming;
|
||||
final bool txIsReceived;
|
||||
final String assetName;
|
||||
|
||||
final assetName = _getAssetName(
|
||||
transaction.isCancelled,
|
||||
txIsReceived,
|
||||
!transaction.isConfirmed(
|
||||
currentHeight,
|
||||
coin.requiredConfirmations,
|
||||
),
|
||||
ref.watch(themeAssetsProvider),
|
||||
);
|
||||
if (transaction is Transaction) {
|
||||
final tx = transaction as Transaction;
|
||||
txIsReceived = tx.type == TransactionType.incoming;
|
||||
assetName = _getAssetName(
|
||||
tx.isCancelled,
|
||||
txIsReceived,
|
||||
!tx.isConfirmed(
|
||||
currentHeight,
|
||||
coin.requiredConfirmations,
|
||||
),
|
||||
tx.subType,
|
||||
ref.watch(themeAssetsProvider),
|
||||
);
|
||||
} else if (transaction is TransactionV2) {
|
||||
final tx = transaction as TransactionV2;
|
||||
txIsReceived = tx.type == TransactionType.incoming;
|
||||
assetName = _getAssetName(
|
||||
false,
|
||||
txIsReceived,
|
||||
!tx.isConfirmed(
|
||||
currentHeight,
|
||||
coin.requiredConfirmations,
|
||||
),
|
||||
tx.subType,
|
||||
ref.watch(themeAssetsProvider),
|
||||
);
|
||||
} else {
|
||||
throw ArgumentError(
|
||||
"Unknown transaction type ${transaction.runtimeType}",
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
child: Center(
|
||||
// if it starts with "assets" we assume its local
|
||||
// TODO: a more thorough check
|
||||
child: assetName.startsWith("assets")
|
||||
? SvgPicture.asset(
|
||||
// if it starts with "assets" we assume its local
|
||||
// TODO: a more thorough check
|
||||
child: assetName.startsWith("assets")
|
||||
? SvgPicture.asset(
|
||||
assetName,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
)
|
||||
: SvgPicture.file(
|
||||
File(
|
||||
assetName,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
)
|
||||
: SvgPicture.file(
|
||||
File(
|
||||
assetName,
|
||||
),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
)),
|
||||
),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'
|
|||
import 'package:stackwallet/models/isar/models/contact_entry.dart';
|
||||
import 'package:stackwallet/models/transaction_filter.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/token_view/token_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
|
||||
|
@ -46,15 +47,23 @@ import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
|||
import 'package:stackwallet/widgets/transaction_card.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
typedef _GroupedTransactions = ({
|
||||
String label,
|
||||
DateTime startDate,
|
||||
List<Transaction> transactions
|
||||
});
|
||||
|
||||
class AllTransactionsView extends ConsumerStatefulWidget {
|
||||
const AllTransactionsView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
this.isTokens = false,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/allTransactions";
|
||||
|
||||
final String walletId;
|
||||
final bool isTokens;
|
||||
|
||||
@override
|
||||
ConsumerState<AllTransactionsView> createState() =>
|
||||
|
@ -189,25 +198,24 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
.toList();
|
||||
}
|
||||
|
||||
List<Tuple2<String, List<Transaction>>> groupTransactionsByMonth(
|
||||
List<Transaction> transactions) {
|
||||
Map<String, List<Transaction>> map = {};
|
||||
List<_GroupedTransactions> groupTransactionsByMonth(
|
||||
List<Transaction> transactions,
|
||||
) {
|
||||
Map<String, _GroupedTransactions> map = {};
|
||||
|
||||
for (var tx in transactions) {
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000);
|
||||
final monthYear = "${Constants.monthMap[date.month]} ${date.year}";
|
||||
if (map[monthYear] == null) {
|
||||
map[monthYear] = [];
|
||||
map[monthYear] =
|
||||
(label: monthYear, startDate: date, transactions: [tx]);
|
||||
} else {
|
||||
map[monthYear]!.transactions.add(tx);
|
||||
}
|
||||
map[monthYear]!.add(tx);
|
||||
}
|
||||
|
||||
List<Tuple2<String, List<Transaction>>> result = [];
|
||||
map.forEach((key, value) {
|
||||
result.add(Tuple2(key, value));
|
||||
});
|
||||
|
||||
return result;
|
||||
return map.values.toList()
|
||||
..sort((a, b) => b.startDate.compareTo(a.startDate));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -445,12 +453,12 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
),
|
||||
if (isDesktop &&
|
||||
ref.watch(transactionFilterProvider.state).state != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
children: const [
|
||||
children: [
|
||||
TransactionFilterOptionBar(),
|
||||
],
|
||||
),
|
||||
|
@ -472,8 +480,11 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
// debugPrint("Consumer build called");
|
||||
|
||||
return FutureBuilder(
|
||||
future: ref.watch(
|
||||
managerProvider.select((value) => value.transactions)),
|
||||
future: widget.isTokens
|
||||
? ref.watch(tokenServiceProvider
|
||||
.select((value) => value!.transactions))
|
||||
: ref.watch(managerProvider
|
||||
.select((value) => value.transactions)),
|
||||
builder: (_, AsyncSnapshot<List<Transaction>> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
|
@ -498,7 +509,7 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
height: 12,
|
||||
),
|
||||
Text(
|
||||
month.item1,
|
||||
month.label,
|
||||
style: STextStyles.smallMed12(context),
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -517,14 +528,15 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
.extension<StackColors>()!
|
||||
.background,
|
||||
),
|
||||
itemCount: month.item2.length,
|
||||
itemCount: month.transactions.length,
|
||||
itemBuilder: (context, index) =>
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: DesktopTransactionCardRow(
|
||||
key: Key(
|
||||
"transactionCard_key_${month.item2[index].txid}"),
|
||||
transaction: month.item2[index],
|
||||
"transactionCard_key_${month.transactions[index].txid}"),
|
||||
transaction:
|
||||
month.transactions[index],
|
||||
walletId: walletId,
|
||||
),
|
||||
),
|
||||
|
@ -535,7 +547,7 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
padding: const EdgeInsets.all(0),
|
||||
child: Column(
|
||||
children: [
|
||||
...month.item2.map(
|
||||
...month.transactions.map(
|
||||
(tx) => TransactionCard(
|
||||
key: Key(
|
||||
"transactionCard_key_${tx.txid}"),
|
||||
|
|
|
@ -831,7 +831,7 @@ class _TransactionDetailsViewState
|
|||
),
|
||||
if (isDesktop)
|
||||
IconCopyButton(
|
||||
data: _transaction.address.value!.value,
|
||||
data: _transaction.otherData ?? "",
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* 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:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list_item.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class FusionGroupDetailsView extends ConsumerStatefulWidget {
|
||||
const FusionGroupDetailsView({
|
||||
Key? key,
|
||||
required this.transactions,
|
||||
required this.walletId,
|
||||
required this.coin,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/fusionGroupDetailsView";
|
||||
|
||||
final List<TransactionV2> transactions;
|
||||
final String walletId;
|
||||
final Coin coin;
|
||||
|
||||
@override
|
||||
ConsumerState<FusionGroupDetailsView> createState() =>
|
||||
_FusionGroupDetailsViewState();
|
||||
}
|
||||
|
||||
class _FusionGroupDetailsViewState
|
||||
extends ConsumerState<FusionGroupDetailsView> {
|
||||
late final bool isDesktop;
|
||||
late final String walletId;
|
||||
|
||||
BorderRadius get _borderRadiusFirst {
|
||||
return BorderRadius.only(
|
||||
topLeft: Radius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
topRight: Radius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BorderRadius get _borderRadiusLast {
|
||||
return BorderRadius.only(
|
||||
bottomLeft: Radius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
bottomRight: Radius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isDesktop = Util.isDesktop;
|
||||
walletId = widget.walletId;
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isDesktop) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 32),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Fusion transactions",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 32,
|
||||
bottom: 32,
|
||||
),
|
||||
child: RoundedWhiteContainer(
|
||||
borderColor: isDesktop
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar
|
||||
: null,
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
BorderRadius? radius;
|
||||
if (widget.transactions.length == 1) {
|
||||
radius = BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
);
|
||||
} else if (index == widget.transactions.length - 1) {
|
||||
radius = _borderRadiusLast;
|
||||
} else if (index == 0) {
|
||||
radius = _borderRadiusFirst;
|
||||
}
|
||||
final tx = widget.transactions[index];
|
||||
return TxListItem(
|
||||
tx: tx,
|
||||
coin: widget.coin,
|
||||
radius: radius,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: 1.2,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.background,
|
||||
);
|
||||
},
|
||||
itemCount: widget.transactions.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Fusion transactions",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: ListView.builder(
|
||||
itemCount: widget.transactions.length,
|
||||
itemBuilder: (context, index) {
|
||||
BorderRadius? radius;
|
||||
if (widget.transactions.length == 1) {
|
||||
radius = BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
);
|
||||
} else if (index == widget.transactions.length - 1) {
|
||||
radius = _borderRadiusLast;
|
||||
} else if (index == 0) {
|
||||
radius = _borderRadiusFirst;
|
||||
}
|
||||
final tx = widget.transactions[index];
|
||||
|
||||
return TxListItem(
|
||||
tx: tx,
|
||||
coin: widget.coin,
|
||||
radius: radius,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/fusion_group_details_view.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
|
||||
class FusionTxGroup {
|
||||
final List<TransactionV2> transactions;
|
||||
FusionTxGroup(this.transactions);
|
||||
}
|
||||
|
||||
class FusionTxGroupCard extends ConsumerWidget {
|
||||
const FusionTxGroupCard({super.key, required this.group});
|
||||
|
||||
final FusionTxGroup group;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final walletId = group.transactions.first.walletId;
|
||||
|
||||
final coin = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).coin));
|
||||
|
||||
final currentHeight = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).currentHeight));
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(Constants.size.circularBorderRadius),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: RawMaterialButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
if (Util.isDesktop) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => DesktopDialog(
|
||||
maxWidth: 580,
|
||||
child: FusionGroupDetailsView(
|
||||
transactions: group.transactions,
|
||||
coin: coin,
|
||||
walletId: walletId,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamed(
|
||||
FusionGroupDetailsView.routeName,
|
||||
arguments: (
|
||||
transactions: group.transactions,
|
||||
coin: coin,
|
||||
walletId: walletId,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: [
|
||||
TxIcon(
|
||||
transaction: group.transactions.first,
|
||||
coin: coin,
|
||||
currentHeight: currentHeight,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 14,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
"Fusions",
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Builder(
|
||||
builder: (_) {
|
||||
return Text(
|
||||
"${group.transactions.length} fusion transactions",
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
Format.extractDateFrom(
|
||||
group.transactions.last.timestamp,
|
||||
),
|
||||
style: STextStyles.label(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
// if (ref.watch(prefsChangeNotifierProvider
|
||||
// .select((value) => value.externalCalls)))
|
||||
// const SizedBox(
|
||||
// width: 10,
|
||||
// ),
|
||||
// if (ref.watch(prefsChangeNotifierProvider
|
||||
// .select((value) => value.externalCalls)))
|
||||
// Flexible(
|
||||
// child: FittedBox(
|
||||
// fit: BoxFit.scaleDown,
|
||||
// child: Builder(
|
||||
// builder: (_) {
|
||||
// return Text(
|
||||
// "$prefix${Amount.fromDecimal(
|
||||
// amount.decimal * price,
|
||||
// fractionDigits: 2,
|
||||
// ).fiatString(
|
||||
// locale: locale,
|
||||
// )} $baseCurrency",
|
||||
// style: STextStyles.label(context),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||
import 'package:stackwallet/providers/global/locale_provider.dart';
|
||||
import 'package:stackwallet/providers/global/prefs_provider.dart';
|
||||
import 'package:stackwallet/providers/global/price_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.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/format.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
|
||||
class TransactionCardV2 extends ConsumerStatefulWidget {
|
||||
const TransactionCardV2({
|
||||
Key? key,
|
||||
required this.transaction,
|
||||
}) : super(key: key);
|
||||
|
||||
final TransactionV2 transaction;
|
||||
|
||||
@override
|
||||
ConsumerState<TransactionCardV2> createState() => _TransactionCardStateV2();
|
||||
}
|
||||
|
||||
class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
|
||||
late final TransactionV2 _transaction;
|
||||
late final String walletId;
|
||||
late final String prefix;
|
||||
late final String unit;
|
||||
late final Coin coin;
|
||||
late final TransactionType txType;
|
||||
|
||||
String whatIsIt(
|
||||
Coin coin,
|
||||
int currentHeight,
|
||||
) {
|
||||
final confirmedStatus = _transaction.isConfirmed(
|
||||
currentHeight,
|
||||
coin.requiredConfirmations,
|
||||
);
|
||||
|
||||
if (_transaction.subType == TransactionSubType.cashFusion) {
|
||||
if (confirmedStatus) {
|
||||
return "Anonymized";
|
||||
} else {
|
||||
return "Anonymizing";
|
||||
}
|
||||
}
|
||||
|
||||
if (_transaction.type == TransactionType.incoming) {
|
||||
// if (_transaction.isMinting) {
|
||||
// return "Minting";
|
||||
// } else
|
||||
if (confirmedStatus) {
|
||||
return "Received";
|
||||
} else {
|
||||
return "Receiving";
|
||||
}
|
||||
} else if (_transaction.type == TransactionType.outgoing) {
|
||||
if (confirmedStatus) {
|
||||
return "Sent";
|
||||
} else {
|
||||
return "Sending";
|
||||
}
|
||||
} else if (_transaction.type == TransactionType.sentToSelf) {
|
||||
return "Sent to self";
|
||||
} else {
|
||||
return _transaction.type.name;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_transaction = widget.transaction;
|
||||
walletId = _transaction.walletId;
|
||||
|
||||
if (Util.isDesktop) {
|
||||
if (_transaction.type == TransactionType.outgoing &&
|
||||
_transaction.subType != TransactionSubType.cashFusion) {
|
||||
prefix = "-";
|
||||
} else if (_transaction.type == TransactionType.incoming) {
|
||||
prefix = "+";
|
||||
} else {
|
||||
prefix = "";
|
||||
}
|
||||
} else {
|
||||
prefix = "";
|
||||
}
|
||||
coin = ref.read(walletsChangeNotifierProvider).getManager(walletId).coin;
|
||||
|
||||
unit = coin.ticker;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final locale = ref.watch(
|
||||
localeServiceChangeNotifierProvider.select((value) => value.locale));
|
||||
|
||||
final baseCurrency = ref
|
||||
.watch(prefsChangeNotifierProvider.select((value) => value.currency));
|
||||
|
||||
final price = ref
|
||||
.watch(priceAnd24hChangeNotifierProvider
|
||||
.select((value) => value.getPrice(coin)))
|
||||
.item1;
|
||||
|
||||
final currentHeight = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).currentHeight));
|
||||
|
||||
final Amount amount;
|
||||
|
||||
if (_transaction.subType == TransactionSubType.cashFusion) {
|
||||
amount = _transaction.getAmountReceivedThisWallet(coin: coin);
|
||||
} else {
|
||||
switch (_transaction.type) {
|
||||
case TransactionType.outgoing:
|
||||
amount = _transaction.getAmountSentFromThisWallet(coin: coin);
|
||||
break;
|
||||
|
||||
case TransactionType.incoming:
|
||||
case TransactionType.sentToSelf:
|
||||
amount = _transaction.getAmountReceivedThisWallet(coin: coin);
|
||||
break;
|
||||
|
||||
case TransactionType.unknown:
|
||||
amount = _transaction.getAmountSentFromThisWallet(coin: coin);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(Constants.size.circularBorderRadius),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: RawMaterialButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
if (Util.isDesktop) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => DesktopDialog(
|
||||
maxHeight: MediaQuery.of(context).size.height - 64,
|
||||
maxWidth: 580,
|
||||
child: TransactionV2DetailsView(
|
||||
transaction: _transaction,
|
||||
coin: coin,
|
||||
walletId: walletId,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamed(
|
||||
TransactionV2DetailsView.routeName,
|
||||
arguments: (
|
||||
tx: _transaction,
|
||||
coin: coin,
|
||||
walletId: walletId,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: [
|
||||
TxIcon(
|
||||
transaction: _transaction,
|
||||
coin: coin,
|
||||
currentHeight: currentHeight,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 14,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
whatIsIt(
|
||||
coin,
|
||||
currentHeight,
|
||||
),
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Builder(
|
||||
builder: (_) {
|
||||
return Text(
|
||||
"$prefix${ref.watch(pAmountFormatter(coin)).format(amount)}",
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
Format.extractDateFrom(_transaction.timestamp),
|
||||
style: STextStyles.label(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (ref.watch(prefsChangeNotifierProvider
|
||||
.select((value) => value.externalCalls)))
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
if (ref.watch(prefsChangeNotifierProvider
|
||||
.select((value) => value.externalCalls)))
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Builder(
|
||||
builder: (_) {
|
||||
return Text(
|
||||
"$prefix${Amount.fromDecimal(
|
||||
amount.decimal * price,
|
||||
fractionDigits: 2,
|
||||
).fiatString(
|
||||
locale: locale,
|
||||
)} $baseCurrency",
|
||||
style: STextStyles.label(context),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* 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-10-19
|
||||
*
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/fusion_tx_group_card.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list_item.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||
|
||||
class TransactionsV2List extends ConsumerStatefulWidget {
|
||||
const TransactionsV2List({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<TransactionsV2List> createState() => _TransactionsV2ListState();
|
||||
}
|
||||
|
||||
class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
|
||||
bool _hasLoaded = false;
|
||||
List<TransactionV2> _transactions = [];
|
||||
|
||||
BorderRadius get _borderRadiusFirst {
|
||||
return BorderRadius.only(
|
||||
topLeft: Radius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
topRight: Radius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BorderRadius get _borderRadiusLast {
|
||||
return BorderRadius.only(
|
||||
bottomLeft: Radius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
bottomRight: Radius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final manager = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId)));
|
||||
|
||||
return FutureBuilder(
|
||||
future: ref
|
||||
.watch(mainDBProvider)
|
||||
.isar
|
||||
.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(widget.walletId)
|
||||
.sortByTimestampDesc()
|
||||
.findAll(),
|
||||
builder: (fbContext, AsyncSnapshot<List<TransactionV2>> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
_transactions = snapshot.data!;
|
||||
_hasLoaded = true;
|
||||
}
|
||||
if (!_hasLoaded) {
|
||||
return const Column(
|
||||
children: [
|
||||
Spacer(),
|
||||
Center(
|
||||
child: LoadingIndicator(
|
||||
height: 50,
|
||||
width: 50,
|
||||
),
|
||||
),
|
||||
Spacer(
|
||||
flex: 4,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
if (_transactions.isEmpty) {
|
||||
return const NoTransActionsFound();
|
||||
} else {
|
||||
_transactions.sort((a, b) => b.timestamp - a.timestamp);
|
||||
|
||||
final List<Object> _txns = [];
|
||||
|
||||
List<TransactionV2> fusions = [];
|
||||
|
||||
for (int i = 0; i < _transactions.length; i++) {
|
||||
final tx = _transactions[i];
|
||||
|
||||
if (tx.subType == TransactionSubType.cashFusion) {
|
||||
if (fusions.isNotEmpty) {
|
||||
final prevTime = DateTime.fromMillisecondsSinceEpoch(
|
||||
fusions.last.timestamp * 1000);
|
||||
final thisTime =
|
||||
DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000);
|
||||
|
||||
if (prevTime.difference(thisTime).inMinutes > 30) {
|
||||
_txns.add(FusionTxGroup(fusions));
|
||||
fusions = [tx];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
fusions.add(tx);
|
||||
}
|
||||
|
||||
if (i + 1 < _transactions.length) {
|
||||
final nextTx = _transactions[i + 1];
|
||||
if (nextTx.subType != TransactionSubType.cashFusion &&
|
||||
fusions.isNotEmpty) {
|
||||
_txns.add(FusionTxGroup(fusions));
|
||||
fusions = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (tx.subType != TransactionSubType.cashFusion) {
|
||||
_txns.add(tx);
|
||||
}
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
final managerProvider = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManagerProvider(widget.walletId);
|
||||
if (!ref.read(managerProvider).isRefreshing) {
|
||||
unawaited(ref.read(managerProvider).refresh());
|
||||
}
|
||||
},
|
||||
child: Util.isDesktop
|
||||
? ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
BorderRadius? radius;
|
||||
if (_txns.length == 1) {
|
||||
radius = BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
);
|
||||
} else if (index == _txns.length - 1) {
|
||||
radius = _borderRadiusLast;
|
||||
} else if (index == 0) {
|
||||
radius = _borderRadiusFirst;
|
||||
}
|
||||
final tx = _txns[index];
|
||||
return TxListItem(
|
||||
tx: tx,
|
||||
coin: manager.coin,
|
||||
radius: radius,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: 2,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.background,
|
||||
);
|
||||
},
|
||||
itemCount: _txns.length,
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: _txns.length,
|
||||
itemBuilder: (context, index) {
|
||||
BorderRadius? radius;
|
||||
bool shouldWrap = false;
|
||||
if (_txns.length == 1) {
|
||||
radius = BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
);
|
||||
} else if (index == _txns.length - 1) {
|
||||
radius = _borderRadiusLast;
|
||||
shouldWrap = true;
|
||||
} else if (index == 0) {
|
||||
radius = _borderRadiusFirst;
|
||||
}
|
||||
final tx = _txns[index];
|
||||
if (shouldWrap) {
|
||||
return Column(
|
||||
children: [
|
||||
TxListItem(
|
||||
tx: tx,
|
||||
coin: manager.coin,
|
||||
radius: radius,
|
||||
),
|
||||
const SizedBox(
|
||||
height: WalletView.navBarHeight + 14,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return TxListItem(
|
||||
tx: tx,
|
||||
coin: manager.coin,
|
||||
radius: radius,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/fusion_tx_group_card.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart';
|
||||
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import 'package:stackwallet/widgets/trade_card.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class TxListItem extends ConsumerWidget {
|
||||
const TxListItem({
|
||||
super.key,
|
||||
required this.tx,
|
||||
this.radius,
|
||||
required this.coin,
|
||||
}) : assert(tx is TransactionV2 || tx is FusionTxGroup);
|
||||
|
||||
final Object tx;
|
||||
final BorderRadius? radius;
|
||||
final Coin coin;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
if (tx is TransactionV2) {
|
||||
final _tx = tx as TransactionV2;
|
||||
|
||||
final matchingTrades = ref
|
||||
.read(tradesServiceProvider)
|
||||
.trades
|
||||
.where((e) => e.payInTxid == _tx.txid || e.payOutTxid == _tx.txid);
|
||||
|
||||
if (_tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) {
|
||||
final trade = matchingTrades.first;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
borderRadius: radius,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TransactionCardV2(
|
||||
key: UniqueKey(),
|
||||
transaction: _tx,
|
||||
),
|
||||
TradeCard(
|
||||
key: Key(_tx.txid +
|
||||
_tx.type.name +
|
||||
_tx.hashCode.toString() +
|
||||
trade.uuid), //
|
||||
trade: trade,
|
||||
onTap: () async {
|
||||
if (Util.isDesktop) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => Navigator(
|
||||
initialRoute: TradeDetailsView.routeName,
|
||||
onGenerateRoute: RouteGenerator.generateRoute,
|
||||
onGenerateInitialRoutes: (_, __) {
|
||||
return [
|
||||
FadePageRoute(
|
||||
DesktopDialog(
|
||||
maxHeight: null,
|
||||
maxWidth: 580,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
bottom: 16,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Trade details",
|
||||
style:
|
||||
STextStyles.desktopH3(context),
|
||||
),
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).pop,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: TradeDetailsView(
|
||||
tradeId: trade.tradeId,
|
||||
// TODO
|
||||
// transactionIfSentFromStack: tx,
|
||||
transactionIfSentFromStack: null,
|
||||
walletName: ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value
|
||||
.getManager(_tx.walletId)
|
||||
.walletName,
|
||||
),
|
||||
),
|
||||
walletId: _tx.walletId,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const RouteSettings(
|
||||
name: TradeDetailsView.routeName,
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamed(
|
||||
TradeDetailsView.routeName,
|
||||
arguments: Tuple4(
|
||||
trade.tradeId,
|
||||
_tx,
|
||||
_tx.walletId,
|
||||
ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(_tx.walletId)
|
||||
.walletName,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
borderRadius: radius,
|
||||
),
|
||||
child: TransactionCardV2(
|
||||
// this may mess with combined firo transactions
|
||||
key: UniqueKey(),
|
||||
transaction: _tx,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final group = tx as FusionTxGroup;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
borderRadius: radius,
|
||||
),
|
||||
child: FusionTxGroupCard(
|
||||
key: UniqueKey(),
|
||||
group: group,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import 'package:isar/isar.dart';
|
|||
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart';
|
||||
import 'package:stackwallet/pages/cashfusion/cashfusion_view.dart';
|
||||
import 'package:stackwallet/pages/coin_control/coin_control_view.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
|
||||
import 'package:stackwallet/pages/home_view/home_view.dart';
|
||||
|
@ -36,6 +37,8 @@ import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
|
|||
import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_summary.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list.dart';
|
||||
import 'package:stackwallet/providers/global/auto_swb_service_provider.dart';
|
||||
import 'package:stackwallet/providers/global/paynym_api_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
|
@ -84,6 +87,8 @@ import 'package:stackwallet/widgets/wallet_navigation_bar/components/wallet_navi
|
|||
import 'package:stackwallet/widgets/wallet_navigation_bar/wallet_navigation_bar.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../widgets/wallet_navigation_bar/components/icons/fusion_nav_icon.dart';
|
||||
|
||||
/// [eventBus] should only be set during testing
|
||||
class WalletView extends ConsumerStatefulWidget {
|
||||
const WalletView({
|
||||
|
@ -839,7 +844,10 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
text: "See all",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
AllTransactionsView.routeName,
|
||||
coin == Coin.bitcoincash ||
|
||||
coin == Coin.bitcoincashTestnet
|
||||
? AllTransactionsV2View.routeName
|
||||
: AllTransactionsView.routeName,
|
||||
arguments: walletId,
|
||||
);
|
||||
},
|
||||
|
@ -878,7 +886,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
0.0,
|
||||
0.8,
|
||||
1.0,
|
||||
], // 10% purple, 80% transparent, 10% purple
|
||||
],
|
||||
).createShader(bounds);
|
||||
},
|
||||
child: Container(
|
||||
|
@ -893,10 +901,16 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TransactionsList(
|
||||
managerProvider: managerProvider,
|
||||
walletId: walletId,
|
||||
),
|
||||
child: coin == Coin.bitcoincash ||
|
||||
coin == Coin.bitcoincashTestnet
|
||||
? TransactionsV2List(
|
||||
walletId: widget.walletId,
|
||||
)
|
||||
: TransactionsList(
|
||||
managerProvider:
|
||||
managerProvider,
|
||||
walletId: walletId,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -1101,6 +1115,22 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) =>
|
||||
value.getManager(widget.walletId).hasFusionSupport,
|
||||
),
|
||||
))
|
||||
WalletNavigationBarItemData(
|
||||
label: "Fusion",
|
||||
icon: const FusionNavIcon(),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
CashFusionView.routeName,
|
||||
arguments: walletId,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
@ -210,30 +210,27 @@ class _DesktopAddressListState extends ConsumerState<DesktopAddressList> {
|
|||
height: 20,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: RoundedWhiteContainer(
|
||||
padding: EdgeInsets.zero,
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: ids.length,
|
||||
separatorBuilder: (_, __) => Container(
|
||||
height: 1,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar,
|
||||
),
|
||||
itemBuilder: (_, index) => Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: AddressCard(
|
||||
key: Key("addressCardDesktop_key_${ids[index]}"),
|
||||
walletId: widget.walletId,
|
||||
addressId: ids[index],
|
||||
coin: coin,
|
||||
onPressed: () {
|
||||
ref.read(desktopSelectedAddressId.state).state =
|
||||
ids[index];
|
||||
},
|
||||
),
|
||||
child: RoundedWhiteContainer(
|
||||
padding: EdgeInsets.zero,
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: ids.length,
|
||||
separatorBuilder: (_, __) => Container(
|
||||
height: 1,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar,
|
||||
),
|
||||
itemBuilder: (_, index) => Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: AddressCard(
|
||||
key: Key("addressCardDesktop_key_${ids[index]}"),
|
||||
walletId: widget.walletId,
|
||||
addressId: ids[index],
|
||||
coin: coin,
|
||||
onPressed: () {
|
||||
ref.read(desktopSelectedAddressId.state).state = ids[index];
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -0,0 +1,586 @@
|
|||
/*
|
||||
* 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 'dart:async';
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_native_splash/cli_commands.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/pages/cashfusion/fusion_rounds_selection_sheet.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart';
|
||||
import 'package:stackwallet/providers/cash_fusion/fusion_progress_ui_state_provider.dart';
|
||||
import 'package:stackwallet/providers/global/prefs_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.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/desktop/desktop_app_bar.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_scaffold.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
|
||||
class DesktopCashFusionView extends ConsumerStatefulWidget {
|
||||
const DesktopCashFusionView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
static const String routeName = "/desktopCashFusionView";
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<DesktopCashFusionView> createState() => _DesktopCashFusion();
|
||||
}
|
||||
|
||||
class _DesktopCashFusion extends ConsumerState<DesktopCashFusionView> {
|
||||
late final TextEditingController serverController;
|
||||
late final FocusNode serverFocusNode;
|
||||
late final TextEditingController portController;
|
||||
late final FocusNode portFocusNode;
|
||||
late final TextEditingController fusionRoundController;
|
||||
late final FocusNode fusionRoundFocusNode;
|
||||
|
||||
bool _enableStartButton = false;
|
||||
bool _enableSSLCheckbox = false;
|
||||
|
||||
FusionOption _roundType = FusionOption.continuous;
|
||||
|
||||
Future<void> _startFusion() async {
|
||||
final fusionWallet = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as FusionWalletInterface;
|
||||
|
||||
try {
|
||||
fusionWallet.uiState = ref.read(
|
||||
fusionProgressUIStateProvider(widget.walletId),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!e.toString().contains(
|
||||
"FusionProgressUIState was already set for ${widget.walletId}")) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
final int rounds = _roundType == FusionOption.continuous
|
||||
? 0
|
||||
: int.parse(fusionRoundController.text);
|
||||
|
||||
final newInfo = FusionInfo(
|
||||
host: serverController.text,
|
||||
port: int.parse(portController.text),
|
||||
ssl: _enableSSLCheckbox,
|
||||
rounds: rounds,
|
||||
);
|
||||
|
||||
// update user prefs (persistent)
|
||||
ref.read(prefsChangeNotifierProvider).fusionServerInfo = newInfo;
|
||||
|
||||
unawaited(
|
||||
fusionWallet.fuse(
|
||||
fusionInfo: newInfo,
|
||||
),
|
||||
);
|
||||
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) {
|
||||
return FusionDialogView(
|
||||
walletId: widget.walletId,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
serverController = TextEditingController();
|
||||
portController = TextEditingController();
|
||||
fusionRoundController = TextEditingController();
|
||||
|
||||
serverFocusNode = FocusNode();
|
||||
portFocusNode = FocusNode();
|
||||
fusionRoundFocusNode = FocusNode();
|
||||
|
||||
final info = ref.read(prefsChangeNotifierProvider).fusionServerInfo;
|
||||
serverController.text = info.host;
|
||||
portController.text = info.port.toString();
|
||||
_enableSSLCheckbox = info.ssl;
|
||||
_roundType =
|
||||
info.rounds == 0 ? FusionOption.continuous : FusionOption.custom;
|
||||
fusionRoundController.text = info.rounds.toString();
|
||||
|
||||
_enableStartButton =
|
||||
serverController.text.isNotEmpty && portController.text.isNotEmpty;
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
serverController.dispose();
|
||||
portController.dispose();
|
||||
fusionRoundController.dispose();
|
||||
|
||||
serverFocusNode.dispose();
|
||||
portFocusNode.dispose();
|
||||
fusionRoundFocusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
return DesktopScaffold(
|
||||
appBar: DesktopAppBar(
|
||||
background: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
isCompactHeight: true,
|
||||
useSpacers: false,
|
||||
leading: Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// const SizedBox(
|
||||
// width: 32,
|
||||
// ),
|
||||
AppBarIconButton(
|
||||
size: 32,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
shadows: const [],
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.arrowLeft,
|
||||
width: 18,
|
||||
height: 18,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.cashFusion,
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
"CashFusion",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.circleQuestion,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.radioButtonIconBorder,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
text: "What is CashFusion?",
|
||||
style: STextStyles.richLink(context).copyWith(
|
||||
fontSize: 16,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return DesktopDialog(
|
||||
maxWidth: 580,
|
||||
maxHeight: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
left: 20,
|
||||
bottom: 20,
|
||||
right: 10,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"What is CashFusion?",
|
||||
style: STextStyles.desktopH2(
|
||||
context),
|
||||
),
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: () =>
|
||||
Navigator.of(context)
|
||||
.pop(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
"A fully decentralized privacy protocol that allows "
|
||||
"anyone to create multi-party transactions with other "
|
||||
"network participants. This process obscures your real "
|
||||
"spending and makes it difficult for chain-analysis "
|
||||
"companies to track your coins.",
|
||||
style:
|
||||
STextStyles.desktopTextMedium(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 460,
|
||||
child: RoundedWhiteContainer(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"CashFusion allows you to anonymize your BCH coins.",
|
||||
style:
|
||||
STextStyles.desktopTextExtraExtraSmall(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
SizedBox(
|
||||
width: 460,
|
||||
child: RoundedWhiteContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Server settings",
|
||||
style: STextStyles.desktopTextExtraExtraSmall(
|
||||
context),
|
||||
),
|
||||
CustomTextButton(
|
||||
text: "Default",
|
||||
onTap: () {
|
||||
const def = FusionInfo.DEFAULTS;
|
||||
serverController.text = def.host;
|
||||
portController.text = def.port.toString();
|
||||
fusionRoundController.text =
|
||||
def.rounds.toString();
|
||||
_roundType = FusionOption.continuous;
|
||||
setState(() {
|
||||
_enableSSLCheckbox = def.ssl;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: serverController,
|
||||
focusNode: serverFocusNode,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_enableStartButton = value.isNotEmpty &&
|
||||
portController.text.isNotEmpty &&
|
||||
fusionRoundController.text.isNotEmpty;
|
||||
});
|
||||
},
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Server",
|
||||
serverFocusNode,
|
||||
context,
|
||||
desktopMed: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: portController,
|
||||
focusNode: portFocusNode,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly
|
||||
],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_enableStartButton = value.isNotEmpty &&
|
||||
serverController.text.isNotEmpty &&
|
||||
fusionRoundController.text.isNotEmpty;
|
||||
});
|
||||
},
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Port",
|
||||
portFocusNode,
|
||||
context,
|
||||
desktopMed: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_enableSSLCheckbox = !_enableSSLCheckbox;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Checkbox(
|
||||
materialTapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
value: _enableSSLCheckbox,
|
||||
onChanged: (newValue) {
|
||||
setState(
|
||||
() {
|
||||
_enableSSLCheckbox =
|
||||
!_enableSSLCheckbox;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
"Use SSL",
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
"Rounds of fusion",
|
||||
style:
|
||||
STextStyles.desktopTextExtraExtraSmall(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<FusionOption>(
|
||||
value: _roundType,
|
||||
items: [
|
||||
...FusionOption.values.map(
|
||||
(e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
e.name.capitalize(),
|
||||
style: STextStyles.smallMed14(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value is FusionOption) {
|
||||
setState(() {
|
||||
_roundType = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
isExpanded: true,
|
||||
iconStyleData: IconStyleData(
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
offset: const Offset(0, -10),
|
||||
elevation: 0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_roundType == FusionOption.custom)
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
if (_roundType == FusionOption.custom)
|
||||
SizedBox(
|
||||
width: 460,
|
||||
child: RoundedWhiteContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: fusionRoundController,
|
||||
focusNode: fusionRoundFocusNode,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly
|
||||
],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_enableStartButton = value
|
||||
.isNotEmpty &&
|
||||
serverController
|
||||
.text.isNotEmpty &&
|
||||
portController.text.isNotEmpty;
|
||||
});
|
||||
},
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Number of fusions",
|
||||
fusionRoundFocusNode,
|
||||
context,
|
||||
desktopMed: true,
|
||||
).copyWith(
|
||||
labelText:
|
||||
"Enter number of fusions.."),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Start",
|
||||
enabled: _enableStartButton,
|
||||
onPressed: _startFusion,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_progress.dart';
|
||||
import 'package:stackwallet/providers/cash_fusion/fusion_progress_ui_state_provider.dart';
|
||||
import 'package:stackwallet/providers/global/prefs_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
enum CashFusionStatus { waiting, running, success, failed }
|
||||
|
||||
class CashFusionState {
|
||||
final CashFusionStatus status;
|
||||
final String? info;
|
||||
|
||||
CashFusionState({required this.status, this.info});
|
||||
}
|
||||
|
||||
class FusionDialogView extends ConsumerStatefulWidget {
|
||||
const FusionDialogView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<FusionDialogView> createState() => _FusionDialogViewState();
|
||||
}
|
||||
|
||||
class _FusionDialogViewState extends ConsumerState<FusionDialogView> {
|
||||
Future<bool> _requestAndProcessCancel() async {
|
||||
if (!ref.read(fusionProgressUIStateProvider(widget.walletId)).running) {
|
||||
return true;
|
||||
} else {
|
||||
bool? shouldCancel = await showDialog<bool?>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => DesktopDialog(
|
||||
maxWidth: 580,
|
||||
maxHeight: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 32,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Cancel fusion?",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: () => Navigator.of(context).pop(false),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 0,
|
||||
right: 32,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Do you really want to cancel the fusion process?",
|
||||
style: STextStyles.smallMed14(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "No",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
label: "Yes",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (shouldCancel == true && mounted) {
|
||||
final fusionWallet = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as FusionWalletInterface;
|
||||
|
||||
await showLoading(
|
||||
whileFuture: Future.wait([
|
||||
fusionWallet.stop(),
|
||||
Future<void>.delayed(const Duration(seconds: 2)),
|
||||
]),
|
||||
context: context,
|
||||
isDesktop: true,
|
||||
message: "Stopping fusion",
|
||||
);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool _succeeded =
|
||||
ref.watch(fusionProgressUIStateProvider(widget.walletId)).succeeded;
|
||||
|
||||
final bool _failed =
|
||||
ref.watch(fusionProgressUIStateProvider(widget.walletId)).failed;
|
||||
|
||||
final int _fusionRoundsCompleted = ref
|
||||
.watch(fusionProgressUIStateProvider(widget.walletId))
|
||||
.fusionRoundsCompleted;
|
||||
|
||||
return DesktopDialog(
|
||||
maxHeight: 600,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 32),
|
||||
child: Text(
|
||||
"Fusion progress",
|
||||
style: STextStyles.desktopH2(context),
|
||||
),
|
||||
),
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: () async {
|
||||
if (await _requestAndProcessCancel()) {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 20,
|
||||
left: 32,
|
||||
right: 32,
|
||||
bottom: 32,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_fusionRoundsCompleted > 0
|
||||
? RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Fusion rounds completed: $_fusionRoundsCompleted",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)
|
||||
: RoundedContainer(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.snackBarBackError,
|
||||
child: Text(
|
||||
"Do not close this window. If you exit, "
|
||||
"the process will be canceled.",
|
||||
style: STextStyles.smallMed14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.snackBarTextError,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
FusionProgress(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
if (_succeeded)
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
buttonHeight: ButtonHeight.m,
|
||||
label: "Fuse again",
|
||||
onPressed: _fuseAgain,
|
||||
),
|
||||
),
|
||||
if (_succeeded)
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
if (_failed)
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
buttonHeight: ButtonHeight.m,
|
||||
label: "Try again",
|
||||
onPressed: _fuseAgain,
|
||||
),
|
||||
),
|
||||
if (_failed)
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
if (!_succeeded && !_failed) const Spacer(),
|
||||
if (!_succeeded && !_failed)
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
buttonHeight: ButtonHeight.m,
|
||||
enabled: true,
|
||||
label: "Cancel",
|
||||
onPressed: () async {
|
||||
if (await _requestAndProcessCancel()) {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Fuse again.
|
||||
void _fuseAgain() async {
|
||||
final fusionWallet = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as FusionWalletInterface;
|
||||
|
||||
final fusionInfo = ref.read(prefsChangeNotifierProvider).fusionServerInfo;
|
||||
|
||||
try {
|
||||
fusionWallet.uiState = ref.read(
|
||||
fusionProgressUIStateProvider(widget.walletId),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!e.toString().contains(
|
||||
"FusionProgressUIState was already set for ${widget.walletId}")) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
unawaited(fusionWallet.fuse(fusionInfo: fusionInfo));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_item_card.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart';
|
||||
import 'package:stackwallet/providers/cash_fusion/fusion_progress_ui_state_provider.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||
|
||||
class FusionProgress extends ConsumerWidget {
|
||||
const FusionProgress({super.key, required this.walletId});
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_ProgressItem(
|
||||
iconAsset: Assets.svg.node,
|
||||
label: "Connecting to server",
|
||||
state: ref.watch(fusionProgressUIStateProvider(walletId)
|
||||
.select((value) => value.connecting))),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
_ProgressItem(
|
||||
iconAsset: Assets.svg.upFromLine,
|
||||
label: "Allocating outputs",
|
||||
state: ref.watch(fusionProgressUIStateProvider(walletId)
|
||||
.select((value) => value.outputs))),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
_ProgressItem(
|
||||
iconAsset: Assets.svg.peers,
|
||||
label: "Waiting for peers",
|
||||
state: ref.watch(fusionProgressUIStateProvider(walletId)
|
||||
.select((value) => value.peers))),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
_ProgressItem(
|
||||
iconAsset: Assets.svg.fusing,
|
||||
label: "Fusing",
|
||||
state: ref.watch(fusionProgressUIStateProvider(walletId)
|
||||
.select((value) => value.fusing))),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
_ProgressItem(
|
||||
iconAsset: Assets.svg.checkCircle,
|
||||
label: "Complete",
|
||||
state: ref.watch(fusionProgressUIStateProvider(walletId)
|
||||
.select((value) => value.complete))),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProgressItem extends StatelessWidget {
|
||||
const _ProgressItem({
|
||||
super.key,
|
||||
required this.iconAsset,
|
||||
required this.label,
|
||||
required this.state,
|
||||
});
|
||||
|
||||
final String iconAsset;
|
||||
final String label;
|
||||
final CashFusionState state;
|
||||
|
||||
Widget _getIconForState(CashFusionStatus state, BuildContext context) {
|
||||
switch (state) {
|
||||
case CashFusionStatus.waiting:
|
||||
return SvgPicture.asset(
|
||||
Assets.svg.loader,
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.buttonBackSecondary,
|
||||
);
|
||||
case CashFusionStatus.running:
|
||||
return SvgPicture.asset(
|
||||
Assets.svg.loader,
|
||||
color: Theme.of(context).extension<StackColors>()!.accentColorGreen,
|
||||
);
|
||||
case CashFusionStatus.success:
|
||||
return SvgPicture.asset(
|
||||
Assets.svg.checkCircle,
|
||||
color: Theme.of(context).extension<StackColors>()!.accentColorGreen,
|
||||
);
|
||||
case CashFusionStatus.failed:
|
||||
return SvgPicture.asset(
|
||||
Assets.svg.circleAlert,
|
||||
color: Theme.of(context).extension<StackColors>()!.textError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConditionalParent(
|
||||
condition: Util.isDesktop,
|
||||
builder: (child) => RoundedContainer(
|
||||
padding: EdgeInsets.zero,
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
borderColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
child: child,
|
||||
),
|
||||
child: RestoringItemCard(
|
||||
left: SizedBox(
|
||||
width: 32,
|
||||
height: 32,
|
||||
child: RoundedContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.buttonBackSecondary,
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
iconAsset,
|
||||
width: 18,
|
||||
height: 18,
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
right: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: _getIconForState(state.status, context),
|
||||
),
|
||||
title: label,
|
||||
subTitle: state.info != null && state.info!.isNotEmpty
|
||||
? Text(
|
||||
state.info!,
|
||||
style: STextStyles.w500_12(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textError,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -218,7 +218,10 @@ class _DesktopTokenViewState extends ConsumerState<DesktopTokenView> {
|
|||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
AllTransactionsView.routeName,
|
||||
arguments: widget.walletId,
|
||||
arguments: (
|
||||
walletId: widget.walletId,
|
||||
isTokens: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -21,6 +21,8 @@ import 'package:stackwallet/pages/special/firo_rescan_recovery_error_dialog.dart
|
|||
import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart';
|
||||
|
@ -445,7 +447,7 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
|||
.getManager(widget.walletId)
|
||||
.hasTokenSupport))
|
||||
? "Tokens"
|
||||
: "Recent transactions",
|
||||
: "Recent activity",
|
||||
style: STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
|
@ -460,30 +462,33 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
|||
.hasTokenSupport))
|
||||
? "Edit"
|
||||
: "See all",
|
||||
onTap: ref.watch(walletsChangeNotifierProvider.select(
|
||||
(value) => value
|
||||
.getManager(widget.walletId)
|
||||
.hasTokenSupport))
|
||||
? () async {
|
||||
final result = await showDialog<int?>(
|
||||
context: context,
|
||||
builder: (context) => EditWalletTokensView(
|
||||
walletId: widget.walletId,
|
||||
isDesktopPopup: true,
|
||||
),
|
||||
);
|
||||
onTap: () async {
|
||||
if (ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.hasTokenSupport) {
|
||||
final result = await showDialog<int?>(
|
||||
context: context,
|
||||
builder: (context) => EditWalletTokensView(
|
||||
walletId: widget.walletId,
|
||||
isDesktopPopup: true,
|
||||
),
|
||||
);
|
||||
|
||||
if (result == 42) {
|
||||
// wallet tokens were edited so update ui
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
AllTransactionsView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
},
|
||||
if (result == 42) {
|
||||
// wallet tokens were edited so update ui
|
||||
setState(() {});
|
||||
}
|
||||
} else {
|
||||
await Navigator.of(context).pushNamed(
|
||||
coin == Coin.bitcoincash ||
|
||||
coin == Coin.bitcoincashTestnet
|
||||
? AllTransactionsV2View.routeName
|
||||
: AllTransactionsView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -514,13 +519,18 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
|||
? MyTokensView(
|
||||
walletId: widget.walletId,
|
||||
)
|
||||
: TransactionsList(
|
||||
managerProvider: ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value.getManagerProvider(
|
||||
widget.walletId))),
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
: coin == Coin.bitcoincash ||
|
||||
coin == Coin.bitcoincashTestnet
|
||||
? TransactionsV2List(
|
||||
walletId: widget.walletId,
|
||||
)
|
||||
: TransactionsList(
|
||||
managerProvider: ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value.getManagerProvider(
|
||||
widget.walletId))),
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -19,6 +19,7 @@ import 'package:stackwallet/notifications/show_flush_bar.dart';
|
|||
import 'package:stackwallet/pages/monkey/monkey_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/paynym_claim_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/cashfusion/desktop_cashfusion_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/coin_control/desktop_coin_control_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/desktop_menu.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
|
||||
|
@ -84,6 +85,7 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
onWhirlpoolPressed: _onWhirlpoolPressed,
|
||||
onOrdinalsPressed: _onOrdinalsPressed,
|
||||
onMonkeyPressed: _onMonkeyPressed,
|
||||
onFusionPressed: _onFusionPressed,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -335,6 +337,15 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
);
|
||||
}
|
||||
|
||||
void _onFusionPressed() {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
|
||||
Navigator.of(context).pushNamed(
|
||||
DesktopCashFusionView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final manager = ref.watch(
|
||||
|
@ -354,7 +365,9 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
manager.coin == Coin.firoTestNet ||
|
||||
manager.hasWhirlpoolSupport ||
|
||||
manager.coin == Coin.banano ||
|
||||
manager.hasOrdinalsSupport;
|
||||
manager.hasOrdinalsSupport ||
|
||||
manager.hasFusionSupport;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (Constants.enableExchange)
|
||||
|
|
|
@ -31,6 +31,7 @@ class MoreFeaturesDialog extends ConsumerStatefulWidget {
|
|||
required this.onWhirlpoolPressed,
|
||||
required this.onOrdinalsPressed,
|
||||
required this.onMonkeyPressed,
|
||||
required this.onFusionPressed,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
|
@ -40,6 +41,7 @@ class MoreFeaturesDialog extends ConsumerStatefulWidget {
|
|||
final VoidCallback? onWhirlpoolPressed;
|
||||
final VoidCallback? onOrdinalsPressed;
|
||||
final VoidCallback? onMonkeyPressed;
|
||||
final VoidCallback? onFusionPressed;
|
||||
|
||||
@override
|
||||
ConsumerState<MoreFeaturesDialog> createState() => _MoreFeaturesDialogState();
|
||||
|
@ -121,6 +123,13 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
|
|||
iconAsset: Assets.svg.monkey,
|
||||
onPressed: () => widget.onMonkeyPressed?.call(),
|
||||
),
|
||||
if (manager.hasFusionSupport)
|
||||
_MoreFeaturesItem(
|
||||
label: "CashFusion",
|
||||
detail: "Decentralized Bitcoin Cash mixing protocol",
|
||||
iconAsset: Assets.svg.cashFusion,
|
||||
onPressed: () => widget.onFusionPressed?.call(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 28,
|
||||
),
|
||||
|
|
|
@ -215,40 +215,37 @@ class _TorSettingsState extends ConsumerState<TorSettings> {
|
|||
return DesktopDialog(
|
||||
maxWidth: 580,
|
||||
maxHeight: double.infinity,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(
|
||||
left: 32,
|
||||
),
|
||||
child: Text(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
left: 20,
|
||||
bottom: 20,
|
||||
right: 10,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"What is Tor?",
|
||||
style:
|
||||
STextStyles.desktopH2(
|
||||
context),
|
||||
),
|
||||
),
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: () =>
|
||||
Navigator.of(context)
|
||||
.pop(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 12,
|
||||
left: 32,
|
||||
bottom: 32,
|
||||
right: 32,
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: () =>
|
||||
Navigator.of(context)
|
||||
.pop(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
"Short for \"The Onion Router\", is an open-source software that enables internet communication"
|
||||
" to remain anonymous by routing internet traffic through a series of layered nodes,"
|
||||
" to obscure the origin and destination of data.",
|
||||
|
@ -261,8 +258,8 @@ class _TorSettingsState extends ConsumerState<TorSettings> {
|
|||
.textDark3,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -287,43 +284,49 @@ class _TorSettingsState extends ConsumerState<TorSettings> {
|
|||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Tor killswitch",
|
||||
style: STextStyles.desktopTextExtraExtraSmall(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
RichText(
|
||||
textAlign: TextAlign.start,
|
||||
text: TextSpan(
|
||||
text: "What is Tor killswitch?",
|
||||
style: STextStyles.richLink(context).copyWith(
|
||||
fontSize: 14,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return DesktopDialog(
|
||||
maxWidth: 580,
|
||||
maxHeight: double.infinity,
|
||||
child: Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Tor killswitch",
|
||||
style: STextStyles.desktopTextExtraExtraSmall(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
RichText(
|
||||
textAlign: TextAlign.start,
|
||||
text: TextSpan(
|
||||
text: "What is Tor killswitch?",
|
||||
style: STextStyles.richLink(context).copyWith(
|
||||
fontSize: 14,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return DesktopDialog(
|
||||
maxWidth: 580,
|
||||
maxHeight: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
left: 20,
|
||||
bottom: 20,
|
||||
right: 10,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
|
@ -331,16 +334,11 @@ class _TorSettingsState extends ConsumerState<TorSettings> {
|
|||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(
|
||||
left: 32,
|
||||
),
|
||||
child: Text(
|
||||
"What is Tor killswitch?",
|
||||
style: STextStyles
|
||||
.desktopH2(context),
|
||||
),
|
||||
Text(
|
||||
"What is Tor killswitch?",
|
||||
style:
|
||||
STextStyles.desktopH2(
|
||||
context),
|
||||
),
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: () =>
|
||||
|
@ -349,44 +347,36 @@ class _TorSettingsState extends ConsumerState<TorSettings> {
|
|||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(
|
||||
top: 12,
|
||||
left: 32,
|
||||
bottom: 32,
|
||||
right: 32,
|
||||
),
|
||||
child: Text(
|
||||
"A security feature that protects your information from accidental exposure by"
|
||||
" disconnecting your device from the Tor network if the"
|
||||
" connection is disrupted or compromised.",
|
||||
style: STextStyles
|
||||
.desktopTextMedium(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<
|
||||
StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
"A security feature that protects your information from accidental exposure by"
|
||||
" disconnecting your device from the Tor network if the"
|
||||
" connection is disrupted or compromised.",
|
||||
style: STextStyles
|
||||
.desktopTextMedium(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<
|
||||
StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: SizedBox(
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 40,
|
||||
child: DraggableSwitchButton(
|
||||
|
@ -401,13 +391,13 @@ class _TorSettingsState extends ConsumerState<TorSettings> {
|
|||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/models/fusion_progress_ui_state.dart';
|
||||
|
||||
final fusionProgressUIStateProvider =
|
||||
ChangeNotifierProvider.family<FusionProgressUIState, String>(
|
||||
(ref, walletId) {
|
||||
return FusionProgressUIState();
|
||||
},
|
||||
);
|
|
@ -17,6 +17,7 @@ import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/eth_token_
|
|||
import 'package:stackwallet/models/buy/response_objects/quote.dart';
|
||||
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/contact_entry.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/models/isar/ordinal.dart';
|
||||
|
@ -44,6 +45,8 @@ import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_name_
|
|||
import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart';
|
||||
import 'package:stackwallet/pages/buy_view/buy_quote_preview.dart';
|
||||
import 'package:stackwallet/pages/buy_view/buy_view.dart';
|
||||
import 'package:stackwallet/pages/cashfusion/cashfusion_view.dart';
|
||||
import 'package:stackwallet/pages/cashfusion/fusion_progress_view.dart';
|
||||
import 'package:stackwallet/pages/coin_control/coin_control_view.dart';
|
||||
import 'package:stackwallet/pages/coin_control/utxo_details_view.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart';
|
||||
|
@ -129,11 +132,15 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions
|
|||
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/fusion_group_details_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
||||
import 'package:stackwallet/pages/wallets_view/wallets_overview.dart';
|
||||
import 'package:stackwallet/pages/wallets_view/wallets_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/address_book_view/desktop_address_book.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/cashfusion/desktop_cashfusion_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/coin_control/desktop_coin_control_view.dart';
|
||||
// import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_buys_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/desktop_buy/desktop_buy_view.dart';
|
||||
|
@ -579,6 +586,48 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case CashFusionView.routeName:
|
||||
if (args is String) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => CashFusionView(
|
||||
walletId: args,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case FusionProgressView.routeName:
|
||||
if (args is String) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => FusionProgressView(
|
||||
walletId: args,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case DesktopCashFusionView.routeName:
|
||||
if (args is String) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => DesktopCashFusionView(
|
||||
walletId: args,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case GlobalSettingsView.routeName:
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
|
@ -1214,7 +1263,55 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case TransactionV2DetailsView.routeName:
|
||||
if (args is ({TransactionV2 tx, Coin coin, String walletId})) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => TransactionV2DetailsView(
|
||||
transaction: args.tx,
|
||||
coin: args.coin,
|
||||
walletId: args.walletId,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case FusionGroupDetailsView.routeName:
|
||||
if (args is ({
|
||||
List<TransactionV2> transactions,
|
||||
Coin coin,
|
||||
String walletId
|
||||
})) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => FusionGroupDetailsView(
|
||||
transactions: args.transactions,
|
||||
coin: args.coin,
|
||||
walletId: args.walletId,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case AllTransactionsView.routeName:
|
||||
if (args is ({String walletId, bool isTokens})) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => AllTransactionsView(
|
||||
walletId: args.walletId,
|
||||
isTokens: args.isTokens,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (args is String) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
|
@ -1228,6 +1325,20 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case AllTransactionsV2View.routeName:
|
||||
if (args is String) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => AllTransactionsV2View(
|
||||
walletId: args,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case TransactionSearchFilterView.routeName:
|
||||
if (args is Coin) {
|
||||
return getRoute(
|
||||
|
@ -1992,6 +2103,17 @@ class RouteGenerator {
|
|||
name: settings.name,
|
||||
),
|
||||
);
|
||||
} else if (args is ({String walletId, bool popPrevious})) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => TokenView(
|
||||
walletId: args.walletId,
|
||||
popPrevious: args.popPrevious,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
|
|
61
lib/services/coins/bitcoincash/bch_utils.dart
Normal file
61
lib/services/coins/bitcoincash/bch_utils.dart
Normal file
|
@ -0,0 +1,61 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bitcoindart/src/utils/constants/op.dart' as op;
|
||||
import 'package:bitcoindart/src/utils/script.dart' as bscript;
|
||||
import 'package:stackwallet/utilities/extensions/impl/string.dart';
|
||||
|
||||
abstract final class BchUtils {
|
||||
static const FUSE_ID = 'FUZ\x00';
|
||||
|
||||
static bool isSLP(Uint8List scriptPubKey) {
|
||||
const id = [83, 76, 80, 0]; // 'SLP\x00'
|
||||
final decompiled = bscript.decompile(scriptPubKey);
|
||||
|
||||
if (decompiled != null &&
|
||||
decompiled.length > 1 &&
|
||||
decompiled.first == op.OPS["OP_RETURN"]) {
|
||||
final _id = decompiled[1];
|
||||
|
||||
if (_id is List<int> && _id.length == id.length) {
|
||||
for (int i = 0; i < id.length; i++) {
|
||||
if (_id[i] != id[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// lists match!
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isFUZE(Uint8List scriptPubKey) {
|
||||
final id = FUSE_ID.toUint8ListFromUtf8;
|
||||
final decompiled = bscript.decompile(scriptPubKey);
|
||||
|
||||
if (decompiled != null &&
|
||||
decompiled.length > 2 &&
|
||||
decompiled.first == op.OPS["OP_RETURN"]) {
|
||||
// check session hash length. Should be 32 bytes
|
||||
final sessionHash = decompiled[2];
|
||||
if (!(sessionHash is List<int> && sessionHash.length == 32)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final _id = decompiled[1];
|
||||
|
||||
if (_id is List<int> && _id.length == id.length) {
|
||||
for (int i = 0; i < id.length; i++) {
|
||||
if (_id[i] != id[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// lists match!
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
317
lib/services/coins/bitcoincash/cashtokens.dart
Normal file
317
lib/services/coins/bitcoincash/cashtokens.dart
Normal file
|
@ -0,0 +1,317 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
// The Structure enum
|
||||
enum Structure {
|
||||
HasAmount,
|
||||
HasNFT,
|
||||
HasCommitmentLength,
|
||||
}
|
||||
|
||||
// The Capability enum
|
||||
enum Capability {
|
||||
NoCapability,
|
||||
Mutable,
|
||||
Minting,
|
||||
}
|
||||
|
||||
// Used as a "custom tuple" for the supporting functions of readCompactSize to return
|
||||
// a convenient data structure.
|
||||
class CompactSizeResult {
|
||||
final int amount;
|
||||
final int bytesRead;
|
||||
|
||||
CompactSizeResult({required this.amount, required this.bytesRead});
|
||||
}
|
||||
|
||||
// This class is a data structure representing the entire output, comprised of both the
|
||||
// normal Script pub key and the token data. We get this after we parse/unwrap the raw
|
||||
// output.
|
||||
class ParsedOutput {
|
||||
List<int>? script_pub_key;
|
||||
TokenOutputData? token_data;
|
||||
ParsedOutput({this.script_pub_key, this.token_data});
|
||||
}
|
||||
|
||||
// This is equivalent to the Electron Cash python's "OutputData" in token.py.
|
||||
// Named here specifically as "TokenOutputData" to reflect the fact that
|
||||
// it is specifically for tokens, whereas the other class ParsedOutput represents
|
||||
// the entire output, comprised of both the normal Script pub key and the token data.
|
||||
class TokenOutputData {
|
||||
Uint8List? id;
|
||||
int? amount;
|
||||
Uint8List? commitment;
|
||||
Uint8List? bitfield; // A byte (Uint8List of length 1)
|
||||
|
||||
// Constructor
|
||||
TokenOutputData({
|
||||
this.id,
|
||||
this.amount,
|
||||
this.commitment,
|
||||
this.bitfield,
|
||||
});
|
||||
|
||||
// Get the "capability", see Capability enum.
|
||||
int getCapability() {
|
||||
if (bitfield != null) {
|
||||
return bitfield![0] & 0x0f;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// functions to return attributes of the token bitfield.
|
||||
bool hasCommitmentLength() {
|
||||
if (bitfield != null) {
|
||||
return (bitfield![0] & 0x40) != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasAmount() {
|
||||
if (bitfield != null) {
|
||||
return (bitfield![0] & 0x10) != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasNFT() {
|
||||
if (bitfield != null) {
|
||||
return (bitfield![0] & 0x20) != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Functions to return specific attributes based on the Capability.
|
||||
bool isMintingNFT() {
|
||||
return hasNFT() && getCapability() == Capability.Minting.index;
|
||||
}
|
||||
|
||||
bool isMutableNFT() {
|
||||
return hasNFT() && getCapability() == Capability.Mutable.index;
|
||||
}
|
||||
|
||||
bool isImmutableNFT() {
|
||||
return hasNFT() && getCapability() == Capability.NoCapability.index;
|
||||
}
|
||||
|
||||
// This function validates if the bitfield makes sense or violates known rules/logic.
|
||||
bool isValidBitfield() {
|
||||
if (bitfield == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int s = bitfield![0] & 0xf0;
|
||||
if (s >= 0x80 || s == 0x00) {
|
||||
return false;
|
||||
}
|
||||
if (bitfield![0] & 0x0f > 2) {
|
||||
return false;
|
||||
}
|
||||
if (!hasNFT() && !hasAmount()) {
|
||||
return false;
|
||||
}
|
||||
if (!hasNFT() && (bitfield![0] & 0x0f) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (!hasNFT() && hasCommitmentLength()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// The serialze and deserialize functions are the nuts and bolts of how we unpack
|
||||
// and pack outputs. These are called by the wrap and unwrap functions.
|
||||
int deserialize(Uint8List buffer, {int cursor = 0, bool strict = false}) {
|
||||
try {
|
||||
this.id = buffer.sublist(cursor, cursor + 32);
|
||||
cursor += 32;
|
||||
|
||||
this.bitfield = Uint8List.fromList([buffer[cursor]]);
|
||||
cursor += 1;
|
||||
|
||||
if (this.hasCommitmentLength()) {
|
||||
// Read the first byte to determine the length of the commitment data
|
||||
int commitmentLength = buffer[cursor];
|
||||
|
||||
// Move cursor to the next byte
|
||||
cursor += 1;
|
||||
|
||||
// Read 'commitmentLength' bytes for the commitment data
|
||||
this.commitment = buffer.sublist(cursor, cursor + commitmentLength);
|
||||
|
||||
// Adjust the cursor by the length of the commitment data
|
||||
cursor += commitmentLength;
|
||||
} else {
|
||||
this.commitment = null;
|
||||
}
|
||||
|
||||
if (this.hasAmount()) {
|
||||
// Use readCompactSize that returns CompactSizeResult
|
||||
CompactSizeResult result =
|
||||
readCompactSize(buffer, cursor, strict: strict);
|
||||
this.amount = result.amount;
|
||||
cursor += result.bytesRead;
|
||||
} else {
|
||||
this.amount = 0;
|
||||
}
|
||||
|
||||
if (!this.isValidBitfield() ||
|
||||
(this.hasAmount() && this.amount == 0) ||
|
||||
(this.amount! < 0 || this.amount! > (1 << 63) - 1) ||
|
||||
(this.hasCommitmentLength() && this.commitment!.isEmpty) ||
|
||||
(this.amount! == 0 && !this.hasNFT())) {
|
||||
throw Exception('Unable to parse token data or token data is invalid');
|
||||
}
|
||||
|
||||
return cursor; // Return the number of bytes read
|
||||
} catch (e) {
|
||||
throw Exception('Deserialization failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize method
|
||||
Uint8List serialize() {
|
||||
var buffer = BytesBuilder();
|
||||
|
||||
// write ID and bitfield
|
||||
buffer.add(this.id!);
|
||||
buffer.addByte(this.bitfield![0]);
|
||||
|
||||
// Write optional fields
|
||||
if (this.hasCommitmentLength()) {
|
||||
buffer.add(this.commitment!);
|
||||
}
|
||||
|
||||
if (this.hasAmount()) {
|
||||
List<int> compactSizeBytes = writeCompactSize(this.amount!);
|
||||
buffer.add(compactSizeBytes);
|
||||
}
|
||||
|
||||
return buffer.toBytes();
|
||||
}
|
||||
} //END OF OUTPUTDATA CLASS
|
||||
|
||||
// The prefix byte is specified by the CashTokens spec.
|
||||
final List<int> PREFIX_BYTE = [0xef];
|
||||
|
||||
// This function wraps a "normal" output together with token data.
|
||||
ParsedOutput wrap_spk(TokenOutputData? token_data, Uint8List script_pub_key) {
|
||||
ParsedOutput parsedOutput = ParsedOutput();
|
||||
|
||||
if (token_data == null) {
|
||||
parsedOutput.script_pub_key = script_pub_key;
|
||||
return parsedOutput;
|
||||
}
|
||||
|
||||
final buf = BytesBuilder();
|
||||
|
||||
buf.add(PREFIX_BYTE);
|
||||
buf.add(token_data.serialize());
|
||||
buf.add(script_pub_key);
|
||||
|
||||
parsedOutput.script_pub_key = buf.toBytes();
|
||||
parsedOutput.token_data = token_data;
|
||||
|
||||
return parsedOutput;
|
||||
}
|
||||
|
||||
// This function unwraps any output, either "normal" (containing no token data)
|
||||
// or an output with token data. If no token data, just the output is returned,
|
||||
// and if token data exists, both the output and token data are returned.
|
||||
// Note that the data returend in both cases in of ParsedOutput type, which
|
||||
// holds both the script pub key and token data.
|
||||
ParsedOutput unwrap_spk(Uint8List wrapped_spk) {
|
||||
ParsedOutput parsedOutput = ParsedOutput();
|
||||
|
||||
if (wrapped_spk.isEmpty || wrapped_spk[0] != PREFIX_BYTE[0]) {
|
||||
parsedOutput.script_pub_key = wrapped_spk;
|
||||
return parsedOutput;
|
||||
}
|
||||
|
||||
int read_cursor = 1; // Start after the PREFIX_BYTE
|
||||
TokenOutputData token_data = TokenOutputData();
|
||||
|
||||
Uint8List wrapped_spk_without_prefix_byte;
|
||||
try {
|
||||
// Deserialize updates read_cursor by the number of bytes read
|
||||
|
||||
wrapped_spk_without_prefix_byte = wrapped_spk.sublist(read_cursor);
|
||||
int bytesRead = token_data.deserialize(wrapped_spk_without_prefix_byte);
|
||||
|
||||
read_cursor += bytesRead;
|
||||
parsedOutput.token_data = token_data;
|
||||
parsedOutput.script_pub_key = wrapped_spk.sublist(read_cursor);
|
||||
} catch (e) {
|
||||
// If unable to deserialize, return all bytes as the full scriptPubKey
|
||||
parsedOutput.script_pub_key = wrapped_spk;
|
||||
}
|
||||
|
||||
return parsedOutput;
|
||||
}
|
||||
|
||||
// HELPER FUNCTIONS
|
||||
|
||||
//These are part of a "length value " scheme where the length (and endianness) are given first
|
||||
// and inform the program of how many bytes to grab next. These are in turn used by the serialize
|
||||
// and deserialize functions.-
|
||||
CompactSizeResult readCompactSize(
|
||||
Uint8List buffer,
|
||||
int cursor, {
|
||||
bool strict = false,
|
||||
}) {
|
||||
int bytesRead = 0; // Variable to count bytes read
|
||||
int val;
|
||||
try {
|
||||
val = buffer[cursor];
|
||||
cursor += 1;
|
||||
bytesRead += 1;
|
||||
int minVal;
|
||||
if (val == 253) {
|
||||
val = buffer.buffer.asByteData().getUint16(cursor, Endian.little);
|
||||
cursor += 2;
|
||||
bytesRead += 2;
|
||||
minVal = 253;
|
||||
} else if (val == 254) {
|
||||
val = buffer.buffer.asByteData().getUint32(cursor, Endian.little);
|
||||
cursor += 4;
|
||||
bytesRead += 4;
|
||||
minVal = 1 << 16;
|
||||
} else if (val == 255) {
|
||||
val = buffer.buffer.asByteData().getInt64(cursor, Endian.little);
|
||||
cursor += 8;
|
||||
bytesRead += 8;
|
||||
minVal = 1 << 32;
|
||||
} else {
|
||||
minVal = 0;
|
||||
}
|
||||
if (strict && val < minVal) {
|
||||
throw Exception("CompactSize is not minimally encoded");
|
||||
}
|
||||
|
||||
return CompactSizeResult(amount: val, bytesRead: bytesRead);
|
||||
} catch (e) {
|
||||
throw Exception("attempt to read past end of buffer");
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List writeCompactSize(int size) {
|
||||
var buffer = ByteData(9); // Maximum needed size for compact size is 9 bytes
|
||||
if (size < 0) {
|
||||
throw Exception("attempt to write size < 0");
|
||||
} else if (size < 253) {
|
||||
return Uint8List.fromList([size]);
|
||||
} else if (size < (1 << 16)) {
|
||||
buffer.setUint8(0, 253);
|
||||
buffer.setUint16(1, size, Endian.little);
|
||||
return buffer.buffer.asUint8List(0, 3);
|
||||
} else if (size < (1 << 32)) {
|
||||
buffer.setUint8(0, 254);
|
||||
buffer.setUint32(1, size, Endian.little);
|
||||
return buffer.buffer.asUint8List(0, 5);
|
||||
} else if (size < (1 << 64)) {
|
||||
buffer.setUint8(0, 255);
|
||||
buffer.setInt64(1, size, Endian.little);
|
||||
return buffer.buffer.asUint8List(0, 9);
|
||||
} else {
|
||||
throw Exception("Size too large to represent as CompactSize");
|
||||
}
|
||||
}
|
|
@ -227,7 +227,9 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
Future<void> updateBalance() async {
|
||||
web3.Web3Client client = getEthClient();
|
||||
web3.EtherAmount ethBalance = await client.getBalance(_credentials.address);
|
||||
|
||||
final address = web3.EthereumAddress.fromHex(await currentReceivingAddress);
|
||||
web3.EtherAmount ethBalance = await client.getBalance(address);
|
||||
_balance = Balance(
|
||||
total: Amount(
|
||||
rawValue: ethBalance.getInWei,
|
||||
|
|
|
@ -21,6 +21,7 @@ import 'package:stackwallet/services/event_bus/events/global/node_connection_sta
|
|||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
|
||||
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
|
||||
import 'package:stackwallet/services/mixins/ordinals_interface.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/services/mixins/xpubable.dart';
|
||||
|
@ -253,6 +254,8 @@ class Manager with ChangeNotifier {
|
|||
|
||||
bool get hasWhirlpoolSupport => false;
|
||||
|
||||
bool get hasFusionSupport => _currentWallet is FusionWalletInterface;
|
||||
|
||||
int get rescanOnOpenVersion =>
|
||||
DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNameDBInfo,
|
||||
|
|
|
@ -75,7 +75,7 @@ abstract class EthereumAPI {
|
|||
for (final map in list!) {
|
||||
final txn = EthTxDTO.fromMap(Map<String, dynamic>.from(map as Map));
|
||||
|
||||
if (txn.hasToken == 0 || includeTokens) {
|
||||
if (!txn.hasToken || includeTokens) {
|
||||
txns.add(txn);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -468,7 +468,7 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache {
|
|||
}
|
||||
|
||||
final response2 = await EthereumAPI.getEthTokenTransactionsByTxids(
|
||||
response.value!.map((e) => e.transactionHash).toList(),
|
||||
response.value!.map((e) => e.transactionHash).toSet().toList(),
|
||||
);
|
||||
|
||||
if (response2.value == null) {
|
||||
|
@ -477,14 +477,25 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache {
|
|||
}
|
||||
final List<Tuple2<EthTokenTxDto, EthTokenTxExtraDTO>> data = [];
|
||||
for (final tokenDto in response.value!) {
|
||||
data.add(
|
||||
Tuple2(
|
||||
tokenDto,
|
||||
response2.value!.firstWhere(
|
||||
(e) => e.hash == tokenDto.transactionHash,
|
||||
try {
|
||||
final txExtra = response2.value!.firstWhere(
|
||||
(e) => e.hash == tokenDto.transactionHash,
|
||||
);
|
||||
data.add(
|
||||
Tuple2(
|
||||
tokenDto,
|
||||
txExtra,
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} catch (_) {
|
||||
// Server indexing failed for some reason. Instead of hard crashing or
|
||||
// showing no transactions we just skip it here. Not ideal but better
|
||||
// than nothing showing up
|
||||
Logging.instance.log(
|
||||
"Server error: Transaction ${tokenDto.transactionHash} not found.",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final List<Tuple2<Transaction, Address?>> txnsData = [];
|
||||
|
|
71
lib/services/fusion_tor_service.dart
Normal file
71
lib/services/fusion_tor_service.dart
Normal file
|
@ -0,0 +1,71 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:tor_ffi_plugin/tor_ffi_plugin.dart';
|
||||
|
||||
class FusionTorService {
|
||||
Tor? _tor;
|
||||
String? _torDataDirPath;
|
||||
|
||||
TorStatus get status => _tor!.status;
|
||||
|
||||
/// Singleton instance of the TorService.
|
||||
///
|
||||
/// Use this to access the TorService and its properties.
|
||||
static final sharedInstance = FusionTorService._();
|
||||
|
||||
// private constructor for singleton
|
||||
FusionTorService._();
|
||||
|
||||
/// Getter for the proxyInfo.
|
||||
///
|
||||
/// Throws if Tor is not connected.
|
||||
({
|
||||
InternetAddress host,
|
||||
int port,
|
||||
}) getProxyInfo() {
|
||||
try {
|
||||
return (
|
||||
host: InternetAddress.loopbackIPv4,
|
||||
port: _tor!.port,
|
||||
);
|
||||
} catch (_) {
|
||||
throw Exception("Tor proxy info fetched while not connected!");
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the tor ffi lib instance if it hasn't already been set. Nothing
|
||||
/// changes if _tor is already been set.
|
||||
void init({
|
||||
required String torDataDirPath,
|
||||
Tor? mockableOverride,
|
||||
}) {
|
||||
_tor ??= mockableOverride ?? Tor.instance;
|
||||
_torDataDirPath ??= torDataDirPath;
|
||||
}
|
||||
|
||||
/// Start the Tor service.
|
||||
///
|
||||
/// This will start the Tor service and establish a Tor circuit.
|
||||
///
|
||||
/// Throws an exception if the Tor library was not inited or if the Tor
|
||||
/// service fails to start.
|
||||
///
|
||||
/// Returns a Future that completes when the Tor service has started.
|
||||
Future<void> start() async {
|
||||
if (_tor == null || _torDataDirPath == null) {
|
||||
throw Exception("FusionTorService.init has not been called!");
|
||||
}
|
||||
|
||||
// Start the Tor service.
|
||||
try {
|
||||
await _tor!.start(torDataDirPath: _torDataDirPath!);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"FusionTorService.start failed: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,13 +12,114 @@ import 'dart:convert';
|
|||
|
||||
import 'package:bip47/src/util.dart';
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/util.dart' as util;
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class TT with ElectrumXParsing {
|
||||
//
|
||||
}
|
||||
|
||||
mixin ElectrumXParsing {
|
||||
Future<TransactionV2> getTransaction(
|
||||
String txHash,
|
||||
Coin coin,
|
||||
String walletId,
|
||||
CachedElectrumX cachedElectrumX, [
|
||||
String? debugTitle,
|
||||
]) async {
|
||||
final jsonTx = await cachedElectrumX.getTransaction(
|
||||
txHash: txHash,
|
||||
coin: coin,
|
||||
);
|
||||
print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
|
||||
util.Util.printJson(jsonTx, debugTitle);
|
||||
print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
|
||||
|
||||
// parse inputs
|
||||
final List<InputV2> inputs = [];
|
||||
for (final jsonInput in jsonTx["vin"] as List) {
|
||||
final map = Map<String, dynamic>.from(jsonInput as Map);
|
||||
|
||||
final List<String> addresses = [];
|
||||
String valueStringSats = "0";
|
||||
OutpointV2? outpoint;
|
||||
|
||||
final coinbase = map["coinbase"] as String?;
|
||||
|
||||
if (coinbase == null) {
|
||||
final txid = map["txid"] as String;
|
||||
final vout = map["vout"] as int;
|
||||
|
||||
final inputTx =
|
||||
await cachedElectrumX.getTransaction(txHash: txid, coin: coin);
|
||||
|
||||
final prevOutJson = Map<String, dynamic>.from(
|
||||
(inputTx["vout"] as List).firstWhere((e) => e["n"] == vout) as Map);
|
||||
|
||||
final prevOut = OutputV2.fromElectrumXJson(
|
||||
prevOutJson,
|
||||
decimalPlaces: coin.decimals,
|
||||
walletOwns: false,
|
||||
);
|
||||
|
||||
outpoint = OutpointV2.isarCantDoRequiredInDefaultConstructor(
|
||||
txid: txid,
|
||||
vout: vout,
|
||||
);
|
||||
valueStringSats = prevOut.valueStringSats;
|
||||
addresses.addAll(prevOut.addresses);
|
||||
}
|
||||
|
||||
final input = InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptSigHex: map["scriptSig"]?["hex"] as String?,
|
||||
sequence: map["sequence"] as int?,
|
||||
outpoint: outpoint,
|
||||
valueStringSats: valueStringSats,
|
||||
addresses: addresses,
|
||||
witness: map["witness"] as String?,
|
||||
coinbase: coinbase,
|
||||
innerRedeemScriptAsm: map["innerRedeemscriptAsm"] as String?,
|
||||
walletOwns: false,
|
||||
);
|
||||
|
||||
inputs.add(input);
|
||||
}
|
||||
|
||||
// parse outputs
|
||||
final List<OutputV2> outputs = [];
|
||||
for (final outputJson in jsonTx["vout"] as List) {
|
||||
final output = OutputV2.fromElectrumXJson(
|
||||
Map<String, dynamic>.from(outputJson as Map),
|
||||
decimalPlaces: coin.decimals,
|
||||
walletOwns: false,
|
||||
);
|
||||
outputs.add(output);
|
||||
}
|
||||
|
||||
return TransactionV2(
|
||||
walletId: walletId,
|
||||
blockHash: jsonTx["blockhash"] as String?,
|
||||
hash: jsonTx["hash"] as String,
|
||||
txid: jsonTx["txid"] as String,
|
||||
height: jsonTx["height"] as int?,
|
||||
version: jsonTx["version"] as int,
|
||||
timestamp: jsonTx["blocktime"] as int? ??
|
||||
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
|
||||
inputs: List.unmodifiable(inputs),
|
||||
outputs: List.unmodifiable(outputs),
|
||||
subType: TransactionSubType.none,
|
||||
type: TransactionType.unknown,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Tuple2<Transaction, Address>> parseTransaction(
|
||||
Map<String, dynamic> txData,
|
||||
dynamic electrumxClient,
|
||||
|
|
744
lib/services/mixins/fusion_wallet_interface.dart
Normal file
744
lib/services/mixins/fusion_wallet_interface.dart
Normal file
|
@ -0,0 +1,744 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||
import 'package:bitcoindart/bitcoindart.dart' as btcdart;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:fusiondart/fusiondart.dart' as fusion;
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/isar/main_db.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
||||
import 'package:stackwallet/models/fusion_progress_ui_state.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart';
|
||||
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
|
||||
import 'package:stackwallet/services/fusion_tor_service.dart';
|
||||
import 'package:stackwallet/utilities/bip32_utils.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||
|
||||
const String kReservedFusionAddress = "reserved_fusion_address";
|
||||
|
||||
class FusionInfo {
|
||||
final String host;
|
||||
final int port;
|
||||
final bool ssl;
|
||||
|
||||
/// set to 0 for continuous
|
||||
final int rounds;
|
||||
|
||||
const FusionInfo({
|
||||
required this.host,
|
||||
required this.port,
|
||||
required this.ssl,
|
||||
required this.rounds,
|
||||
}) : assert(rounds >= 0);
|
||||
|
||||
static const DEFAULTS = FusionInfo(
|
||||
host: "fusion.servo.cash",
|
||||
port: 8789,
|
||||
ssl: true,
|
||||
// host: "cashfusion.stackwallet.com",
|
||||
// port: 8787,
|
||||
// ssl: false,
|
||||
rounds: 0, // 0 is continuous
|
||||
);
|
||||
|
||||
factory FusionInfo.fromJsonString(String jsonString) {
|
||||
final json = jsonDecode(jsonString);
|
||||
return FusionInfo(
|
||||
host: json['host'] as String,
|
||||
port: json['port'] as int,
|
||||
ssl: json['ssl'] as bool,
|
||||
rounds: json['rounds'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
String toJsonString() {
|
||||
return jsonEncode({
|
||||
'host': host,
|
||||
'port': port,
|
||||
'ssl': ssl,
|
||||
'rounds': rounds,
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return toJsonString();
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return other is FusionInfo &&
|
||||
other.host == host &&
|
||||
other.port == port &&
|
||||
other.ssl == ssl &&
|
||||
other.rounds == rounds;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return Object.hash(
|
||||
host.hashCode,
|
||||
port.hashCode,
|
||||
ssl.hashCode,
|
||||
rounds.hashCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A mixin for the BitcoinCashWallet class that adds CashFusion functionality.
|
||||
mixin FusionWalletInterface {
|
||||
// Passed in wallet data.
|
||||
late final String _walletId;
|
||||
late final Coin _coin;
|
||||
late final MainDB _db;
|
||||
late final FusionTorService _torService;
|
||||
late final Future<String?> _mnemonic;
|
||||
late final Future<String?> _mnemonicPassphrase;
|
||||
late final btcdart.NetworkType _network;
|
||||
|
||||
final _prefs = Prefs.instance;
|
||||
|
||||
// setting values on this should notify any listeners (the GUI)
|
||||
FusionProgressUIState? _uiState;
|
||||
FusionProgressUIState get uiState {
|
||||
if (_uiState == null) {
|
||||
throw Exception("FusionProgressUIState has not been set for $_walletId");
|
||||
}
|
||||
return _uiState!;
|
||||
}
|
||||
|
||||
set uiState(FusionProgressUIState state) {
|
||||
if (_uiState != null) {
|
||||
throw Exception("FusionProgressUIState was already set for $_walletId");
|
||||
}
|
||||
_uiState = state;
|
||||
}
|
||||
|
||||
// Passed in wallet functions.
|
||||
late final Future<List<Address>> Function({int numberOfAddresses})
|
||||
_getNextUnusedChangeAddresses;
|
||||
late final CachedElectrumX Function() _getWalletCachedElectrumX;
|
||||
late final Future<int> Function() _getChainHeight;
|
||||
late final Future<void> Function() _updateWalletUTXOS;
|
||||
late final String Function(String bchAddress, btcdart.NetworkType network)
|
||||
_convertToScriptHash;
|
||||
|
||||
// Fusion object.
|
||||
fusion.Fusion? _mainFusionObject;
|
||||
bool _stopRequested = false;
|
||||
|
||||
/// An int storing the number of successfully completed fusion rounds.
|
||||
int _completedFuseCount = 0;
|
||||
|
||||
/// An int storing the number of failed fusion rounds.
|
||||
int _failedFuseCount = 0;
|
||||
|
||||
/// The maximum number of consecutive failed fusion rounds before stopping.
|
||||
int get maxFailedFuseCount => 5;
|
||||
|
||||
/// Initializes the FusionWalletInterface mixin.
|
||||
///
|
||||
/// This function must be called before any other functions in this mixin.
|
||||
Future<void> initFusionInterface({
|
||||
required String walletId,
|
||||
required Coin coin,
|
||||
required MainDB db,
|
||||
required Future<List<Address>> Function({int numberOfAddresses})
|
||||
getNextUnusedChangeAddress,
|
||||
required CachedElectrumX Function() getWalletCachedElectrumX,
|
||||
required Future<int> Function() getChainHeight,
|
||||
required Future<void> Function() updateWalletUTXOS,
|
||||
required Future<String?> mnemonic,
|
||||
required Future<String?> mnemonicPassphrase,
|
||||
required btcdart.NetworkType network,
|
||||
required final String Function(
|
||||
String bchAddress, btcdart.NetworkType network)
|
||||
convertToScriptHash,
|
||||
}) async {
|
||||
// Set passed in wallet data.
|
||||
_walletId = walletId;
|
||||
_coin = coin;
|
||||
_db = db;
|
||||
_getNextUnusedChangeAddresses = getNextUnusedChangeAddress;
|
||||
_torService = FusionTorService.sharedInstance;
|
||||
_getWalletCachedElectrumX = getWalletCachedElectrumX;
|
||||
_getChainHeight = getChainHeight;
|
||||
_updateWalletUTXOS = updateWalletUTXOS;
|
||||
_mnemonic = mnemonic;
|
||||
_mnemonicPassphrase = mnemonicPassphrase;
|
||||
_network = network;
|
||||
_convertToScriptHash = convertToScriptHash;
|
||||
}
|
||||
|
||||
// callback to update the ui state object
|
||||
void _updateStatus({required fusion.FusionStatus status, String? info}) {
|
||||
switch (status) {
|
||||
case fusion.FusionStatus.connecting:
|
||||
_uiState?.setConnecting(
|
||||
CashFusionState(status: CashFusionStatus.running, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setOutputs(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setPeers(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setFusing(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setComplete(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null),
|
||||
shouldNotify: true);
|
||||
break;
|
||||
case fusion.FusionStatus.setup:
|
||||
_uiState?.setConnecting(
|
||||
CashFusionState(status: CashFusionStatus.success, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setOutputs(
|
||||
CashFusionState(status: CashFusionStatus.running, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setPeers(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setFusing(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setComplete(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null),
|
||||
shouldNotify: true);
|
||||
break;
|
||||
case fusion.FusionStatus.waiting:
|
||||
_uiState?.setConnecting(
|
||||
CashFusionState(status: CashFusionStatus.success, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setOutputs(
|
||||
CashFusionState(status: CashFusionStatus.success, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setPeers(
|
||||
CashFusionState(status: CashFusionStatus.running, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setFusing(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setComplete(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null),
|
||||
shouldNotify: true);
|
||||
break;
|
||||
case fusion.FusionStatus.running:
|
||||
_uiState?.setConnecting(
|
||||
CashFusionState(status: CashFusionStatus.success, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setOutputs(
|
||||
CashFusionState(status: CashFusionStatus.success, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setPeers(
|
||||
CashFusionState(status: CashFusionStatus.success, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setFusing(
|
||||
CashFusionState(status: CashFusionStatus.running, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setComplete(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: null),
|
||||
shouldNotify: true);
|
||||
break;
|
||||
case fusion.FusionStatus.complete:
|
||||
_uiState?.setConnecting(
|
||||
CashFusionState(status: CashFusionStatus.success, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setOutputs(
|
||||
CashFusionState(status: CashFusionStatus.success, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setPeers(
|
||||
CashFusionState(status: CashFusionStatus.success, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setFusing(
|
||||
CashFusionState(status: CashFusionStatus.success, info: null),
|
||||
shouldNotify: false);
|
||||
_uiState?.setComplete(
|
||||
CashFusionState(status: CashFusionStatus.success, info: null),
|
||||
shouldNotify: true);
|
||||
break;
|
||||
case fusion.FusionStatus.failed:
|
||||
failCurrentUiState(info);
|
||||
break;
|
||||
case fusion.FusionStatus.exception:
|
||||
failCurrentUiState(info);
|
||||
break;
|
||||
case fusion.FusionStatus.reset:
|
||||
_uiState?.setConnecting(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: info),
|
||||
shouldNotify: false);
|
||||
_uiState?.setOutputs(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: info),
|
||||
shouldNotify: false);
|
||||
_uiState?.setPeers(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: info),
|
||||
shouldNotify: false);
|
||||
_uiState?.setFusing(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: info),
|
||||
shouldNotify: false);
|
||||
_uiState?.setComplete(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: info),
|
||||
shouldNotify: false);
|
||||
|
||||
_uiState?.setFusionState(
|
||||
CashFusionState(status: CashFusionStatus.waiting, info: info),
|
||||
shouldNotify: false);
|
||||
|
||||
_uiState?.setFailed(false, shouldNotify: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void failCurrentUiState(String? info) {
|
||||
// Check each _uiState value to see if it is running. If so, set it to failed.
|
||||
if (_uiState?.connecting.status == CashFusionStatus.running) {
|
||||
_uiState?.setConnecting(
|
||||
CashFusionState(status: CashFusionStatus.failed, info: info),
|
||||
shouldNotify: true);
|
||||
return;
|
||||
}
|
||||
if (_uiState?.outputs.status == CashFusionStatus.running) {
|
||||
_uiState?.setOutputs(
|
||||
CashFusionState(status: CashFusionStatus.failed, info: info),
|
||||
shouldNotify: true);
|
||||
return;
|
||||
}
|
||||
if (_uiState?.peers.status == CashFusionStatus.running) {
|
||||
_uiState?.setPeers(
|
||||
CashFusionState(status: CashFusionStatus.failed, info: info),
|
||||
shouldNotify: true);
|
||||
return;
|
||||
}
|
||||
if (_uiState?.fusing.status == CashFusionStatus.running) {
|
||||
_uiState?.setFusing(
|
||||
CashFusionState(status: CashFusionStatus.failed, info: info),
|
||||
shouldNotify: true);
|
||||
return;
|
||||
}
|
||||
if (_uiState?.complete.status == CashFusionStatus.running) {
|
||||
_uiState?.setComplete(
|
||||
CashFusionState(status: CashFusionStatus.failed, info: info),
|
||||
shouldNotify: true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of all transactions in the wallet for the given address.
|
||||
Future<List<Map<String, dynamic>>> _getTransactionsByAddress(
|
||||
String address,
|
||||
) async {
|
||||
final txidList =
|
||||
await _db.getTransactions(_walletId).txidProperty().findAll();
|
||||
|
||||
final futures = txidList.map(
|
||||
(e) => _getWalletCachedElectrumX().getTransaction(
|
||||
txHash: e,
|
||||
coin: _coin,
|
||||
),
|
||||
);
|
||||
|
||||
return await Future.wait(futures);
|
||||
}
|
||||
|
||||
Future<Uint8List> _getPrivateKeyForPubKey(List<int> pubKey) async {
|
||||
// can't directly query for equal lists in isar so we need to fetch
|
||||
// all addresses then search in dart
|
||||
try {
|
||||
final derivationPath = (await _db
|
||||
.getAddresses(_walletId)
|
||||
.filter()
|
||||
.typeEqualTo(AddressType.p2pkh)
|
||||
.and()
|
||||
.derivationPathIsNotNull()
|
||||
.and()
|
||||
.group((q) => q
|
||||
.subTypeEqualTo(AddressSubType.receiving)
|
||||
.or()
|
||||
.subTypeEqualTo(AddressSubType.change))
|
||||
.findAll())
|
||||
.firstWhere((e) => e.publicKey.toString() == pubKey.toString())
|
||||
.derivationPath!
|
||||
.value;
|
||||
|
||||
final node = await Bip32Utils.getBip32Node(
|
||||
(await _mnemonic)!,
|
||||
(await _mnemonicPassphrase)!,
|
||||
_network,
|
||||
derivationPath,
|
||||
);
|
||||
|
||||
return node.privateKey!;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Fatal);
|
||||
throw Exception("Derivation path for pubkey=$pubKey could not be found");
|
||||
}
|
||||
}
|
||||
|
||||
/// Reserve an address for fusion.
|
||||
Future<List<Address>> _reserveAddresses(Iterable<Address> addresses) async {
|
||||
if (addresses.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final updatedAddresses = addresses
|
||||
.map((e) => e.copyWith(otherData: kReservedFusionAddress))
|
||||
.toList();
|
||||
|
||||
await _db.isar.writeTxn(() async {
|
||||
for (final newAddress in updatedAddresses) {
|
||||
final oldAddress = await _db.getAddress(
|
||||
newAddress.walletId,
|
||||
newAddress.value,
|
||||
);
|
||||
|
||||
if (oldAddress != null) {
|
||||
newAddress.id = oldAddress.id;
|
||||
await _db.isar.addresses.delete(oldAddress.id);
|
||||
}
|
||||
|
||||
await _db.isar.addresses.put(newAddress);
|
||||
}
|
||||
});
|
||||
|
||||
return updatedAddresses;
|
||||
}
|
||||
|
||||
/// un reserve a fusion reserved address.
|
||||
/// If [address] is not reserved nothing happens
|
||||
Future<Address> _unReserveAddress(Address address) async {
|
||||
if (address.otherData != kReservedFusionAddress) {
|
||||
return address;
|
||||
}
|
||||
|
||||
final updated = address.copyWith(otherData: null);
|
||||
|
||||
// Make sure the address is updated in the database.
|
||||
await _db.updateAddress(address, updated);
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
/// Returns a list of unused reserved change addresses.
|
||||
///
|
||||
/// If there are not enough unused reserved change addresses, new ones are created.
|
||||
Future<List<fusion.Address>> _getUnusedReservedChangeAddresses(
|
||||
int numberOfAddresses,
|
||||
) async {
|
||||
final unusedChangeAddresses = await _getNextUnusedChangeAddresses(
|
||||
numberOfAddresses: numberOfAddresses,
|
||||
);
|
||||
|
||||
// Initialize a list of unused reserved change addresses.
|
||||
final List<Address> unusedReservedAddresses = unusedChangeAddresses
|
||||
.where((e) => e.otherData == kReservedFusionAddress)
|
||||
.toList();
|
||||
|
||||
unusedReservedAddresses.addAll(await _reserveAddresses(
|
||||
unusedChangeAddresses.where((e) => e.otherData == null)));
|
||||
|
||||
// Return the list of unused reserved change addresses.
|
||||
return unusedReservedAddresses
|
||||
.map(
|
||||
(e) => fusion.Address(
|
||||
address: e.value,
|
||||
publicKey: e.publicKey,
|
||||
fusionReserved: true,
|
||||
derivationPath: fusion.DerivationPath(
|
||||
e.derivationPath!.value,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
int _torStartCount = 0;
|
||||
|
||||
/// Returns the current Tor proxy address.
|
||||
Future<({InternetAddress host, int port})> _getSocksProxyAddress() async {
|
||||
if (_torStartCount > 5) {
|
||||
// something is quite broken so stop trying to recursively fetch
|
||||
// start up tor and fetch proxy info
|
||||
throw Exception(
|
||||
"Fusion interface attempted to start tor $_torStartCount times and failed!",
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
final info = _torService.getProxyInfo();
|
||||
|
||||
// reset counter before return info;
|
||||
_torStartCount = 0;
|
||||
|
||||
return info;
|
||||
} catch (_) {
|
||||
// tor is probably not running so lets fix that
|
||||
final torDir = await StackFileSystem.applicationTorDirectory();
|
||||
_torService.init(torDataDirPath: torDir.path);
|
||||
|
||||
// increment start attempt count
|
||||
_torStartCount++;
|
||||
|
||||
await _torService.start();
|
||||
|
||||
// try again to fetch proxy info
|
||||
return await _getSocksProxyAddress();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _checkUtxoExists(
|
||||
String address,
|
||||
String prevTxid,
|
||||
int prevIndex,
|
||||
) async {
|
||||
final scriptHash = _convertToScriptHash(address, _network);
|
||||
|
||||
final utxos = await _getWalletCachedElectrumX()
|
||||
.electrumXClient
|
||||
.getUTXOs(scripthash: scriptHash);
|
||||
|
||||
for (final utxo in utxos) {
|
||||
if (utxo["tx_hash"] == prevTxid && utxo["tx_pos"] == prevIndex) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initial attempt for CashFusion integration goes here.
|
||||
|
||||
/// Fuse the wallet's UTXOs.
|
||||
///
|
||||
/// This function is called when the user taps the "Fuse" button in the UI.
|
||||
Future<void> fuse({
|
||||
required FusionInfo fusionInfo,
|
||||
}) async {
|
||||
// Initial attempt for CashFusion integration goes here.
|
||||
|
||||
try {
|
||||
_updateStatus(status: fusion.FusionStatus.reset);
|
||||
_updateStatus(
|
||||
status: fusion.FusionStatus.connecting,
|
||||
info: "Connecting to the CashFusion server.",
|
||||
);
|
||||
|
||||
// Use server host and port which ultimately come from text fields.
|
||||
fusion.FusionParams serverParams = fusion.FusionParams(
|
||||
serverHost: fusionInfo.host,
|
||||
serverPort: fusionInfo.port,
|
||||
serverSsl: fusionInfo.ssl,
|
||||
genesisHashHex:
|
||||
_coin.isTestNet ? GENESIS_HASH_TESTNET : GENESIS_HASH_MAINNET,
|
||||
enableDebugPrint: kDebugMode,
|
||||
torForOvert: _prefs.useTor,
|
||||
mode: fusion.FusionMode.normal,
|
||||
);
|
||||
|
||||
// Instantiate a Fusion object with custom parameters.
|
||||
_mainFusionObject = fusion.Fusion(serverParams);
|
||||
|
||||
// Pass wallet functions to the Fusion object
|
||||
await _mainFusionObject!.initFusion(
|
||||
getTransactionsByAddress: _getTransactionsByAddress,
|
||||
getUnusedReservedChangeAddresses: _getUnusedReservedChangeAddresses,
|
||||
getSocksProxyAddress: _getSocksProxyAddress,
|
||||
getChainHeight: _getChainHeight,
|
||||
updateStatusCallback: _updateStatus,
|
||||
checkUtxoExists: _checkUtxoExists,
|
||||
getTransactionJson: (String txid) async =>
|
||||
await _getWalletCachedElectrumX().getTransaction(
|
||||
coin: _coin,
|
||||
txHash: txid,
|
||||
),
|
||||
getPrivateKeyForPubKey: _getPrivateKeyForPubKey,
|
||||
broadcastTransaction: (String txHex) => _getWalletCachedElectrumX()
|
||||
.electrumXClient
|
||||
.broadcastTransaction(rawTx: txHex),
|
||||
unReserveAddresses: (List<fusion.Address> addresses) async {
|
||||
final List<Future<void>> futures = [];
|
||||
for (final addr in addresses) {
|
||||
futures.add(
|
||||
_db.getAddress(_walletId, addr.address).then(
|
||||
(address) async {
|
||||
if (address == null) {
|
||||
// matching address not found in db so cannot mark as unreserved
|
||||
// just ignore I guess. Should never actually happen in practice.
|
||||
// Might be useful check in debugging cases?
|
||||
return;
|
||||
} else {
|
||||
await _unReserveAddress(address);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
await Future.wait(futures);
|
||||
},
|
||||
);
|
||||
|
||||
// Reset internal and UI counts and flag.
|
||||
_completedFuseCount = 0;
|
||||
_uiState?.fusionRoundsCompleted = 0;
|
||||
_failedFuseCount = 0;
|
||||
_uiState?.fusionRoundsFailed = 0;
|
||||
_stopRequested = false;
|
||||
|
||||
bool shouldFuzeAgain() {
|
||||
if (fusionInfo.rounds <= 0) {
|
||||
// ignore count if continuous
|
||||
return !_stopRequested;
|
||||
} else {
|
||||
// not continuous
|
||||
// check to make sure we aren't doing more fusions than requested
|
||||
return !_stopRequested && _completedFuseCount < fusionInfo.rounds;
|
||||
}
|
||||
}
|
||||
|
||||
while (shouldFuzeAgain()) {
|
||||
if (_completedFuseCount > 0 || _failedFuseCount > 0) {
|
||||
_updateStatus(status: fusion.FusionStatus.reset);
|
||||
_updateStatus(
|
||||
status: fusion.FusionStatus.connecting,
|
||||
info: "Connecting to the CashFusion server.",
|
||||
);
|
||||
}
|
||||
|
||||
// refresh wallet utxos
|
||||
await _updateWalletUTXOS();
|
||||
|
||||
// Add unfrozen stack UTXOs.
|
||||
final List<UTXO> walletUtxos = await _db
|
||||
.getUTXOs(_walletId)
|
||||
.filter()
|
||||
.isBlockedEqualTo(false)
|
||||
.and()
|
||||
.addressIsNotNull()
|
||||
.findAll();
|
||||
|
||||
final List<fusion.UtxoDTO> coinList = [];
|
||||
// Loop through UTXOs, checking and adding valid ones.
|
||||
for (final utxo in walletUtxos) {
|
||||
final String addressString = utxo.address!;
|
||||
final List<String> possibleAddresses = [addressString];
|
||||
|
||||
if (bitbox.Address.detectFormat(addressString) ==
|
||||
bitbox.Address.formatCashAddr) {
|
||||
possibleAddresses
|
||||
.add(bitbox.Address.toLegacyAddress(addressString));
|
||||
} else {
|
||||
possibleAddresses.add(bitbox.Address.toCashAddress(addressString));
|
||||
}
|
||||
|
||||
// Fetch address to get pubkey
|
||||
final addr = await _db
|
||||
.getAddresses(_walletId)
|
||||
.filter()
|
||||
.anyOf<String,
|
||||
QueryBuilder<Address, Address, QAfterFilterCondition>>(
|
||||
possibleAddresses, (q, e) => q.valueEqualTo(e))
|
||||
.and()
|
||||
.group((q) => q
|
||||
.subTypeEqualTo(AddressSubType.change)
|
||||
.or()
|
||||
.subTypeEqualTo(AddressSubType.receiving))
|
||||
.and()
|
||||
.typeEqualTo(AddressType.p2pkh)
|
||||
.findFirst();
|
||||
|
||||
// depending on the address type in the query above this can be null
|
||||
if (addr == null) {
|
||||
// A utxo object should always have a non null address.
|
||||
// If non found then just ignore the UTXO (aka don't fuse it)
|
||||
Logging.instance.log(
|
||||
"Ignoring utxo=$utxo for address=\"$addressString\" while selecting UTXOs for Fusion",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
final dto = fusion.UtxoDTO(
|
||||
txid: utxo.txid,
|
||||
vout: utxo.vout,
|
||||
value: utxo.value,
|
||||
address: utxo.address!,
|
||||
pubKey: addr.publicKey,
|
||||
);
|
||||
|
||||
// Add UTXO to coinList.
|
||||
coinList.add(dto);
|
||||
}
|
||||
|
||||
// Fuse UTXOs.
|
||||
try {
|
||||
await _mainFusionObject!.fuse(
|
||||
inputsFromWallet: coinList,
|
||||
network: _coin.isTestNet
|
||||
? fusion.Utilities.testNet
|
||||
: fusion.Utilities.mainNet,
|
||||
);
|
||||
|
||||
// Increment the number of successfully completed fusion rounds.
|
||||
_completedFuseCount++;
|
||||
|
||||
// Do the same for the UI state. This also resets the failed count (for
|
||||
// the UI state only).
|
||||
_uiState?.incrementFusionRoundsCompleted();
|
||||
|
||||
// Also reset the failed count here.
|
||||
_failedFuseCount = 0;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
// just continue on attempt failure
|
||||
|
||||
// Increment the number of failed fusion rounds.
|
||||
_failedFuseCount++;
|
||||
|
||||
// Do the same for the UI state.
|
||||
_uiState?.incrementFusionRoundsFailed();
|
||||
|
||||
// If we fail too many times in a row, stop trying.
|
||||
if (_failedFuseCount >= maxFailedFuseCount) {
|
||||
_updateStatus(
|
||||
status: fusion.FusionStatus.failed,
|
||||
info: "Failed $maxFailedFuseCount times in a row, stopping.");
|
||||
_stopRequested = true;
|
||||
_uiState?.setFailed(true, shouldNotify: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
|
||||
// Stop the fusion process and update the UI state.
|
||||
await _mainFusionObject?.stop();
|
||||
_mainFusionObject = null;
|
||||
_uiState?.setRunning(false, shouldNotify: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop the fusion process.
|
||||
///
|
||||
/// This function is called when the user taps the "Cancel" button in the UI
|
||||
/// or closes the fusion progress dialog.
|
||||
Future<void> stop() async {
|
||||
_stopRequested = true;
|
||||
await _mainFusionObject?.stop();
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ class TorService {
|
|||
Tor? _tor;
|
||||
String? _torDataDirPath;
|
||||
|
||||
/// Current status. Same as that fired on the event bus
|
||||
/// Current status. Same as that fired on the event bus.
|
||||
TorConnectionStatus get status => _status;
|
||||
TorConnectionStatus _status = TorConnectionStatus.disconnected;
|
||||
|
||||
|
@ -26,7 +26,7 @@ class TorService {
|
|||
|
||||
/// Getter for the proxyInfo.
|
||||
///
|
||||
/// Returns null if disabled on the stack wallet level.
|
||||
/// Throws if Tor is not connected.
|
||||
({
|
||||
InternetAddress host,
|
||||
int port,
|
||||
|
@ -55,7 +55,8 @@ class TorService {
|
|||
///
|
||||
/// This will start the Tor service and establish a Tor circuit.
|
||||
///
|
||||
/// Throws an exception if the Tor service fails to start.
|
||||
/// Throws an exception if the Tor library was not inited or if the Tor
|
||||
/// service fails to start.
|
||||
///
|
||||
/// Returns a Future that completes when the Tor service has started.
|
||||
Future<void> start() async {
|
||||
|
@ -79,6 +80,9 @@ class TorService {
|
|||
status: TorConnectionStatus.connected,
|
||||
message: "TorService.start call success",
|
||||
);
|
||||
|
||||
// Complete the future.
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"TorService.start failed: $e\n$s",
|
||||
|
@ -110,6 +114,8 @@ class TorService {
|
|||
status: TorConnectionStatus.disconnected,
|
||||
message: "TorService.disable call success",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void _updateStatusAndFireEvent({
|
||||
|
|
|
@ -93,6 +93,9 @@ class _SVG {
|
|||
|
||||
final coinControl = const _COIN_CONTROL();
|
||||
|
||||
String get peers => "assets/svg/peers.svg";
|
||||
String get fusing => "assets/svg/fusing.svg";
|
||||
String get upFromLine => "assets/svg/up-from-line.svg";
|
||||
String get connectedButton => "assets/svg/connected-button.svg";
|
||||
String get connectingButton => "assets/svg/connecting-button.svg";
|
||||
String get disconnectedButton => "assets/svg/disconnected-button.svg";
|
||||
|
@ -195,6 +198,7 @@ class _SVG {
|
|||
String get arrowDown => "assets/svg/arrow-down.svg";
|
||||
String get robotHead => "assets/svg/robot-head.svg";
|
||||
String get whirlPool => "assets/svg/whirlpool.svg";
|
||||
String get cashFusion => "assets/svg/cashfusion-icon.svg";
|
||||
String get fingerprint => "assets/svg/fingerprint.svg";
|
||||
String get faceId => "assets/svg/faceid.svg";
|
||||
String get tokens => "assets/svg/tokens.svg";
|
||||
|
@ -202,6 +206,7 @@ class _SVG {
|
|||
String get creditCard => "assets/svg/cc.svg";
|
||||
String get file => "assets/svg/file.svg";
|
||||
String get fileUpload => "assets/svg/file-upload.svg";
|
||||
String get txCashFusion => "assets/svg/tx-cashfusion.svg";
|
||||
|
||||
String get ellipse1 => "assets/svg/Ellipse-43.svg";
|
||||
String get ellipse2 => "assets/svg/Ellipse-42.svg";
|
||||
|
|
|
@ -50,16 +50,27 @@ abstract class Format {
|
|||
// }
|
||||
|
||||
// format date string from unix timestamp
|
||||
static String extractDateFrom(int timestamp, {bool localized = true}) {
|
||||
static String extractDateFrom(
|
||||
int timestamp, {
|
||||
bool localized = true,
|
||||
bool noTime = false,
|
||||
}) {
|
||||
var date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||
|
||||
if (!localized) {
|
||||
date = date.toUtc();
|
||||
}
|
||||
|
||||
final dayAndYear =
|
||||
"${date.day} ${Constants.monthMapShort[date.month]} ${date.year}";
|
||||
|
||||
if (noTime) {
|
||||
return dayAndYear;
|
||||
}
|
||||
|
||||
final minutes =
|
||||
date.minute < 10 ? "0${date.minute}" : date.minute.toString();
|
||||
return "${date.day} ${Constants.monthMapShort[date.month]} ${date.year}, ${date.hour}:$minutes";
|
||||
return "$dayAndYear, ${date.hour}:$minutes";
|
||||
}
|
||||
|
||||
// static String localizedStringAsFixed({
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:flutter/cupertino.dart';
|
|||
import 'package:stackwallet/db/hive/db.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/tor_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount_unit.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
|
||||
|
@ -64,6 +65,7 @@ class Prefs extends ChangeNotifier {
|
|||
await _setAmountUnits();
|
||||
await _setMaxDecimals();
|
||||
_useTor = await _getUseTor();
|
||||
_fusionServerInfo = await _getFusionServerInfo();
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
@ -931,4 +933,35 @@ class Prefs extends ChangeNotifier {
|
|||
) as bool? ??
|
||||
false;
|
||||
}
|
||||
|
||||
// fusion server info
|
||||
|
||||
FusionInfo _fusionServerInfo = FusionInfo.DEFAULTS;
|
||||
|
||||
FusionInfo get fusionServerInfo => _fusionServerInfo;
|
||||
|
||||
set fusionServerInfo(FusionInfo fusionServerInfo) {
|
||||
if (this.fusionServerInfo != fusionServerInfo) {
|
||||
DB.instance.put<dynamic>(
|
||||
boxName: DB.boxNamePrefs,
|
||||
key: "fusionServerInfo",
|
||||
value: fusionServerInfo.toJsonString(),
|
||||
);
|
||||
_fusionServerInfo = fusionServerInfo;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<FusionInfo> _getFusionServerInfo() async {
|
||||
final saved = await DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNamePrefs,
|
||||
key: "fusionServerInfo",
|
||||
) as String?;
|
||||
|
||||
try {
|
||||
return FusionInfo.fromJsonString(saved!);
|
||||
} catch (_) {
|
||||
return FusionInfo.DEFAULTS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,14 +73,21 @@ abstract class Util {
|
|||
return MaterialColor(color.value, swatch);
|
||||
}
|
||||
|
||||
static void printJson(dynamic json) {
|
||||
static void printJson(dynamic json, [String? debugTitle]) {
|
||||
final String result;
|
||||
if (json is Map || json is List) {
|
||||
final spaces = ' ' * 4;
|
||||
final encoder = JsonEncoder.withIndent(spaces);
|
||||
final pretty = encoder.convert(json);
|
||||
log(pretty);
|
||||
result = pretty;
|
||||
} else {
|
||||
log(dynamic.toString());
|
||||
result = dynamic.toString();
|
||||
}
|
||||
|
||||
if (debugTitle != null) {
|
||||
log("$debugTitle\n$result");
|
||||
} else {
|
||||
log(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart';
|
|||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart';
|
||||
import 'package:stackwallet/widgets/expandable.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
@ -141,7 +142,7 @@ class _MasterWalletCardState extends ConsumerState<MasterWalletCard> {
|
|||
child: SimpleWalletCard(
|
||||
walletId: widget.walletId,
|
||||
contractAddress: e,
|
||||
popPrevious: true,
|
||||
popPrevious: Util.isDesktop,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -157,7 +157,7 @@ class SimpleWalletCard extends ConsumerWidget {
|
|||
} else {
|
||||
await nav.pushNamed(
|
||||
TokenView.routeName,
|
||||
arguments: walletId,
|
||||
arguments: (walletId: walletId, popPrevious: !Util.isDesktop),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
|
||||
class FusionNavIcon extends StatelessWidget {
|
||||
const FusionNavIcon({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SvgPicture.asset(
|
||||
Assets.svg.cashFusion,
|
||||
height: 20,
|
||||
width: 20,
|
||||
color: Theme.of(context).extension<StackColors>()!.bottomNavIconIcon,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
coinlib_flutter
|
||||
tor_ffi_plugin
|
||||
)
|
||||
|
||||
|
|
52
pubspec.lock
52
pubspec.lock
|
@ -98,6 +98,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
bip340:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: bip340
|
||||
sha256: "2a92f6ed68959f75d67c9a304c17928b9c9449587d4f75ee68f34152f7f69e87"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
bip39:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -255,13 +263,21 @@ packages:
|
|||
source: hosted
|
||||
version: "4.6.0"
|
||||
coinlib:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: coinlib
|
||||
sha256: c8018027801ddcb093837ad8f27e9ac014434b84860ecd3114ca28980614ec4a
|
||||
sha256: "410993f49aef30e48b76bbad8a33fef33f6b90e103b15b6be0277683ffe15399"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0-rc.3"
|
||||
version: "1.0.0"
|
||||
coinlib_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: coinlib_flutter
|
||||
sha256: d0a6a3694fc8c851f6b912bb1c552e33998e3f453efab3d62f75c0b1773d1ab9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -287,7 +303,7 @@ packages:
|
|||
source: hosted
|
||||
version: "1.2.4"
|
||||
convert:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: convert
|
||||
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
||||
|
@ -605,6 +621,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0+1"
|
||||
flutter_hooks:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_hooks
|
||||
sha256: "7c8db779c2d1010aa7f9ea3fbefe8f86524fcb87b69e8b0af31e1a4b55422dec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.3"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -778,6 +802,15 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fusiondart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: df8f7c627cfc77eaa3e364c0d166f3d04169ae05
|
||||
resolved-ref: df8f7c627cfc77eaa3e364c0d166f3d04169ae05
|
||||
url: "https://github.com/cypherstack/fusiondart.git"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1459,6 +1492,15 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3+dev.3"
|
||||
socks_socket:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: master
|
||||
resolved-ref: ac6d721fe655208a6d488a088a35bab0ddc25702
|
||||
url: "https://github.com/cypherstack/socks_socket.git"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1976,5 +2018,5 @@ packages:
|
|||
source: hosted
|
||||
version: "1.0.0"
|
||||
sdks:
|
||||
dart: ">=3.0.2 <4.0.0"
|
||||
dart: ">=3.0.6 <4.0.0"
|
||||
flutter: ">=3.10.3"
|
||||
|
|
143
pubspec.yaml
143
pubspec.yaml
|
@ -11,7 +11,7 @@ description: Stack Wallet
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.8.0+191
|
||||
version: 1.8.1+192
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.2 <4.0.0"
|
||||
|
@ -62,6 +62,11 @@ dependencies:
|
|||
url: https://github.com/cypherstack/tor.git
|
||||
ref: 0a6888282f4e98401051a396e9d2293bd55ac2c2
|
||||
|
||||
fusiondart:
|
||||
git:
|
||||
url: https://github.com/cypherstack/fusiondart.git
|
||||
ref: df8f7c627cfc77eaa3e364c0d166f3d04169ae05
|
||||
|
||||
# Utility plugins
|
||||
http: ^0.13.0
|
||||
local_auth: ^1.1.10
|
||||
|
@ -144,9 +149,16 @@ dependencies:
|
|||
nanodart: ^2.0.0
|
||||
basic_utils: ^5.5.4
|
||||
stellar_flutter_sdk: ^1.5.3
|
||||
socks_socket:
|
||||
git:
|
||||
url: https://github.com/cypherstack/socks_socket.git
|
||||
ref: master
|
||||
bip340: ^0.2.0
|
||||
tezart: ^2.0.5
|
||||
socks5_proxy: ^1.0.3+dev.3
|
||||
coinlib: ^1.0.0-rc.3
|
||||
coinlib_flutter: ^1.0.0
|
||||
convert: ^3.1.1
|
||||
flutter_hooks: ^0.20.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -234,132 +246,7 @@ flutter:
|
|||
- assets/images/splash.png
|
||||
- assets/icon/icon.png
|
||||
- google_fonts/
|
||||
- assets/svg/circle-check.svg
|
||||
- assets/svg/clipboard.svg
|
||||
- assets/images/glasses.png
|
||||
- assets/images/glasses-hidden.png
|
||||
- assets/svg/unclaimed.svg
|
||||
- assets/svg/plus.svg
|
||||
- assets/svg/gear.svg
|
||||
- assets/svg/bell.svg
|
||||
- assets/svg/arrow-left-fa.svg
|
||||
- assets/svg/copy-fa.svg
|
||||
- assets/svg/star.svg
|
||||
- assets/svg/x-circle.svg
|
||||
- assets/svg/check.svg
|
||||
- assets/svg/alert-circle.svg
|
||||
- assets/svg/alert-circle2.svg
|
||||
- assets/svg/arrow-down-left.svg
|
||||
- assets/svg/arrow-up-right.svg
|
||||
- assets/svg/bars.svg
|
||||
- assets/svg/filter.svg
|
||||
- assets/svg/pending.svg
|
||||
- assets/svg/signal-stream.svg
|
||||
- assets/svg/Ellipse-43.svg
|
||||
- assets/svg/Ellipse-42.svg
|
||||
- assets/svg/arrow-rotate.svg
|
||||
- assets/svg/arrow-rotate2.svg
|
||||
- assets/svg/qrcode1.svg
|
||||
- assets/svg/gear-3.svg
|
||||
- assets/svg/swap.svg
|
||||
- assets/svg/chevron-down.svg
|
||||
- assets/svg/chevron-up.svg
|
||||
- assets/svg/lock-keyhole.svg
|
||||
- assets/svg/lock-open.svg
|
||||
- assets/svg/folder-down.svg
|
||||
- assets/svg/network-wired.svg
|
||||
- assets/svg/network-wired-2.svg
|
||||
- assets/svg/address-book.svg
|
||||
- assets/svg/address-book2.svg
|
||||
- assets/svg/arrow-right.svg
|
||||
- assets/svg/delete.svg
|
||||
- assets/svg/dollar-sign.svg
|
||||
- assets/svg/sun-bright2.svg
|
||||
- assets/svg/language2.svg
|
||||
- assets/svg/pen-solid-fa.svg
|
||||
- assets/svg/magnifying-glass.svg
|
||||
- assets/svg/x.svg
|
||||
- assets/svg/x-fat.svg
|
||||
- assets/svg/user.svg
|
||||
- assets/svg/user-plus.svg
|
||||
- assets/svg/user-minus.svg
|
||||
- assets/svg/trash.svg
|
||||
- assets/svg/eye.svg
|
||||
- assets/svg/eye-slash.svg
|
||||
- assets/svg/folder.svg
|
||||
- assets/svg/calendar-days.svg
|
||||
- assets/svg/circle-question.svg
|
||||
- assets/svg/info-circle.svg
|
||||
- assets/svg/key.svg
|
||||
- assets/svg/node-alt.svg
|
||||
- assets/svg/signal-problem-alt.svg
|
||||
- assets/svg/signal-sync-alt.svg
|
||||
- assets/svg/wallet-settings.svg
|
||||
- assets/svg/ellipsis-vertical1.svg
|
||||
- assets/svg/dice-alt.svg
|
||||
- assets/svg/circle-arrow-up-right2.svg
|
||||
- assets/svg/loader.svg
|
||||
- assets/svg/add-backup.svg
|
||||
- assets/svg/auto-backup.svg
|
||||
- assets/svg/restore-backup.svg
|
||||
- assets/svg/sliders-solid.svg
|
||||
- assets/svg/message-question.svg
|
||||
- assets/svg/envelope.svg
|
||||
- assets/svg/share-2.svg
|
||||
- assets/svg/anonymize.svg
|
||||
- assets/svg/tx-icon-anonymize.svg
|
||||
- assets/svg/tx-icon-anonymize-pending.svg
|
||||
- assets/svg/tx-icon-anonymize-failed.svg
|
||||
- assets/svg/Polygon.svg
|
||||
- assets/svg/Button.svg
|
||||
- assets/svg/enabled-button.svg
|
||||
- assets/svg/lock-circle.svg
|
||||
- assets/svg/dollar-sign-circle.svg
|
||||
- assets/svg/language-circle.svg
|
||||
- assets/svg/rotate-circle.svg
|
||||
- assets/svg/sun-circle.svg
|
||||
- assets/svg/node-circle.svg
|
||||
- assets/svg/address-book-desktop.svg
|
||||
- assets/svg/about-desktop.svg
|
||||
- assets/svg/exchange-desktop.svg
|
||||
- assets/svg/wallet-desktop.svg
|
||||
- assets/svg/exit-desktop.svg
|
||||
- assets/svg/keys.svg
|
||||
- assets/svg/arrow-down.svg
|
||||
- assets/svg/plus-circle.svg
|
||||
- assets/svg/circle-plus-filled.svg
|
||||
- assets/svg/configuration.svg
|
||||
- assets/svg/tokens.svg
|
||||
- assets/svg/circle-plus.svg
|
||||
- assets/svg/robot-head.svg
|
||||
- assets/svg/whirlpool.svg
|
||||
- assets/svg/fingerprint.svg
|
||||
- assets/svg/faceid.svg
|
||||
- assets/svg/chevron-right.svg
|
||||
- assets/svg/minimize.svg
|
||||
- assets/svg/wallet-fa.svg
|
||||
- assets/svg/exchange-3.svg
|
||||
- assets/svg/message-question-1.svg
|
||||
- assets/svg/drd-icon.svg
|
||||
- assets/svg/box-auto.svg
|
||||
- assets/svg/framed-address-book.svg
|
||||
- assets/svg/framed-gear.svg
|
||||
- assets/svg/list-ul.svg
|
||||
- assets/svg/cc.svg
|
||||
- assets/svg/file.svg
|
||||
- assets/svg/file-upload.svg
|
||||
- assets/svg/trocador_rating_a.svg
|
||||
- assets/svg/trocador_rating_b.svg
|
||||
- assets/svg/trocador_rating_c.svg
|
||||
- assets/svg/trocador_rating_d.svg
|
||||
- assets/svg/send.svg
|
||||
- assets/svg/ordinal.svg
|
||||
- assets/svg/monkey.svg
|
||||
- assets/svg/tor.svg
|
||||
- assets/svg/tor-circle.svg
|
||||
- assets/svg/connected-button.svg
|
||||
- assets/svg/connecting-button.svg
|
||||
- assets/svg/disconnected-button.svg
|
||||
- assets/svg/
|
||||
|
||||
# coin control icons
|
||||
- assets/svg/coin_control/
|
||||
|
|
|
@ -3,17 +3,19 @@
|
|||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i4;
|
||||
import 'dart:ui' as _i10;
|
||||
import 'dart:async' as _i5;
|
||||
import 'dart:ui' as _i11;
|
||||
|
||||
import 'package:decimal/decimal.dart' as _i2;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i3;
|
||||
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i8;
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i7;
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9;
|
||||
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i6;
|
||||
import 'package:stackwallet/utilities/prefs.dart' as _i5;
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i4;
|
||||
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart'
|
||||
as _i3;
|
||||
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i9;
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i8;
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10;
|
||||
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i7;
|
||||
import 'package:stackwallet/utilities/prefs.dart' as _i6;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
|
@ -46,16 +48,26 @@ class _FakeDecimal_1 extends _i1.SmartFake implements _i2.Decimal {
|
|||
);
|
||||
}
|
||||
|
||||
class _FakeFusionInfo_2 extends _i1.SmartFake implements _i3.FusionInfo {
|
||||
_FakeFusionInfo_2(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
parent,
|
||||
parentInvocation,
|
||||
);
|
||||
}
|
||||
|
||||
/// A class which mocks [ElectrumX].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
||||
class MockElectrumX extends _i1.Mock implements _i4.ElectrumX {
|
||||
MockElectrumX() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
set failovers(List<_i3.ElectrumXNode>? _failovers) => super.noSuchMethod(
|
||||
set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#failovers,
|
||||
_failovers,
|
||||
|
@ -100,7 +112,7 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
_i4.Future<dynamic> request({
|
||||
_i5.Future<dynamic> request({
|
||||
required String? command,
|
||||
List<dynamic>? args = const [],
|
||||
String? requestID,
|
||||
|
@ -119,10 +131,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
#requestTimeout: requestTimeout,
|
||||
},
|
||||
),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
) as _i4.Future<dynamic>);
|
||||
returnValue: _i5.Future<dynamic>.value(),
|
||||
) as _i5.Future<dynamic>);
|
||||
@override
|
||||
_i4.Future<List<Map<String, dynamic>>> batchRequest({
|
||||
_i5.Future<List<Map<String, dynamic>>> batchRequest({
|
||||
required String? command,
|
||||
required Map<String, List<dynamic>>? args,
|
||||
Duration? requestTimeout = const Duration(seconds: 60),
|
||||
|
@ -139,11 +151,11 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
#retries: retries,
|
||||
},
|
||||
),
|
||||
returnValue: _i4.Future<List<Map<String, dynamic>>>.value(
|
||||
returnValue: _i5.Future<List<Map<String, dynamic>>>.value(
|
||||
<Map<String, dynamic>>[]),
|
||||
) as _i4.Future<List<Map<String, dynamic>>>);
|
||||
) as _i5.Future<List<Map<String, dynamic>>>);
|
||||
@override
|
||||
_i4.Future<bool> ping({
|
||||
_i5.Future<bool> ping({
|
||||
String? requestID,
|
||||
int? retryCount = 1,
|
||||
}) =>
|
||||
|
@ -156,10 +168,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
#retryCount: retryCount,
|
||||
},
|
||||
),
|
||||
returnValue: _i4.Future<bool>.value(false),
|
||||
) as _i4.Future<bool>);
|
||||
returnValue: _i5.Future<bool>.value(false),
|
||||
) as _i5.Future<bool>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getBlockHeadTip({String? requestID}) =>
|
||||
_i5.Future<Map<String, dynamic>> getBlockHeadTip({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getBlockHeadTip,
|
||||
|
@ -167,10 +179,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
{#requestID: requestID},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getServerFeatures({String? requestID}) =>
|
||||
_i5.Future<Map<String, dynamic>> getServerFeatures({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getServerFeatures,
|
||||
|
@ -178,10 +190,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
{#requestID: requestID},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<String> broadcastTransaction({
|
||||
_i5.Future<String> broadcastTransaction({
|
||||
required String? rawTx,
|
||||
String? requestID,
|
||||
}) =>
|
||||
|
@ -194,10 +206,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
#requestID: requestID,
|
||||
},
|
||||
),
|
||||
returnValue: _i4.Future<String>.value(''),
|
||||
) as _i4.Future<String>);
|
||||
returnValue: _i5.Future<String>.value(''),
|
||||
) as _i5.Future<String>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getBalance({
|
||||
_i5.Future<Map<String, dynamic>> getBalance({
|
||||
required String? scripthash,
|
||||
String? requestID,
|
||||
}) =>
|
||||
|
@ -211,10 +223,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<List<Map<String, dynamic>>> getHistory({
|
||||
_i5.Future<List<Map<String, dynamic>>> getHistory({
|
||||
required String? scripthash,
|
||||
String? requestID,
|
||||
}) =>
|
||||
|
@ -227,11 +239,11 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
#requestID: requestID,
|
||||
},
|
||||
),
|
||||
returnValue: _i4.Future<List<Map<String, dynamic>>>.value(
|
||||
returnValue: _i5.Future<List<Map<String, dynamic>>>.value(
|
||||
<Map<String, dynamic>>[]),
|
||||
) as _i4.Future<List<Map<String, dynamic>>>);
|
||||
) as _i5.Future<List<Map<String, dynamic>>>);
|
||||
@override
|
||||
_i4.Future<Map<String, List<Map<String, dynamic>>>> getBatchHistory(
|
||||
_i5.Future<Map<String, List<Map<String, dynamic>>>> getBatchHistory(
|
||||
{required Map<String, List<dynamic>>? args}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -239,11 +251,11 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
[],
|
||||
{#args: args},
|
||||
),
|
||||
returnValue: _i4.Future<Map<String, List<Map<String, dynamic>>>>.value(
|
||||
returnValue: _i5.Future<Map<String, List<Map<String, dynamic>>>>.value(
|
||||
<String, List<Map<String, dynamic>>>{}),
|
||||
) as _i4.Future<Map<String, List<Map<String, dynamic>>>>);
|
||||
) as _i5.Future<Map<String, List<Map<String, dynamic>>>>);
|
||||
@override
|
||||
_i4.Future<List<Map<String, dynamic>>> getUTXOs({
|
||||
_i5.Future<List<Map<String, dynamic>>> getUTXOs({
|
||||
required String? scripthash,
|
||||
String? requestID,
|
||||
}) =>
|
||||
|
@ -256,11 +268,11 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
#requestID: requestID,
|
||||
},
|
||||
),
|
||||
returnValue: _i4.Future<List<Map<String, dynamic>>>.value(
|
||||
returnValue: _i5.Future<List<Map<String, dynamic>>>.value(
|
||||
<Map<String, dynamic>>[]),
|
||||
) as _i4.Future<List<Map<String, dynamic>>>);
|
||||
) as _i5.Future<List<Map<String, dynamic>>>);
|
||||
@override
|
||||
_i4.Future<Map<String, List<Map<String, dynamic>>>> getBatchUTXOs(
|
||||
_i5.Future<Map<String, List<Map<String, dynamic>>>> getBatchUTXOs(
|
||||
{required Map<String, List<dynamic>>? args}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -268,11 +280,11 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
[],
|
||||
{#args: args},
|
||||
),
|
||||
returnValue: _i4.Future<Map<String, List<Map<String, dynamic>>>>.value(
|
||||
returnValue: _i5.Future<Map<String, List<Map<String, dynamic>>>>.value(
|
||||
<String, List<Map<String, dynamic>>>{}),
|
||||
) as _i4.Future<Map<String, List<Map<String, dynamic>>>>);
|
||||
) as _i5.Future<Map<String, List<Map<String, dynamic>>>>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getTransaction({
|
||||
_i5.Future<Map<String, dynamic>> getTransaction({
|
||||
required String? txHash,
|
||||
bool? verbose = true,
|
||||
String? requestID,
|
||||
|
@ -288,10 +300,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getAnonymitySet({
|
||||
_i5.Future<Map<String, dynamic>> getAnonymitySet({
|
||||
String? groupId = r'1',
|
||||
String? blockhash = r'',
|
||||
String? requestID,
|
||||
|
@ -307,10 +319,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<dynamic> getMintData({
|
||||
_i5.Future<dynamic> getMintData({
|
||||
dynamic mints,
|
||||
String? requestID,
|
||||
}) =>
|
||||
|
@ -323,10 +335,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
#requestID: requestID,
|
||||
},
|
||||
),
|
||||
returnValue: _i4.Future<dynamic>.value(),
|
||||
) as _i4.Future<dynamic>);
|
||||
returnValue: _i5.Future<dynamic>.value(),
|
||||
) as _i5.Future<dynamic>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getUsedCoinSerials({
|
||||
_i5.Future<Map<String, dynamic>> getUsedCoinSerials({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
|
@ -340,19 +352,19 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<int> getLatestCoinId({String? requestID}) => (super.noSuchMethod(
|
||||
_i5.Future<int> getLatestCoinId({String? requestID}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getLatestCoinId,
|
||||
[],
|
||||
{#requestID: requestID},
|
||||
),
|
||||
returnValue: _i4.Future<int>.value(0),
|
||||
) as _i4.Future<int>);
|
||||
returnValue: _i5.Future<int>.value(0),
|
||||
) as _i5.Future<int>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
|
||||
_i5.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getFeeRate,
|
||||
|
@ -360,10 +372,10 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
{#requestID: requestID},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<_i2.Decimal> estimateFee({
|
||||
_i5.Future<_i2.Decimal> estimateFee({
|
||||
String? requestID,
|
||||
required int? blocks,
|
||||
}) =>
|
||||
|
@ -376,7 +388,7 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
#blocks: blocks,
|
||||
},
|
||||
),
|
||||
returnValue: _i4.Future<_i2.Decimal>.value(_FakeDecimal_1(
|
||||
returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_1(
|
||||
this,
|
||||
Invocation.method(
|
||||
#estimateFee,
|
||||
|
@ -387,15 +399,15 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
},
|
||||
),
|
||||
)),
|
||||
) as _i4.Future<_i2.Decimal>);
|
||||
) as _i5.Future<_i2.Decimal>);
|
||||
@override
|
||||
_i4.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod(
|
||||
_i5.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#relayFee,
|
||||
[],
|
||||
{#requestID: requestID},
|
||||
),
|
||||
returnValue: _i4.Future<_i2.Decimal>.value(_FakeDecimal_1(
|
||||
returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_1(
|
||||
this,
|
||||
Invocation.method(
|
||||
#relayFee,
|
||||
|
@ -403,13 +415,13 @@ class MockElectrumX extends _i1.Mock implements _i3.ElectrumX {
|
|||
{#requestID: requestID},
|
||||
),
|
||||
)),
|
||||
) as _i4.Future<_i2.Decimal>);
|
||||
) as _i5.Future<_i2.Decimal>);
|
||||
}
|
||||
|
||||
/// A class which mocks [Prefs].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
||||
class MockPrefs extends _i1.Mock implements _i6.Prefs {
|
||||
MockPrefs() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
@ -465,12 +477,12 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i6.SyncingType get syncType => (super.noSuchMethod(
|
||||
_i7.SyncingType get syncType => (super.noSuchMethod(
|
||||
Invocation.getter(#syncType),
|
||||
returnValue: _i6.SyncingType.currentWalletOnly,
|
||||
) as _i6.SyncingType);
|
||||
returnValue: _i7.SyncingType.currentWalletOnly,
|
||||
) as _i7.SyncingType);
|
||||
@override
|
||||
set syncType(_i6.SyncingType? syncType) => super.noSuchMethod(
|
||||
set syncType(_i7.SyncingType? syncType) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#syncType,
|
||||
syncType,
|
||||
|
@ -629,12 +641,12 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i7.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
|
||||
_i8.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
|
||||
Invocation.getter(#backupFrequencyType),
|
||||
returnValue: _i7.BackupFrequencyType.everyTenMinutes,
|
||||
) as _i7.BackupFrequencyType);
|
||||
returnValue: _i8.BackupFrequencyType.everyTenMinutes,
|
||||
) as _i8.BackupFrequencyType);
|
||||
@override
|
||||
set backupFrequencyType(_i7.BackupFrequencyType? backupFrequencyType) =>
|
||||
set backupFrequencyType(_i8.BackupFrequencyType? backupFrequencyType) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#backupFrequencyType,
|
||||
|
@ -780,66 +792,82 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i3.FusionInfo get fusionServerInfo => (super.noSuchMethod(
|
||||
Invocation.getter(#fusionServerInfo),
|
||||
returnValue: _FakeFusionInfo_2(
|
||||
this,
|
||||
Invocation.getter(#fusionServerInfo),
|
||||
),
|
||||
) as _i3.FusionInfo);
|
||||
@override
|
||||
set fusionServerInfo(_i3.FusionInfo? fusionServerInfo) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#fusionServerInfo,
|
||||
fusionServerInfo,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get hasListeners => (super.noSuchMethod(
|
||||
Invocation.getter(#hasListeners),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
_i4.Future<void> init() => (super.noSuchMethod(
|
||||
_i5.Future<void> init() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#init,
|
||||
[],
|
||||
),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
) as _i4.Future<void>);
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
@override
|
||||
_i4.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
|
||||
_i5.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#incrementCurrentNotificationIndex,
|
||||
[],
|
||||
),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
) as _i4.Future<void>);
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
@override
|
||||
_i4.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
|
||||
_i5.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#isExternalCallsSet,
|
||||
[],
|
||||
),
|
||||
returnValue: _i4.Future<bool>.value(false),
|
||||
) as _i4.Future<bool>);
|
||||
returnValue: _i5.Future<bool>.value(false),
|
||||
) as _i5.Future<bool>);
|
||||
@override
|
||||
_i4.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
|
||||
_i5.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#saveUserID,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
) as _i4.Future<void>);
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
@override
|
||||
_i4.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
|
||||
_i5.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#saveSignupEpoch,
|
||||
[signupEpoch],
|
||||
),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
) as _i4.Future<void>);
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
@override
|
||||
_i8.AmountUnit amountUnit(_i9.Coin? coin) => (super.noSuchMethod(
|
||||
_i9.AmountUnit amountUnit(_i10.Coin? coin) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#amountUnit,
|
||||
[coin],
|
||||
),
|
||||
returnValue: _i8.AmountUnit.normal,
|
||||
) as _i8.AmountUnit);
|
||||
returnValue: _i9.AmountUnit.normal,
|
||||
) as _i9.AmountUnit);
|
||||
@override
|
||||
void updateAmountUnit({
|
||||
required _i9.Coin? coin,
|
||||
required _i8.AmountUnit? amountUnit,
|
||||
required _i10.Coin? coin,
|
||||
required _i9.AmountUnit? amountUnit,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -853,7 +881,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
int maxDecimals(_i9.Coin? coin) => (super.noSuchMethod(
|
||||
int maxDecimals(_i10.Coin? coin) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#maxDecimals,
|
||||
[coin],
|
||||
|
@ -862,7 +890,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
) as int);
|
||||
@override
|
||||
void updateMaxDecimals({
|
||||
required _i9.Coin? coin,
|
||||
required _i10.Coin? coin,
|
||||
required int? maxDecimals,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
|
@ -877,7 +905,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(
|
||||
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#addListener,
|
||||
[listener],
|
||||
|
@ -885,7 +913,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod(
|
||||
void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#removeListener,
|
||||
[listener],
|
||||
|
|
|
@ -3,21 +3,23 @@
|
|||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i4;
|
||||
import 'dart:io' as _i3;
|
||||
import 'dart:ui' as _i10;
|
||||
import 'dart:async' as _i5;
|
||||
import 'dart:io' as _i4;
|
||||
import 'dart:ui' as _i11;
|
||||
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:stackwallet/electrumx_rpc/rpc.dart' as _i2;
|
||||
import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart'
|
||||
as _i12;
|
||||
import 'package:stackwallet/services/tor_service.dart' as _i11;
|
||||
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i8;
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i7;
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9;
|
||||
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i6;
|
||||
import 'package:stackwallet/utilities/prefs.dart' as _i5;
|
||||
import 'package:tor_ffi_plugin/tor_ffi_plugin.dart' as _i13;
|
||||
as _i13;
|
||||
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart'
|
||||
as _i3;
|
||||
import 'package:stackwallet/services/tor_service.dart' as _i12;
|
||||
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i9;
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i8;
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10;
|
||||
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i7;
|
||||
import 'package:stackwallet/utilities/prefs.dart' as _i6;
|
||||
import 'package:tor_ffi_plugin/tor_ffi_plugin.dart' as _i14;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
|
@ -51,9 +53,19 @@ class _FakeJsonRPCResponse_1 extends _i1.SmartFake
|
|||
);
|
||||
}
|
||||
|
||||
class _FakeInternetAddress_2 extends _i1.SmartFake
|
||||
implements _i3.InternetAddress {
|
||||
_FakeInternetAddress_2(
|
||||
class _FakeFusionInfo_2 extends _i1.SmartFake implements _i3.FusionInfo {
|
||||
_FakeFusionInfo_2(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
parent,
|
||||
parentInvocation,
|
||||
);
|
||||
}
|
||||
|
||||
class _FakeInternetAddress_3 extends _i1.SmartFake
|
||||
implements _i4.InternetAddress {
|
||||
_FakeInternetAddress_3(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
|
@ -94,7 +106,7 @@ class MockJsonRPC extends _i1.Mock implements _i2.JsonRPC {
|
|||
),
|
||||
) as Duration);
|
||||
@override
|
||||
set proxyInfo(({_i3.InternetAddress host, int port})? _proxyInfo) =>
|
||||
set proxyInfo(({_i4.InternetAddress host, int port})? _proxyInfo) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#proxyInfo,
|
||||
|
@ -103,7 +115,7 @@ class MockJsonRPC extends _i1.Mock implements _i2.JsonRPC {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i4.Future<_i2.JsonRPCResponse> request(
|
||||
_i5.Future<_i2.JsonRPCResponse> request(
|
||||
String? jsonRpcRequest,
|
||||
Duration? requestTimeout,
|
||||
) =>
|
||||
|
@ -116,7 +128,7 @@ class MockJsonRPC extends _i1.Mock implements _i2.JsonRPC {
|
|||
],
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<_i2.JsonRPCResponse>.value(_FakeJsonRPCResponse_1(
|
||||
_i5.Future<_i2.JsonRPCResponse>.value(_FakeJsonRPCResponse_1(
|
||||
this,
|
||||
Invocation.method(
|
||||
#request,
|
||||
|
@ -126,32 +138,32 @@ class MockJsonRPC extends _i1.Mock implements _i2.JsonRPC {
|
|||
],
|
||||
),
|
||||
)),
|
||||
) as _i4.Future<_i2.JsonRPCResponse>);
|
||||
) as _i5.Future<_i2.JsonRPCResponse>);
|
||||
@override
|
||||
_i4.Future<void> disconnect({required String? reason}) => (super.noSuchMethod(
|
||||
_i5.Future<void> disconnect({required String? reason}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#disconnect,
|
||||
[],
|
||||
{#reason: reason},
|
||||
),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
) as _i4.Future<void>);
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
@override
|
||||
_i4.Future<void> connect() => (super.noSuchMethod(
|
||||
_i5.Future<void> connect() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#connect,
|
||||
[],
|
||||
),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
) as _i4.Future<void>);
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
}
|
||||
|
||||
/// A class which mocks [Prefs].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
||||
class MockPrefs extends _i1.Mock implements _i6.Prefs {
|
||||
MockPrefs() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
@ -207,12 +219,12 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i6.SyncingType get syncType => (super.noSuchMethod(
|
||||
_i7.SyncingType get syncType => (super.noSuchMethod(
|
||||
Invocation.getter(#syncType),
|
||||
returnValue: _i6.SyncingType.currentWalletOnly,
|
||||
) as _i6.SyncingType);
|
||||
returnValue: _i7.SyncingType.currentWalletOnly,
|
||||
) as _i7.SyncingType);
|
||||
@override
|
||||
set syncType(_i6.SyncingType? syncType) => super.noSuchMethod(
|
||||
set syncType(_i7.SyncingType? syncType) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#syncType,
|
||||
syncType,
|
||||
|
@ -371,12 +383,12 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i7.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
|
||||
_i8.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
|
||||
Invocation.getter(#backupFrequencyType),
|
||||
returnValue: _i7.BackupFrequencyType.everyTenMinutes,
|
||||
) as _i7.BackupFrequencyType);
|
||||
returnValue: _i8.BackupFrequencyType.everyTenMinutes,
|
||||
) as _i8.BackupFrequencyType);
|
||||
@override
|
||||
set backupFrequencyType(_i7.BackupFrequencyType? backupFrequencyType) =>
|
||||
set backupFrequencyType(_i8.BackupFrequencyType? backupFrequencyType) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#backupFrequencyType,
|
||||
|
@ -522,66 +534,82 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i3.FusionInfo get fusionServerInfo => (super.noSuchMethod(
|
||||
Invocation.getter(#fusionServerInfo),
|
||||
returnValue: _FakeFusionInfo_2(
|
||||
this,
|
||||
Invocation.getter(#fusionServerInfo),
|
||||
),
|
||||
) as _i3.FusionInfo);
|
||||
@override
|
||||
set fusionServerInfo(_i3.FusionInfo? fusionServerInfo) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#fusionServerInfo,
|
||||
fusionServerInfo,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get hasListeners => (super.noSuchMethod(
|
||||
Invocation.getter(#hasListeners),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
_i4.Future<void> init() => (super.noSuchMethod(
|
||||
_i5.Future<void> init() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#init,
|
||||
[],
|
||||
),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
) as _i4.Future<void>);
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
@override
|
||||
_i4.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
|
||||
_i5.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#incrementCurrentNotificationIndex,
|
||||
[],
|
||||
),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
) as _i4.Future<void>);
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
@override
|
||||
_i4.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
|
||||
_i5.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#isExternalCallsSet,
|
||||
[],
|
||||
),
|
||||
returnValue: _i4.Future<bool>.value(false),
|
||||
) as _i4.Future<bool>);
|
||||
returnValue: _i5.Future<bool>.value(false),
|
||||
) as _i5.Future<bool>);
|
||||
@override
|
||||
_i4.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
|
||||
_i5.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#saveUserID,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
) as _i4.Future<void>);
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
@override
|
||||
_i4.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
|
||||
_i5.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#saveSignupEpoch,
|
||||
[signupEpoch],
|
||||
),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
) as _i4.Future<void>);
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
@override
|
||||
_i8.AmountUnit amountUnit(_i9.Coin? coin) => (super.noSuchMethod(
|
||||
_i9.AmountUnit amountUnit(_i10.Coin? coin) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#amountUnit,
|
||||
[coin],
|
||||
),
|
||||
returnValue: _i8.AmountUnit.normal,
|
||||
) as _i8.AmountUnit);
|
||||
returnValue: _i9.AmountUnit.normal,
|
||||
) as _i9.AmountUnit);
|
||||
@override
|
||||
void updateAmountUnit({
|
||||
required _i9.Coin? coin,
|
||||
required _i8.AmountUnit? amountUnit,
|
||||
required _i10.Coin? coin,
|
||||
required _i9.AmountUnit? amountUnit,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -595,7 +623,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
int maxDecimals(_i9.Coin? coin) => (super.noSuchMethod(
|
||||
int maxDecimals(_i10.Coin? coin) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#maxDecimals,
|
||||
[coin],
|
||||
|
@ -604,7 +632,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
) as int);
|
||||
@override
|
||||
void updateMaxDecimals({
|
||||
required _i9.Coin? coin,
|
||||
required _i10.Coin? coin,
|
||||
required int? maxDecimals,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
|
@ -619,7 +647,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(
|
||||
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#addListener,
|
||||
[listener],
|
||||
|
@ -627,7 +655,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod(
|
||||
void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#removeListener,
|
||||
[listener],
|
||||
|
@ -655,24 +683,24 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
/// A class which mocks [TorService].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockTorService extends _i1.Mock implements _i11.TorService {
|
||||
class MockTorService extends _i1.Mock implements _i12.TorService {
|
||||
MockTorService() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i12.TorConnectionStatus get status => (super.noSuchMethod(
|
||||
_i13.TorConnectionStatus get status => (super.noSuchMethod(
|
||||
Invocation.getter(#status),
|
||||
returnValue: _i12.TorConnectionStatus.disconnected,
|
||||
) as _i12.TorConnectionStatus);
|
||||
returnValue: _i13.TorConnectionStatus.disconnected,
|
||||
) as _i13.TorConnectionStatus);
|
||||
@override
|
||||
({_i3.InternetAddress host, int port}) getProxyInfo() => (super.noSuchMethod(
|
||||
({_i4.InternetAddress host, int port}) getProxyInfo() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getProxyInfo,
|
||||
[],
|
||||
),
|
||||
returnValue: (
|
||||
host: _FakeInternetAddress_2(
|
||||
host: _FakeInternetAddress_3(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getProxyInfo,
|
||||
|
@ -681,11 +709,11 @@ class MockTorService extends _i1.Mock implements _i11.TorService {
|
|||
),
|
||||
port: 0
|
||||
),
|
||||
) as ({_i3.InternetAddress host, int port}));
|
||||
) as ({_i4.InternetAddress host, int port}));
|
||||
@override
|
||||
void init({
|
||||
required String? torDataDirPath,
|
||||
_i13.Tor? mockableOverride,
|
||||
_i14.Tor? mockableOverride,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -699,21 +727,21 @@ class MockTorService extends _i1.Mock implements _i11.TorService {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i4.Future<void> start() => (super.noSuchMethod(
|
||||
_i5.Future<void> start() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#start,
|
||||
[],
|
||||
),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
) as _i4.Future<void>);
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
@override
|
||||
_i4.Future<void> disable() => (super.noSuchMethod(
|
||||
_i5.Future<void> disable() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#disable,
|
||||
[],
|
||||
),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
) as _i4.Future<void>);
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -401,6 +401,11 @@ class MockManager extends _i1.Mock implements _i12.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -362,6 +362,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -360,6 +360,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -3,38 +3,40 @@
|
|||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i7;
|
||||
import 'dart:ui' as _i10;
|
||||
import 'dart:async' as _i8;
|
||||
import 'dart:ui' as _i11;
|
||||
|
||||
import 'package:decimal/decimal.dart' as _i16;
|
||||
import 'package:decimal/decimal.dart' as _i17;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'
|
||||
as _i19;
|
||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'
|
||||
as _i21;
|
||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'
|
||||
as _i22;
|
||||
import 'package:stackwallet/models/exchange/response_objects/estimate.dart'
|
||||
as _i18;
|
||||
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'
|
||||
as _i20;
|
||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'
|
||||
as _i22;
|
||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'
|
||||
as _i23;
|
||||
import 'package:stackwallet/models/exchange/response_objects/estimate.dart'
|
||||
as _i19;
|
||||
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'
|
||||
as _i21;
|
||||
import 'package:stackwallet/models/exchange/response_objects/range.dart'
|
||||
as _i17;
|
||||
as _i18;
|
||||
import 'package:stackwallet/models/exchange/response_objects/trade.dart'
|
||||
as _i12;
|
||||
import 'package:stackwallet/models/isar/exchange_cache/currency.dart' as _i15;
|
||||
import 'package:stackwallet/models/isar/exchange_cache/pair.dart' as _i23;
|
||||
import 'package:stackwallet/networking/http.dart' as _i2;
|
||||
as _i13;
|
||||
import 'package:stackwallet/models/isar/exchange_cache/currency.dart' as _i16;
|
||||
import 'package:stackwallet/models/isar/exchange_cache/pair.dart' as _i24;
|
||||
import 'package:stackwallet/networking/http.dart' as _i3;
|
||||
import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'
|
||||
as _i14;
|
||||
import 'package:stackwallet/services/exchange/exchange_response.dart' as _i3;
|
||||
import 'package:stackwallet/services/trade_notes_service.dart' as _i13;
|
||||
import 'package:stackwallet/services/trade_service.dart' as _i11;
|
||||
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i8;
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i6;
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9;
|
||||
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i5;
|
||||
import 'package:stackwallet/utilities/prefs.dart' as _i4;
|
||||
as _i15;
|
||||
import 'package:stackwallet/services/exchange/exchange_response.dart' as _i4;
|
||||
import 'package:stackwallet/services/mixins/fusion_wallet_interface.dart'
|
||||
as _i2;
|
||||
import 'package:stackwallet/services/trade_notes_service.dart' as _i14;
|
||||
import 'package:stackwallet/services/trade_service.dart' as _i12;
|
||||
import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i9;
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i7;
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10;
|
||||
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i6;
|
||||
import 'package:stackwallet/utilities/prefs.dart' as _i5;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
|
@ -47,8 +49,8 @@ import 'package:stackwallet/utilities/prefs.dart' as _i4;
|
|||
// ignore_for_file: camel_case_types
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
|
||||
class _FakeHTTP_0 extends _i1.SmartFake implements _i2.HTTP {
|
||||
_FakeHTTP_0(
|
||||
class _FakeFusionInfo_0 extends _i1.SmartFake implements _i2.FusionInfo {
|
||||
_FakeFusionInfo_0(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
|
@ -57,9 +59,19 @@ class _FakeHTTP_0 extends _i1.SmartFake implements _i2.HTTP {
|
|||
);
|
||||
}
|
||||
|
||||
class _FakeExchangeResponse_1<T> extends _i1.SmartFake
|
||||
implements _i3.ExchangeResponse<T> {
|
||||
_FakeExchangeResponse_1(
|
||||
class _FakeHTTP_1 extends _i1.SmartFake implements _i3.HTTP {
|
||||
_FakeHTTP_1(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
parent,
|
||||
parentInvocation,
|
||||
);
|
||||
}
|
||||
|
||||
class _FakeExchangeResponse_2<T> extends _i1.SmartFake
|
||||
implements _i4.ExchangeResponse<T> {
|
||||
_FakeExchangeResponse_2(
|
||||
Object parent,
|
||||
Invocation parentInvocation,
|
||||
) : super(
|
||||
|
@ -71,7 +83,7 @@ class _FakeExchangeResponse_1<T> extends _i1.SmartFake
|
|||
/// A class which mocks [Prefs].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockPrefs extends _i1.Mock implements _i4.Prefs {
|
||||
class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
||||
MockPrefs() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
@ -127,12 +139,12 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i5.SyncingType get syncType => (super.noSuchMethod(
|
||||
_i6.SyncingType get syncType => (super.noSuchMethod(
|
||||
Invocation.getter(#syncType),
|
||||
returnValue: _i5.SyncingType.currentWalletOnly,
|
||||
) as _i5.SyncingType);
|
||||
returnValue: _i6.SyncingType.currentWalletOnly,
|
||||
) as _i6.SyncingType);
|
||||
@override
|
||||
set syncType(_i5.SyncingType? syncType) => super.noSuchMethod(
|
||||
set syncType(_i6.SyncingType? syncType) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#syncType,
|
||||
syncType,
|
||||
|
@ -291,12 +303,12 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i6.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
|
||||
_i7.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(
|
||||
Invocation.getter(#backupFrequencyType),
|
||||
returnValue: _i6.BackupFrequencyType.everyTenMinutes,
|
||||
) as _i6.BackupFrequencyType);
|
||||
returnValue: _i7.BackupFrequencyType.everyTenMinutes,
|
||||
) as _i7.BackupFrequencyType);
|
||||
@override
|
||||
set backupFrequencyType(_i6.BackupFrequencyType? backupFrequencyType) =>
|
||||
set backupFrequencyType(_i7.BackupFrequencyType? backupFrequencyType) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#backupFrequencyType,
|
||||
|
@ -442,66 +454,82 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i2.FusionInfo get fusionServerInfo => (super.noSuchMethod(
|
||||
Invocation.getter(#fusionServerInfo),
|
||||
returnValue: _FakeFusionInfo_0(
|
||||
this,
|
||||
Invocation.getter(#fusionServerInfo),
|
||||
),
|
||||
) as _i2.FusionInfo);
|
||||
@override
|
||||
set fusionServerInfo(_i2.FusionInfo? fusionServerInfo) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#fusionServerInfo,
|
||||
fusionServerInfo,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get hasListeners => (super.noSuchMethod(
|
||||
Invocation.getter(#hasListeners),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
_i7.Future<void> init() => (super.noSuchMethod(
|
||||
_i8.Future<void> init() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#init,
|
||||
[],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
returnValue: _i8.Future<void>.value(),
|
||||
returnValueForMissingStub: _i8.Future<void>.value(),
|
||||
) as _i8.Future<void>);
|
||||
@override
|
||||
_i7.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
|
||||
_i8.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#incrementCurrentNotificationIndex,
|
||||
[],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
returnValue: _i8.Future<void>.value(),
|
||||
returnValueForMissingStub: _i8.Future<void>.value(),
|
||||
) as _i8.Future<void>);
|
||||
@override
|
||||
_i7.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
|
||||
_i8.Future<bool> isExternalCallsSet() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#isExternalCallsSet,
|
||||
[],
|
||||
),
|
||||
returnValue: _i7.Future<bool>.value(false),
|
||||
) as _i7.Future<bool>);
|
||||
returnValue: _i8.Future<bool>.value(false),
|
||||
) as _i8.Future<bool>);
|
||||
@override
|
||||
_i7.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
|
||||
_i8.Future<void> saveUserID(String? userId) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#saveUserID,
|
||||
[userId],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
returnValue: _i8.Future<void>.value(),
|
||||
returnValueForMissingStub: _i8.Future<void>.value(),
|
||||
) as _i8.Future<void>);
|
||||
@override
|
||||
_i7.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
|
||||
_i8.Future<void> saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#saveSignupEpoch,
|
||||
[signupEpoch],
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
returnValue: _i8.Future<void>.value(),
|
||||
returnValueForMissingStub: _i8.Future<void>.value(),
|
||||
) as _i8.Future<void>);
|
||||
@override
|
||||
_i8.AmountUnit amountUnit(_i9.Coin? coin) => (super.noSuchMethod(
|
||||
_i9.AmountUnit amountUnit(_i10.Coin? coin) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#amountUnit,
|
||||
[coin],
|
||||
),
|
||||
returnValue: _i8.AmountUnit.normal,
|
||||
) as _i8.AmountUnit);
|
||||
returnValue: _i9.AmountUnit.normal,
|
||||
) as _i9.AmountUnit);
|
||||
@override
|
||||
void updateAmountUnit({
|
||||
required _i9.Coin? coin,
|
||||
required _i8.AmountUnit? amountUnit,
|
||||
required _i10.Coin? coin,
|
||||
required _i9.AmountUnit? amountUnit,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -515,7 +543,7 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
int maxDecimals(_i9.Coin? coin) => (super.noSuchMethod(
|
||||
int maxDecimals(_i10.Coin? coin) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#maxDecimals,
|
||||
[coin],
|
||||
|
@ -524,7 +552,7 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
|
|||
) as int);
|
||||
@override
|
||||
void updateMaxDecimals({
|
||||
required _i9.Coin? coin,
|
||||
required _i10.Coin? coin,
|
||||
required int? maxDecimals,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
|
@ -539,7 +567,7 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(
|
||||
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#addListener,
|
||||
[listener],
|
||||
|
@ -547,7 +575,7 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod(
|
||||
void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#removeListener,
|
||||
[listener],
|
||||
|
@ -575,29 +603,29 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
|
|||
/// A class which mocks [TradesService].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockTradesService extends _i1.Mock implements _i11.TradesService {
|
||||
class MockTradesService extends _i1.Mock implements _i12.TradesService {
|
||||
MockTradesService() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
List<_i12.Trade> get trades => (super.noSuchMethod(
|
||||
List<_i13.Trade> get trades => (super.noSuchMethod(
|
||||
Invocation.getter(#trades),
|
||||
returnValue: <_i12.Trade>[],
|
||||
) as List<_i12.Trade>);
|
||||
returnValue: <_i13.Trade>[],
|
||||
) as List<_i13.Trade>);
|
||||
@override
|
||||
bool get hasListeners => (super.noSuchMethod(
|
||||
Invocation.getter(#hasListeners),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
_i12.Trade? get(String? tradeId) => (super.noSuchMethod(Invocation.method(
|
||||
_i13.Trade? get(String? tradeId) => (super.noSuchMethod(Invocation.method(
|
||||
#get,
|
||||
[tradeId],
|
||||
)) as _i12.Trade?);
|
||||
)) as _i13.Trade?);
|
||||
@override
|
||||
_i7.Future<void> add({
|
||||
required _i12.Trade? trade,
|
||||
_i8.Future<void> add({
|
||||
required _i13.Trade? trade,
|
||||
required bool? shouldNotifyListeners,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
|
@ -609,12 +637,12 @@ class MockTradesService extends _i1.Mock implements _i11.TradesService {
|
|||
#shouldNotifyListeners: shouldNotifyListeners,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
returnValue: _i8.Future<void>.value(),
|
||||
returnValueForMissingStub: _i8.Future<void>.value(),
|
||||
) as _i8.Future<void>);
|
||||
@override
|
||||
_i7.Future<void> edit({
|
||||
required _i12.Trade? trade,
|
||||
_i8.Future<void> edit({
|
||||
required _i13.Trade? trade,
|
||||
required bool? shouldNotifyListeners,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
|
@ -626,12 +654,12 @@ class MockTradesService extends _i1.Mock implements _i11.TradesService {
|
|||
#shouldNotifyListeners: shouldNotifyListeners,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
returnValue: _i8.Future<void>.value(),
|
||||
returnValueForMissingStub: _i8.Future<void>.value(),
|
||||
) as _i8.Future<void>);
|
||||
@override
|
||||
_i7.Future<void> delete({
|
||||
required _i12.Trade? trade,
|
||||
_i8.Future<void> delete({
|
||||
required _i13.Trade? trade,
|
||||
required bool? shouldNotifyListeners,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
|
@ -643,11 +671,11 @@ class MockTradesService extends _i1.Mock implements _i11.TradesService {
|
|||
#shouldNotifyListeners: shouldNotifyListeners,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
returnValue: _i8.Future<void>.value(),
|
||||
returnValueForMissingStub: _i8.Future<void>.value(),
|
||||
) as _i8.Future<void>);
|
||||
@override
|
||||
_i7.Future<void> deleteByUuid({
|
||||
_i8.Future<void> deleteByUuid({
|
||||
required String? uuid,
|
||||
required bool? shouldNotifyListeners,
|
||||
}) =>
|
||||
|
@ -660,11 +688,11 @@ class MockTradesService extends _i1.Mock implements _i11.TradesService {
|
|||
#shouldNotifyListeners: shouldNotifyListeners,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
returnValue: _i8.Future<void>.value(),
|
||||
returnValueForMissingStub: _i8.Future<void>.value(),
|
||||
) as _i8.Future<void>);
|
||||
@override
|
||||
void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(
|
||||
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#addListener,
|
||||
[listener],
|
||||
|
@ -672,7 +700,7 @@ class MockTradesService extends _i1.Mock implements _i11.TradesService {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod(
|
||||
void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#removeListener,
|
||||
[listener],
|
||||
|
@ -700,7 +728,7 @@ class MockTradesService extends _i1.Mock implements _i11.TradesService {
|
|||
/// A class which mocks [TradeNotesService].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockTradeNotesService extends _i1.Mock implements _i13.TradeNotesService {
|
||||
class MockTradeNotesService extends _i1.Mock implements _i14.TradeNotesService {
|
||||
MockTradeNotesService() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
@ -725,7 +753,7 @@ class MockTradeNotesService extends _i1.Mock implements _i13.TradeNotesService {
|
|||
returnValue: '',
|
||||
) as String);
|
||||
@override
|
||||
_i7.Future<void> set({
|
||||
_i8.Future<void> set({
|
||||
required String? tradeId,
|
||||
required String? note,
|
||||
}) =>
|
||||
|
@ -738,21 +766,21 @@ class MockTradeNotesService extends _i1.Mock implements _i13.TradeNotesService {
|
|||
#note: note,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
returnValue: _i8.Future<void>.value(),
|
||||
returnValueForMissingStub: _i8.Future<void>.value(),
|
||||
) as _i8.Future<void>);
|
||||
@override
|
||||
_i7.Future<void> delete({required String? tradeId}) => (super.noSuchMethod(
|
||||
_i8.Future<void> delete({required String? tradeId}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#delete,
|
||||
[],
|
||||
{#tradeId: tradeId},
|
||||
),
|
||||
returnValue: _i7.Future<void>.value(),
|
||||
returnValueForMissingStub: _i7.Future<void>.value(),
|
||||
) as _i7.Future<void>);
|
||||
returnValue: _i8.Future<void>.value(),
|
||||
returnValueForMissingStub: _i8.Future<void>.value(),
|
||||
) as _i8.Future<void>);
|
||||
@override
|
||||
void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(
|
||||
void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#addListener,
|
||||
[listener],
|
||||
|
@ -760,7 +788,7 @@ class MockTradeNotesService extends _i1.Mock implements _i13.TradeNotesService {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod(
|
||||
void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#removeListener,
|
||||
[listener],
|
||||
|
@ -788,21 +816,21 @@ class MockTradeNotesService extends _i1.Mock implements _i13.TradeNotesService {
|
|||
/// A class which mocks [ChangeNowAPI].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
||||
class MockChangeNowAPI extends _i1.Mock implements _i15.ChangeNowAPI {
|
||||
MockChangeNowAPI() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i2.HTTP get client => (super.noSuchMethod(
|
||||
_i3.HTTP get client => (super.noSuchMethod(
|
||||
Invocation.getter(#client),
|
||||
returnValue: _FakeHTTP_0(
|
||||
returnValue: _FakeHTTP_1(
|
||||
this,
|
||||
Invocation.getter(#client),
|
||||
),
|
||||
) as _i2.HTTP);
|
||||
) as _i3.HTTP);
|
||||
@override
|
||||
_i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>> getAvailableCurrencies({
|
||||
_i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>> getAvailableCurrencies({
|
||||
bool? fixedRate,
|
||||
bool? active,
|
||||
}) =>
|
||||
|
@ -816,8 +844,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
returnValue:
|
||||
_i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>>.value(
|
||||
_FakeExchangeResponse_1<List<_i15.Currency>>(
|
||||
_i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>>.value(
|
||||
_FakeExchangeResponse_2<List<_i16.Currency>>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getAvailableCurrencies,
|
||||
|
@ -828,26 +856,26 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>>);
|
||||
@override
|
||||
_i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>> getCurrenciesV2() =>
|
||||
_i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>> getCurrenciesV2() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getCurrenciesV2,
|
||||
[],
|
||||
),
|
||||
returnValue:
|
||||
_i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>>.value(
|
||||
_FakeExchangeResponse_1<List<_i15.Currency>>(
|
||||
_i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>>.value(
|
||||
_FakeExchangeResponse_2<List<_i16.Currency>>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getCurrenciesV2,
|
||||
[],
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>>);
|
||||
@override
|
||||
_i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>> getPairedCurrencies({
|
||||
_i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>> getPairedCurrencies({
|
||||
required String? ticker,
|
||||
bool? fixedRate,
|
||||
}) =>
|
||||
|
@ -861,8 +889,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
returnValue:
|
||||
_i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>>.value(
|
||||
_FakeExchangeResponse_1<List<_i15.Currency>>(
|
||||
_i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>>.value(
|
||||
_FakeExchangeResponse_2<List<_i16.Currency>>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getPairedCurrencies,
|
||||
|
@ -873,9 +901,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<List<_i15.Currency>>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<List<_i16.Currency>>>);
|
||||
@override
|
||||
_i7.Future<_i3.ExchangeResponse<_i16.Decimal>> getMinimalExchangeAmount({
|
||||
_i8.Future<_i4.ExchangeResponse<_i17.Decimal>> getMinimalExchangeAmount({
|
||||
required String? fromTicker,
|
||||
required String? toTicker,
|
||||
String? apiKey,
|
||||
|
@ -890,8 +918,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
#apiKey: apiKey,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<_i3.ExchangeResponse<_i16.Decimal>>.value(
|
||||
_FakeExchangeResponse_1<_i16.Decimal>(
|
||||
returnValue: _i8.Future<_i4.ExchangeResponse<_i17.Decimal>>.value(
|
||||
_FakeExchangeResponse_2<_i17.Decimal>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getMinimalExchangeAmount,
|
||||
|
@ -903,9 +931,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<_i16.Decimal>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<_i17.Decimal>>);
|
||||
@override
|
||||
_i7.Future<_i3.ExchangeResponse<_i17.Range>> getRange({
|
||||
_i8.Future<_i4.ExchangeResponse<_i18.Range>> getRange({
|
||||
required String? fromTicker,
|
||||
required String? toTicker,
|
||||
required bool? isFixedRate,
|
||||
|
@ -922,8 +950,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
#apiKey: apiKey,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<_i3.ExchangeResponse<_i17.Range>>.value(
|
||||
_FakeExchangeResponse_1<_i17.Range>(
|
||||
returnValue: _i8.Future<_i4.ExchangeResponse<_i18.Range>>.value(
|
||||
_FakeExchangeResponse_2<_i18.Range>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getRange,
|
||||
|
@ -936,12 +964,12 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<_i17.Range>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<_i18.Range>>);
|
||||
@override
|
||||
_i7.Future<_i3.ExchangeResponse<_i18.Estimate>> getEstimatedExchangeAmount({
|
||||
_i8.Future<_i4.ExchangeResponse<_i19.Estimate>> getEstimatedExchangeAmount({
|
||||
required String? fromTicker,
|
||||
required String? toTicker,
|
||||
required _i16.Decimal? fromAmount,
|
||||
required _i17.Decimal? fromAmount,
|
||||
String? apiKey,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
|
@ -955,8 +983,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
#apiKey: apiKey,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<_i3.ExchangeResponse<_i18.Estimate>>.value(
|
||||
_FakeExchangeResponse_1<_i18.Estimate>(
|
||||
returnValue: _i8.Future<_i4.ExchangeResponse<_i19.Estimate>>.value(
|
||||
_FakeExchangeResponse_2<_i19.Estimate>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getEstimatedExchangeAmount,
|
||||
|
@ -969,13 +997,13 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<_i18.Estimate>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<_i19.Estimate>>);
|
||||
@override
|
||||
_i7.Future<_i3.ExchangeResponse<_i18.Estimate>>
|
||||
_i8.Future<_i4.ExchangeResponse<_i19.Estimate>>
|
||||
getEstimatedExchangeAmountFixedRate({
|
||||
required String? fromTicker,
|
||||
required String? toTicker,
|
||||
required _i16.Decimal? fromAmount,
|
||||
required _i17.Decimal? fromAmount,
|
||||
required bool? reversed,
|
||||
bool? useRateId = true,
|
||||
String? apiKey,
|
||||
|
@ -993,8 +1021,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
#apiKey: apiKey,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<_i3.ExchangeResponse<_i18.Estimate>>.value(
|
||||
_FakeExchangeResponse_1<_i18.Estimate>(
|
||||
returnValue: _i8.Future<_i4.ExchangeResponse<_i19.Estimate>>.value(
|
||||
_FakeExchangeResponse_2<_i19.Estimate>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getEstimatedExchangeAmountFixedRate,
|
||||
|
@ -1009,17 +1037,17 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<_i18.Estimate>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<_i19.Estimate>>);
|
||||
@override
|
||||
_i7.Future<_i3.ExchangeResponse<_i19.CNExchangeEstimate>>
|
||||
_i8.Future<_i4.ExchangeResponse<_i20.CNExchangeEstimate>>
|
||||
getEstimatedExchangeAmountV2({
|
||||
required String? fromTicker,
|
||||
required String? toTicker,
|
||||
required _i19.CNEstimateType? fromOrTo,
|
||||
required _i16.Decimal? amount,
|
||||
required _i20.CNEstimateType? fromOrTo,
|
||||
required _i17.Decimal? amount,
|
||||
String? fromNetwork,
|
||||
String? toNetwork,
|
||||
_i19.CNFlowType? flow = _i19.CNFlowType.standard,
|
||||
_i20.CNFlowType? flow = _i20.CNFlowType.standard,
|
||||
String? apiKey,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
|
@ -1038,8 +1066,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
returnValue:
|
||||
_i7.Future<_i3.ExchangeResponse<_i19.CNExchangeEstimate>>.value(
|
||||
_FakeExchangeResponse_1<_i19.CNExchangeEstimate>(
|
||||
_i8.Future<_i4.ExchangeResponse<_i20.CNExchangeEstimate>>.value(
|
||||
_FakeExchangeResponse_2<_i20.CNExchangeEstimate>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getEstimatedExchangeAmountV2,
|
||||
|
@ -1056,18 +1084,18 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<_i19.CNExchangeEstimate>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<_i20.CNExchangeEstimate>>);
|
||||
@override
|
||||
_i7.Future<_i3.ExchangeResponse<List<_i20.FixedRateMarket>>>
|
||||
_i8.Future<_i4.ExchangeResponse<List<_i21.FixedRateMarket>>>
|
||||
getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getAvailableFixedRateMarkets,
|
||||
[],
|
||||
{#apiKey: apiKey},
|
||||
),
|
||||
returnValue: _i7
|
||||
.Future<_i3.ExchangeResponse<List<_i20.FixedRateMarket>>>.value(
|
||||
_FakeExchangeResponse_1<List<_i20.FixedRateMarket>>(
|
||||
returnValue: _i8
|
||||
.Future<_i4.ExchangeResponse<List<_i21.FixedRateMarket>>>.value(
|
||||
_FakeExchangeResponse_2<List<_i21.FixedRateMarket>>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getAvailableFixedRateMarkets,
|
||||
|
@ -1075,14 +1103,14 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
{#apiKey: apiKey},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<List<_i20.FixedRateMarket>>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<List<_i21.FixedRateMarket>>>);
|
||||
@override
|
||||
_i7.Future<_i3.ExchangeResponse<_i21.ExchangeTransaction>>
|
||||
_i8.Future<_i4.ExchangeResponse<_i22.ExchangeTransaction>>
|
||||
createStandardExchangeTransaction({
|
||||
required String? fromTicker,
|
||||
required String? toTicker,
|
||||
required String? receivingAddress,
|
||||
required _i16.Decimal? amount,
|
||||
required _i17.Decimal? amount,
|
||||
String? extraId = r'',
|
||||
String? userId = r'',
|
||||
String? contactEmail = r'',
|
||||
|
@ -1107,9 +1135,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
#apiKey: apiKey,
|
||||
},
|
||||
),
|
||||
returnValue: _i7
|
||||
.Future<_i3.ExchangeResponse<_i21.ExchangeTransaction>>.value(
|
||||
_FakeExchangeResponse_1<_i21.ExchangeTransaction>(
|
||||
returnValue: _i8
|
||||
.Future<_i4.ExchangeResponse<_i22.ExchangeTransaction>>.value(
|
||||
_FakeExchangeResponse_2<_i22.ExchangeTransaction>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#createStandardExchangeTransaction,
|
||||
|
@ -1128,14 +1156,14 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<_i21.ExchangeTransaction>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<_i22.ExchangeTransaction>>);
|
||||
@override
|
||||
_i7.Future<_i3.ExchangeResponse<_i21.ExchangeTransaction>>
|
||||
_i8.Future<_i4.ExchangeResponse<_i22.ExchangeTransaction>>
|
||||
createFixedRateExchangeTransaction({
|
||||
required String? fromTicker,
|
||||
required String? toTicker,
|
||||
required String? receivingAddress,
|
||||
required _i16.Decimal? amount,
|
||||
required _i17.Decimal? amount,
|
||||
required String? rateId,
|
||||
required bool? reversed,
|
||||
String? extraId = r'',
|
||||
|
@ -1164,9 +1192,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
#apiKey: apiKey,
|
||||
},
|
||||
),
|
||||
returnValue: _i7
|
||||
.Future<_i3.ExchangeResponse<_i21.ExchangeTransaction>>.value(
|
||||
_FakeExchangeResponse_1<_i21.ExchangeTransaction>(
|
||||
returnValue: _i8
|
||||
.Future<_i4.ExchangeResponse<_i22.ExchangeTransaction>>.value(
|
||||
_FakeExchangeResponse_2<_i22.ExchangeTransaction>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#createFixedRateExchangeTransaction,
|
||||
|
@ -1187,11 +1215,11 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<_i21.ExchangeTransaction>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<_i22.ExchangeTransaction>>);
|
||||
@override
|
||||
_i7.Future<
|
||||
_i3
|
||||
.ExchangeResponse<_i22.ExchangeTransactionStatus>> getTransactionStatus({
|
||||
_i8.Future<
|
||||
_i4
|
||||
.ExchangeResponse<_i23.ExchangeTransactionStatus>> getTransactionStatus({
|
||||
required String? id,
|
||||
String? apiKey,
|
||||
}) =>
|
||||
|
@ -1204,9 +1232,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
#apiKey: apiKey,
|
||||
},
|
||||
),
|
||||
returnValue: _i7
|
||||
.Future<_i3.ExchangeResponse<_i22.ExchangeTransactionStatus>>.value(
|
||||
_FakeExchangeResponse_1<_i22.ExchangeTransactionStatus>(
|
||||
returnValue: _i8
|
||||
.Future<_i4.ExchangeResponse<_i23.ExchangeTransactionStatus>>.value(
|
||||
_FakeExchangeResponse_2<_i23.ExchangeTransactionStatus>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getTransactionStatus,
|
||||
|
@ -1217,9 +1245,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<_i22.ExchangeTransactionStatus>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<_i23.ExchangeTransactionStatus>>);
|
||||
@override
|
||||
_i7.Future<_i3.ExchangeResponse<List<_i23.Pair>>>
|
||||
_i8.Future<_i4.ExchangeResponse<List<_i24.Pair>>>
|
||||
getAvailableFloatingRatePairs({bool? includePartners = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -1228,8 +1256,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
{#includePartners: includePartners},
|
||||
),
|
||||
returnValue:
|
||||
_i7.Future<_i3.ExchangeResponse<List<_i23.Pair>>>.value(
|
||||
_FakeExchangeResponse_1<List<_i23.Pair>>(
|
||||
_i8.Future<_i4.ExchangeResponse<List<_i24.Pair>>>.value(
|
||||
_FakeExchangeResponse_2<List<_i24.Pair>>(
|
||||
this,
|
||||
Invocation.method(
|
||||
#getAvailableFloatingRatePairs,
|
||||
|
@ -1237,5 +1265,5 @@ class MockChangeNowAPI extends _i1.Mock implements _i14.ChangeNowAPI {
|
|||
{#includePartners: includePartners},
|
||||
),
|
||||
)),
|
||||
) as _i7.Future<_i3.ExchangeResponse<List<_i23.Pair>>>);
|
||||
) as _i8.Future<_i4.ExchangeResponse<List<_i24.Pair>>>);
|
||||
}
|
||||
|
|
|
@ -680,6 +680,11 @@ class MockManager extends _i1.Mock implements _i13.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -467,6 +467,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -467,6 +467,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -467,6 +467,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -234,6 +234,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -465,6 +465,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -680,6 +680,11 @@ class MockManager extends _i1.Mock implements _i13.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -521,6 +521,11 @@ class MockManager extends _i1.Mock implements _i13.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -234,6 +234,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -234,6 +234,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -449,6 +449,11 @@ class MockManager extends _i1.Mock implements _i12.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -449,6 +449,11 @@ class MockManager extends _i1.Mock implements _i12.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -234,6 +234,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -234,6 +234,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -465,6 +465,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -701,6 +701,11 @@ class MockManager extends _i1.Mock implements _i15.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -465,6 +465,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -236,6 +236,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasFusionSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasFusionSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue