mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-03-31 19:39:06 +00:00
commit
6d55f3fffa
84 changed files with 4328 additions and 1108 deletions
assets/svg/coin_control
lib
db
exceptions/exchange
models
pages
coin_control
exchange_view
generic
receive_view
addresses
address_card.dartedit_address_label_view.dartreceiving_addresses_view.dartwallet_addresses_view.dart
receive_view.dartsend_view
settings_views/global_settings_view/advanced_views
wallet_view
services
coins
event_bus/events/global
exchange/majestic_bank
mixins
notifications_service.dartutilities
widgets
test
cached_electrumx_test.mocks.dartelectrumx_test.mocks.dart
pages/send_view
screen_tests
address_book_view/subviews
add_address_book_view_screen_test.mocks.dartaddress_book_entry_details_view_screen_test.mocks.dartedit_address_book_entry_view_screen_test.mocks.dart
exchange
lockscreen_view_screen_test.mocks.dartmain_view_tests
main_view_screen_testA_test.mocks.dartmain_view_screen_testB_test.mocks.dartmain_view_screen_testC_test.mocks.dart
onboarding
backup_key_view_screen_test.mocks.dartbackup_key_warning_view_screen_test.mocks.dartcreate_pin_view_screen_test.mocks.dartrestore_wallet_view_screen_test.mocks.dartverify_backup_key_view_screen_test.mocks.dart
settings_view
settings_subviews
currency_view_screen_test.mocks.dart
settings_view_screen_test.mocks.dartnetwork_settings_subviews
wallet_backup_view_screen_test.mocks.dartwallet_settings_subviews
wallet_settings_view_screen_test.mocks.darttransaction_subviews
wallet_view
widget_tests
3
assets/svg/coin_control/frozen.svg
Normal file
3
assets/svg/coin_control/frozen.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.8628 14.5871C17.6515 14.9523 17.2648 15.1564 16.8709 15.1564C16.6744 15.1564 16.4756 15.1061 16.2934 14.9998L15.395 14.4755L15.5876 15.2021C15.7091 15.6608 15.4352 16.132 14.976 16.2527C14.9021 16.2728 14.8281 16.2817 14.7553 16.2817C14.3746 16.2817 14.0262 16.0277 13.9242 15.6429L13.289 13.2467L11.1454 11.9946L11.1454 14.519L12.8777 16.2693C13.2121 16.6073 13.2087 17.1509 12.871 17.4846C12.703 17.6522 12.4846 17.7345 12.2661 17.7345C12.0444 17.7345 11.8225 17.6495 11.6545 17.4794L11.1454 16.9649V18.021C11.1454 18.6544 10.6319 19.1668 9.99845 19.1668C9.36502 19.1668 8.85155 18.6544 8.85155 18.021L8.85154 16.9647L8.34236 17.4792C8.00856 17.8172 7.46401 17.8194 7.12599 17.4859C6.78825 17.1525 6.78492 16.6087 7.11927 16.2706L8.85163 14.5204L8.82147 11.9946L6.67768 13.2454L6.04246 15.6416C5.94048 16.0265 5.59201 16.2804 5.21138 16.2804C5.13851 16.2804 5.06457 16.2715 4.99066 16.2513C4.53161 16.1305 4.25768 15.6594 4.37907 15.2007L4.57168 14.4742L3.67327 14.9984C3.52235 15.1063 3.32362 15.1564 3.12703 15.1564C2.7328 15.1564 2.34858 14.9527 2.13553 14.5878C1.8157 14.0417 2 13.3403 2.54731 13.0212L3.47687 12.4787L2.74891 12.2815C2.29022 12.1593 2.01987 11.6867 2.14412 11.2284C2.26837 10.77 2.73888 10.5015 3.19936 10.6232L5.55942 11.2642L7.72576 10.0002L5.55942 8.73616L3.19936 9.37712C3.12378 9.39726 3.04808 9.40733 2.9736 9.40733C2.59512 9.40733 2.24779 9.15557 2.1443 8.77282C2.01987 8.31364 2.29022 7.84456 2.74891 7.71924L3.47687 7.52201L2.54731 6.97803C2.00018 6.65913 1.81577 5.95752 2.13553 5.41146C2.45368 4.86432 3.15604 4.67956 3.70425 4.99967L4.60265 5.52389L4.41001 4.79736C4.28862 4.33939 4.56398 3.86816 4.99009 3.74749C5.48423 3.62324 5.92108 3.89967 6.07505 4.35729L6.71027 6.75352L8.82147 8.0057L8.82146 5.48128L7.12062 3.72959C6.78761 3.39157 6.78761 2.84766 7.12778 2.51429C7.46437 2.17949 8.00864 2.18415 8.34523 2.52109L8.85441 3.03564L8.82147 1.97933C8.82147 1.3459 9.33494 0.833496 9.96838 0.833496C10.6018 0.833496 11.1153 1.3459 11.1153 1.97933L11.1153 3.03564L11.6245 2.52109C11.9577 2.18429 12.5021 2.17981 12.8408 2.51438C13.1786 2.84782 13.1819 3.39166 12.8476 3.72968L11.1152 5.47994L11.1454 8.0057L13.2891 6.75495L13.9244 4.35872C14.0459 3.90111 14.5173 3.62467 14.9764 3.74893C15.4354 3.86978 15.7094 4.34082 15.588 4.79951L15.3954 5.52604L16.2938 5.00182C16.8409 4.68292 17.5438 4.86755 17.8625 5.41361C18.1823 5.95967 17.998 6.66113 17.4507 6.98018L16.5211 7.52266L17.2491 7.71988C17.7078 7.8441 17.9785 8.31643 17.8539 8.77405C17.7502 9.15683 17.4031 9.40855 17.0246 9.40855C16.9501 9.40855 16.8745 9.39848 16.7988 9.37834L14.4074 8.73616L12.2733 10.0002L14.4382 11.2634L16.7997 10.6236C17.2612 10.5015 17.7303 10.77 17.8556 11.2284C17.98 11.686 17.7095 12.1583 17.2509 12.2825L16.5229 12.4797L17.4525 13.0222C17.9989 13.341 18.1815 14.0428 17.8628 14.5871Z" fill="#96B0D6"/>
|
||||
</svg>
|
After (image error) Size: 2.9 KiB |
3
assets/svg/coin_control/gamepad.svg
Normal file
3
assets/svg/coin_control/gamepad.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.0002 14.3397C20.0002 14.1755 19.9859 14.0085 19.9562 13.8404L18.9131 7.22787C18.4814 4.76975 16.3127 3.3335 9.97206 3.3335C3.71894 3.3335 1.52237 4.75412 1.08706 7.22725L0.0439375 13.8397C0.0142906 14.008 0 14.1747 0 14.3391C0 15.9607 1.39313 17.3332 3.17188 17.3332C4.72469 17.3332 6.1025 16.3938 6.59375 15.0007L6.82812 14.3332H13.1719L13.4062 15.0007C13.8975 16.3938 15.2753 17.3332 16.8281 17.3332C18.6064 17.3054 20.0002 15.9616 20.0002 14.3397ZM7.72206 10.0835L6.72331 10.0832L6.72206 11.0835C6.72206 11.496 6.38519 11.8335 5.97269 11.8335C5.56019 11.8335 5.22206 11.496 5.22206 11.0835L5.22321 10.0832L4.22206 10.0835C3.80956 10.0835 3.47269 9.746 3.47269 9.3335C3.47269 8.921 3.80925 8.5835 4.22206 8.5835L5.22331 8.58315L5.22206 7.5835C5.22206 7.171 5.56019 6.8335 5.97269 6.8335C6.38519 6.8335 6.72206 7.171 6.72206 7.5835L6.72321 8.58318L7.72206 8.5835C8.13456 8.5835 8.47269 8.921 8.47269 9.3335C8.47206 9.746 8.16269 10.0835 7.72206 10.0835ZM13.5002 12.0554C12.8099 12.0554 12.2502 11.4954 12.2502 10.8054C12.2502 10.1154 12.8099 9.55537 13.5002 9.55537C14.1905 9.55537 14.7502 10.1154 14.7502 10.8054C14.7502 11.5241 14.1908 12.0554 13.5002 12.0554ZM15.5002 9.05537C14.8099 9.05537 14.2502 8.49537 14.2502 7.80537C14.2502 7.11537 14.8099 6.55537 15.5002 6.55537C16.1905 6.55537 16.7502 7.11537 16.7502 7.80537C16.7502 8.52412 16.1908 9.05537 15.5002 9.05537Z" fill="#8E9192"/>
|
||||
</svg>
|
After (image error) Size: 1.5 KiB |
3
assets/svg/coin_control/selected.svg
Normal file
3
assets/svg/coin_control/selected.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.7179 3.5972C16.0821 3.92551 16.0821 4.45639 15.7179 4.75327L6.28929 13.9739C5.98571 14.3301 5.44286 14.3301 5.10714 13.9739L0.251036 9.22385C-0.0836786 8.92698 -0.0836786 8.3961 0.251036 8.06779C0.585714 7.74297 1.12857 7.74297 1.46321 8.06779L5.71429 12.2275L14.5357 3.5972C14.8714 3.27099 15.4143 3.27099 15.7179 3.5972Z" fill="white"/>
|
||||
</svg>
|
After (image error) Size: 455 B |
3
assets/svg/coin_control/unfrozen.svg
Normal file
3
assets/svg/coin_control/unfrozen.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.27148 8.01188C4.27148 9.27588 6.83529 10.3035 10.0007 10.3035C13.166 10.3035 15.7298 9.27588 15.7298 8.01188C15.7298 6.74788 13.166 5.72021 10.0007 5.72021C6.83529 5.72021 4.27148 6.74788 4.27148 8.01188ZM19.1673 8.29834C19.1673 5.45166 15.0638 3.14209 10.0007 3.14209C4.9375 3.14209 0.833984 5.45166 0.833984 8.29834C0.833984 11.145 4.9375 13.4546 10.0007 13.4546C15.0638 13.4546 19.1673 11.145 19.1673 8.29834ZM3.93454 9.73779C3.50163 9.32601 3.09342 8.74593 3.09342 8.01188C3.09342 7.27783 3.50163 6.69775 3.93454 6.28597C4.36458 5.88135 4.92676 5.56624 5.49251 5.32633C6.72786 4.84652 8.31055 4.57438 9.96842 4.57438C11.6908 4.57438 13.2734 4.84652 14.4766 5.32633C15.0745 5.56624 15.6367 5.88135 16.0664 6.28597C16.4997 6.69775 16.8757 7.27783 16.8757 8.01188C16.8757 8.74593 16.4997 9.32601 16.0664 9.73779C15.6367 10.1424 15.0745 10.4575 14.4766 10.6652C13.2734 11.1772 11.6908 11.4494 10.0007 11.4494C8.31055 11.4494 6.72786 11.1772 5.49251 10.6652C4.92676 10.4575 4.36458 10.1424 3.93454 9.73779ZM19.1673 11.2386C18.6947 11.8008 18.1038 12.2878 17.4486 12.6709V14.9805C18.5299 14.2285 19.1673 13.3047 19.1673 12.277V11.2386ZM16.3027 15.6071V13.3226C15.2858 13.7917 14.1221 14.1462 12.8652 14.361V16.6634C14.1615 16.4521 15.3324 16.0977 16.3027 15.6071ZM9.90337 14.5687C9.3533 14.5689 8.81281 14.5691 8.2819 14.515V16.8138C8.8022 16.8608 9.34137 16.8606 9.89058 16.8604C9.92723 16.8604 9.96392 16.8604 10.0007 16.8604C10.0374 16.8604 10.0741 16.8604 10.1107 16.8604C10.6599 16.8606 11.1991 16.8608 11.7194 16.8138V14.515C11.1885 14.5691 10.648 14.5689 10.0979 14.5687C10.0655 14.5687 10.0331 14.5687 10.0007 14.5687C9.96819 14.5687 9.93576 14.5687 9.90337 14.5687ZM7.13607 16.6634V14.361C5.87923 14.1462 4.71549 13.7917 3.69857 13.3226V15.6071C4.66895 16.0977 5.83984 16.4521 7.13607 16.6634ZM0.833984 12.277C0.833984 13.3047 1.471 14.2285 2.55273 14.9805V12.6709C1.89818 12.2878 1.307 11.8008 0.833984 11.2386V12.277Z" fill="#F7931A"/>
|
||||
</svg>
|
After (image error) Size: 2.1 KiB |
|
@ -1,6 +1,5 @@
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/exceptions/main_db/main_db_exception.dart';
|
||||
import 'package:stackwallet/exceptions/sw_exception.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -161,6 +160,43 @@ class MainDB {
|
|||
await isar.utxos.putAll(utxos);
|
||||
});
|
||||
|
||||
Future<void> updateUTXOs(String walletId, List<UTXO> utxos) async {
|
||||
await isar.writeTxn(() async {
|
||||
final set = utxos.toSet();
|
||||
for (final utxo in utxos) {
|
||||
// check if utxo exists in db and update accordingly
|
||||
final storedUtxo = await isar.utxos
|
||||
.where()
|
||||
.txidWalletIdVoutEqualTo(utxo.txid, utxo.walletId, utxo.vout)
|
||||
.findFirst();
|
||||
|
||||
if (storedUtxo != null) {
|
||||
// update
|
||||
set.remove(utxo);
|
||||
set.add(
|
||||
storedUtxo.copyWith(
|
||||
value: utxo.value,
|
||||
address: utxo.address,
|
||||
blockTime: utxo.blockTime,
|
||||
blockHeight: utxo.blockHeight,
|
||||
blockHash: utxo.blockHash,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await isar.utxos.putAll(set.toList());
|
||||
});
|
||||
}
|
||||
|
||||
Stream<UTXO?> watchUTXO({
|
||||
required Id id,
|
||||
bool fireImmediately = false,
|
||||
}) {
|
||||
return isar.utxos.watchObject(id, fireImmediately: fireImmediately);
|
||||
}
|
||||
|
||||
// transaction notes
|
||||
QueryBuilder<TransactionNote, TransactionNote, QAfterWhereClause>
|
||||
getTransactionNotes(String walletId) =>
|
||||
|
@ -236,10 +272,6 @@ class MainDB {
|
|||
Future<int> updateAddressLabel(AddressLabel addressLabel) async {
|
||||
try {
|
||||
return await isar.writeTxn(() async {
|
||||
final deleted = await isar.addresses.delete(addressLabel.id);
|
||||
if (!deleted) {
|
||||
throw SWException("Failed to delete $addressLabel before updating");
|
||||
}
|
||||
return await isar.addressLabels.put(addressLabel);
|
||||
});
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:stackwallet/exceptions/sw_exception.dart';
|
||||
|
||||
enum ExchangeExceptionType { generic, serializeResponseError }
|
||||
enum ExchangeExceptionType { generic, serializeResponseError, orderNotFound }
|
||||
|
||||
class ExchangeException extends SWException {
|
||||
ExchangeExceptionType type;
|
||||
|
|
|
@ -19,7 +19,7 @@ class Balance {
|
|||
required this.pendingSpendable,
|
||||
});
|
||||
|
||||
Decimal getTotal({bool includeBlocked = false}) => Format.satoshisToAmount(
|
||||
Decimal getTotal({bool includeBlocked = true}) => Format.satoshisToAmount(
|
||||
includeBlocked ? total : total - blockedTotal,
|
||||
coin: coin,
|
||||
);
|
||||
|
@ -39,12 +39,7 @@ class Balance {
|
|||
coin: coin,
|
||||
);
|
||||
|
||||
String toJsonIgnoreCoin() => jsonEncode({
|
||||
"total": total,
|
||||
"spendable": spendable,
|
||||
"blockedTotal": blockedTotal,
|
||||
"pendingSpendable": pendingSpendable,
|
||||
});
|
||||
String toJsonIgnoreCoin() => jsonEncode(toMap()..remove("coin"));
|
||||
|
||||
factory Balance.fromJson(String json, Coin coin) {
|
||||
final decoded = jsonDecode(json);
|
||||
|
@ -56,4 +51,17 @@ class Balance {
|
|||
pendingSpendable: decoded["pendingSpendable"] as int,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
"coin": coin,
|
||||
"total": total,
|
||||
"spendable": spendable,
|
||||
"blockedTotal": blockedTotal,
|
||||
"pendingSpendable": pendingSpendable,
|
||||
};
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return toMap().toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:isar/isar.dart';
|
|||
|
||||
part 'utxo.g.dart';
|
||||
|
||||
@Collection(accessor: "utxos")
|
||||
@Collection(accessor: "utxos", inheritance: false)
|
||||
class UTXO {
|
||||
UTXO({
|
||||
required this.walletId,
|
||||
|
@ -18,6 +18,8 @@ class UTXO {
|
|||
required this.blockHash,
|
||||
required this.blockHeight,
|
||||
required this.blockTime,
|
||||
this.address,
|
||||
this.used,
|
||||
this.otherData,
|
||||
});
|
||||
|
||||
|
@ -26,7 +28,10 @@ class UTXO {
|
|||
@Index()
|
||||
late final String walletId;
|
||||
|
||||
@Index(unique: true, replace: true, composite: [CompositeIndex("walletId")])
|
||||
@Index(unique: true, replace: true, composite: [
|
||||
CompositeIndex("walletId"),
|
||||
CompositeIndex("vout"),
|
||||
])
|
||||
late final String txid;
|
||||
|
||||
late final int vout;
|
||||
|
@ -48,6 +53,10 @@ class UTXO {
|
|||
|
||||
late final int? blockTime;
|
||||
|
||||
late final String? address;
|
||||
|
||||
late final bool? used;
|
||||
|
||||
late final String? otherData;
|
||||
|
||||
int getConfirmations(int currentChainHeight) {
|
||||
|
@ -61,6 +70,40 @@ class UTXO {
|
|||
return confirmations >= minimumConfirms;
|
||||
}
|
||||
|
||||
UTXO copyWith({
|
||||
Id? id,
|
||||
String? walletId,
|
||||
String? txid,
|
||||
int? vout,
|
||||
int? value,
|
||||
String? name,
|
||||
bool? isBlocked,
|
||||
String? blockedReason,
|
||||
bool? isCoinbase,
|
||||
String? blockHash,
|
||||
int? blockHeight,
|
||||
int? blockTime,
|
||||
String? address,
|
||||
bool? used,
|
||||
String? otherData,
|
||||
}) =>
|
||||
UTXO(
|
||||
walletId: walletId ?? this.walletId,
|
||||
txid: txid ?? this.txid,
|
||||
vout: vout ?? this.vout,
|
||||
value: value ?? this.value,
|
||||
name: name ?? this.name,
|
||||
isBlocked: isBlocked ?? this.isBlocked,
|
||||
blockedReason: blockedReason ?? this.blockedReason,
|
||||
isCoinbase: isCoinbase ?? this.isCoinbase,
|
||||
blockHash: blockHash ?? this.blockHash,
|
||||
blockHeight: blockHeight ?? this.blockHeight,
|
||||
blockTime: blockTime ?? this.blockTime,
|
||||
address: address ?? this.address,
|
||||
used: used ?? this.used,
|
||||
otherData: otherData ?? this.otherData,
|
||||
)..id = id ?? this.id;
|
||||
|
||||
@override
|
||||
String toString() => "{ "
|
||||
"id: $id, "
|
||||
|
@ -75,5 +118,20 @@ class UTXO {
|
|||
"blockHash: $blockHash, "
|
||||
"blockHeight: $blockHeight, "
|
||||
"blockTime: $blockTime, "
|
||||
"address: $address, "
|
||||
"used: $used, "
|
||||
"otherData: $otherData, "
|
||||
"}";
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is UTXO &&
|
||||
other.walletId == walletId &&
|
||||
other.txid == txid &&
|
||||
other.vout == vout;
|
||||
}
|
||||
|
||||
@override
|
||||
@ignore
|
||||
int get hashCode => Object.hashAll([walletId, txid, vout]);
|
||||
}
|
||||
|
|
|
@ -17,63 +17,73 @@ const UTXOSchema = CollectionSchema(
|
|||
name: r'UTXO',
|
||||
id: 5934032492047519621,
|
||||
properties: {
|
||||
r'blockHash': PropertySchema(
|
||||
r'address': PropertySchema(
|
||||
id: 0,
|
||||
name: r'address',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'blockHash': PropertySchema(
|
||||
id: 1,
|
||||
name: r'blockHash',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'blockHeight': PropertySchema(
|
||||
id: 1,
|
||||
id: 2,
|
||||
name: r'blockHeight',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'blockTime': PropertySchema(
|
||||
id: 2,
|
||||
id: 3,
|
||||
name: r'blockTime',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'blockedReason': PropertySchema(
|
||||
id: 3,
|
||||
id: 4,
|
||||
name: r'blockedReason',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'isBlocked': PropertySchema(
|
||||
id: 4,
|
||||
id: 5,
|
||||
name: r'isBlocked',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'isCoinbase': PropertySchema(
|
||||
id: 5,
|
||||
id: 6,
|
||||
name: r'isCoinbase',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'name': PropertySchema(
|
||||
id: 6,
|
||||
id: 7,
|
||||
name: r'name',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'otherData': PropertySchema(
|
||||
id: 7,
|
||||
id: 8,
|
||||
name: r'otherData',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'txid': PropertySchema(
|
||||
id: 8,
|
||||
id: 9,
|
||||
name: r'txid',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'used': PropertySchema(
|
||||
id: 10,
|
||||
name: r'used',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'value': PropertySchema(
|
||||
id: 9,
|
||||
id: 11,
|
||||
name: r'value',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'vout': PropertySchema(
|
||||
id: 10,
|
||||
id: 12,
|
||||
name: r'vout',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'walletId': PropertySchema(
|
||||
id: 11,
|
||||
id: 13,
|
||||
name: r'walletId',
|
||||
type: IsarType.string,
|
||||
)
|
||||
|
@ -97,9 +107,9 @@ const UTXOSchema = CollectionSchema(
|
|||
)
|
||||
],
|
||||
),
|
||||
r'txid_walletId': IndexSchema(
|
||||
id: -2771771174176035985,
|
||||
name: r'txid_walletId',
|
||||
r'txid_walletId_vout': IndexSchema(
|
||||
id: -2984264099359759359,
|
||||
name: r'txid_walletId_vout',
|
||||
unique: true,
|
||||
replace: true,
|
||||
properties: [
|
||||
|
@ -112,6 +122,11 @@ const UTXOSchema = CollectionSchema(
|
|||
name: r'walletId',
|
||||
type: IndexType.hash,
|
||||
caseSensitive: true,
|
||||
),
|
||||
IndexPropertySchema(
|
||||
name: r'vout',
|
||||
type: IndexType.value,
|
||||
caseSensitive: false,
|
||||
)
|
||||
],
|
||||
),
|
||||
|
@ -143,6 +158,12 @@ int _uTXOEstimateSize(
|
|||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
var bytesCount = offsets.last;
|
||||
{
|
||||
final value = object.address;
|
||||
if (value != null) {
|
||||
bytesCount += 3 + value.length * 3;
|
||||
}
|
||||
}
|
||||
{
|
||||
final value = object.blockHash;
|
||||
if (value != null) {
|
||||
|
@ -173,18 +194,20 @@ void _uTXOSerialize(
|
|||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
writer.writeString(offsets[0], object.blockHash);
|
||||
writer.writeLong(offsets[1], object.blockHeight);
|
||||
writer.writeLong(offsets[2], object.blockTime);
|
||||
writer.writeString(offsets[3], object.blockedReason);
|
||||
writer.writeBool(offsets[4], object.isBlocked);
|
||||
writer.writeBool(offsets[5], object.isCoinbase);
|
||||
writer.writeString(offsets[6], object.name);
|
||||
writer.writeString(offsets[7], object.otherData);
|
||||
writer.writeString(offsets[8], object.txid);
|
||||
writer.writeLong(offsets[9], object.value);
|
||||
writer.writeLong(offsets[10], object.vout);
|
||||
writer.writeString(offsets[11], object.walletId);
|
||||
writer.writeString(offsets[0], object.address);
|
||||
writer.writeString(offsets[1], object.blockHash);
|
||||
writer.writeLong(offsets[2], object.blockHeight);
|
||||
writer.writeLong(offsets[3], object.blockTime);
|
||||
writer.writeString(offsets[4], object.blockedReason);
|
||||
writer.writeBool(offsets[5], object.isBlocked);
|
||||
writer.writeBool(offsets[6], object.isCoinbase);
|
||||
writer.writeString(offsets[7], object.name);
|
||||
writer.writeString(offsets[8], object.otherData);
|
||||
writer.writeString(offsets[9], object.txid);
|
||||
writer.writeBool(offsets[10], object.used);
|
||||
writer.writeLong(offsets[11], object.value);
|
||||
writer.writeLong(offsets[12], object.vout);
|
||||
writer.writeString(offsets[13], object.walletId);
|
||||
}
|
||||
|
||||
UTXO _uTXODeserialize(
|
||||
|
@ -194,18 +217,20 @@ UTXO _uTXODeserialize(
|
|||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
final object = UTXO(
|
||||
blockHash: reader.readStringOrNull(offsets[0]),
|
||||
blockHeight: reader.readLongOrNull(offsets[1]),
|
||||
blockTime: reader.readLongOrNull(offsets[2]),
|
||||
blockedReason: reader.readStringOrNull(offsets[3]),
|
||||
isBlocked: reader.readBool(offsets[4]),
|
||||
isCoinbase: reader.readBool(offsets[5]),
|
||||
name: reader.readString(offsets[6]),
|
||||
otherData: reader.readStringOrNull(offsets[7]),
|
||||
txid: reader.readString(offsets[8]),
|
||||
value: reader.readLong(offsets[9]),
|
||||
vout: reader.readLong(offsets[10]),
|
||||
walletId: reader.readString(offsets[11]),
|
||||
address: reader.readStringOrNull(offsets[0]),
|
||||
blockHash: reader.readStringOrNull(offsets[1]),
|
||||
blockHeight: reader.readLongOrNull(offsets[2]),
|
||||
blockTime: reader.readLongOrNull(offsets[3]),
|
||||
blockedReason: reader.readStringOrNull(offsets[4]),
|
||||
isBlocked: reader.readBool(offsets[5]),
|
||||
isCoinbase: reader.readBool(offsets[6]),
|
||||
name: reader.readString(offsets[7]),
|
||||
otherData: reader.readStringOrNull(offsets[8]),
|
||||
txid: reader.readString(offsets[9]),
|
||||
used: reader.readBoolOrNull(offsets[10]),
|
||||
value: reader.readLong(offsets[11]),
|
||||
vout: reader.readLong(offsets[12]),
|
||||
walletId: reader.readString(offsets[13]),
|
||||
);
|
||||
object.id = id;
|
||||
return object;
|
||||
|
@ -221,26 +246,30 @@ P _uTXODeserializeProp<P>(
|
|||
case 0:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 1:
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 2:
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
case 3:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
case 4:
|
||||
return (reader.readBool(offset)) as P;
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 5:
|
||||
return (reader.readBool(offset)) as P;
|
||||
case 6:
|
||||
return (reader.readString(offset)) as P;
|
||||
return (reader.readBool(offset)) as P;
|
||||
case 7:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 8:
|
||||
return (reader.readString(offset)) as P;
|
||||
case 8:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 9:
|
||||
return (reader.readLong(offset)) as P;
|
||||
return (reader.readString(offset)) as P;
|
||||
case 10:
|
||||
return (reader.readLong(offset)) as P;
|
||||
return (reader.readBoolOrNull(offset)) as P;
|
||||
case 11:
|
||||
return (reader.readLong(offset)) as P;
|
||||
case 12:
|
||||
return (reader.readLong(offset)) as P;
|
||||
case 13:
|
||||
return (reader.readString(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
|
@ -260,89 +289,91 @@ void _uTXOAttach(IsarCollection<dynamic> col, Id id, UTXO object) {
|
|||
}
|
||||
|
||||
extension UTXOByIndex on IsarCollection<UTXO> {
|
||||
Future<UTXO?> getByTxidWalletId(String txid, String walletId) {
|
||||
return getByIndex(r'txid_walletId', [txid, walletId]);
|
||||
Future<UTXO?> getByTxidWalletIdVout(String txid, String walletId, int vout) {
|
||||
return getByIndex(r'txid_walletId_vout', [txid, walletId, vout]);
|
||||
}
|
||||
|
||||
UTXO? getByTxidWalletIdSync(String txid, String walletId) {
|
||||
return getByIndexSync(r'txid_walletId', [txid, walletId]);
|
||||
UTXO? getByTxidWalletIdVoutSync(String txid, String walletId, int vout) {
|
||||
return getByIndexSync(r'txid_walletId_vout', [txid, walletId, vout]);
|
||||
}
|
||||
|
||||
Future<bool> deleteByTxidWalletId(String txid, String walletId) {
|
||||
return deleteByIndex(r'txid_walletId', [txid, walletId]);
|
||||
Future<bool> deleteByTxidWalletIdVout(
|
||||
String txid, String walletId, int vout) {
|
||||
return deleteByIndex(r'txid_walletId_vout', [txid, walletId, vout]);
|
||||
}
|
||||
|
||||
bool deleteByTxidWalletIdSync(String txid, String walletId) {
|
||||
return deleteByIndexSync(r'txid_walletId', [txid, walletId]);
|
||||
bool deleteByTxidWalletIdVoutSync(String txid, String walletId, int vout) {
|
||||
return deleteByIndexSync(r'txid_walletId_vout', [txid, walletId, vout]);
|
||||
}
|
||||
|
||||
Future<List<UTXO?>> getAllByTxidWalletId(
|
||||
List<String> txidValues, List<String> walletIdValues) {
|
||||
Future<List<UTXO?>> getAllByTxidWalletIdVout(List<String> txidValues,
|
||||
List<String> walletIdValues, List<int> voutValues) {
|
||||
final len = txidValues.length;
|
||||
assert(walletIdValues.length == len,
|
||||
assert(walletIdValues.length == len && voutValues.length == len,
|
||||
'All index values must have the same length');
|
||||
final values = <List<dynamic>>[];
|
||||
for (var i = 0; i < len; i++) {
|
||||
values.add([txidValues[i], walletIdValues[i]]);
|
||||
values.add([txidValues[i], walletIdValues[i], voutValues[i]]);
|
||||
}
|
||||
|
||||
return getAllByIndex(r'txid_walletId', values);
|
||||
return getAllByIndex(r'txid_walletId_vout', values);
|
||||
}
|
||||
|
||||
List<UTXO?> getAllByTxidWalletIdSync(
|
||||
List<String> txidValues, List<String> walletIdValues) {
|
||||
List<UTXO?> getAllByTxidWalletIdVoutSync(List<String> txidValues,
|
||||
List<String> walletIdValues, List<int> voutValues) {
|
||||
final len = txidValues.length;
|
||||
assert(walletIdValues.length == len,
|
||||
assert(walletIdValues.length == len && voutValues.length == len,
|
||||
'All index values must have the same length');
|
||||
final values = <List<dynamic>>[];
|
||||
for (var i = 0; i < len; i++) {
|
||||
values.add([txidValues[i], walletIdValues[i]]);
|
||||
values.add([txidValues[i], walletIdValues[i], voutValues[i]]);
|
||||
}
|
||||
|
||||
return getAllByIndexSync(r'txid_walletId', values);
|
||||
return getAllByIndexSync(r'txid_walletId_vout', values);
|
||||
}
|
||||
|
||||
Future<int> deleteAllByTxidWalletId(
|
||||
List<String> txidValues, List<String> walletIdValues) {
|
||||
Future<int> deleteAllByTxidWalletIdVout(List<String> txidValues,
|
||||
List<String> walletIdValues, List<int> voutValues) {
|
||||
final len = txidValues.length;
|
||||
assert(walletIdValues.length == len,
|
||||
assert(walletIdValues.length == len && voutValues.length == len,
|
||||
'All index values must have the same length');
|
||||
final values = <List<dynamic>>[];
|
||||
for (var i = 0; i < len; i++) {
|
||||
values.add([txidValues[i], walletIdValues[i]]);
|
||||
values.add([txidValues[i], walletIdValues[i], voutValues[i]]);
|
||||
}
|
||||
|
||||
return deleteAllByIndex(r'txid_walletId', values);
|
||||
return deleteAllByIndex(r'txid_walletId_vout', values);
|
||||
}
|
||||
|
||||
int deleteAllByTxidWalletIdSync(
|
||||
List<String> txidValues, List<String> walletIdValues) {
|
||||
int deleteAllByTxidWalletIdVoutSync(List<String> txidValues,
|
||||
List<String> walletIdValues, List<int> voutValues) {
|
||||
final len = txidValues.length;
|
||||
assert(walletIdValues.length == len,
|
||||
assert(walletIdValues.length == len && voutValues.length == len,
|
||||
'All index values must have the same length');
|
||||
final values = <List<dynamic>>[];
|
||||
for (var i = 0; i < len; i++) {
|
||||
values.add([txidValues[i], walletIdValues[i]]);
|
||||
values.add([txidValues[i], walletIdValues[i], voutValues[i]]);
|
||||
}
|
||||
|
||||
return deleteAllByIndexSync(r'txid_walletId', values);
|
||||
return deleteAllByIndexSync(r'txid_walletId_vout', values);
|
||||
}
|
||||
|
||||
Future<Id> putByTxidWalletId(UTXO object) {
|
||||
return putByIndex(r'txid_walletId', object);
|
||||
Future<Id> putByTxidWalletIdVout(UTXO object) {
|
||||
return putByIndex(r'txid_walletId_vout', object);
|
||||
}
|
||||
|
||||
Id putByTxidWalletIdSync(UTXO object, {bool saveLinks = true}) {
|
||||
return putByIndexSync(r'txid_walletId', object, saveLinks: saveLinks);
|
||||
Id putByTxidWalletIdVoutSync(UTXO object, {bool saveLinks = true}) {
|
||||
return putByIndexSync(r'txid_walletId_vout', object, saveLinks: saveLinks);
|
||||
}
|
||||
|
||||
Future<List<Id>> putAllByTxidWalletId(List<UTXO> objects) {
|
||||
return putAllByIndex(r'txid_walletId', objects);
|
||||
Future<List<Id>> putAllByTxidWalletIdVout(List<UTXO> objects) {
|
||||
return putAllByIndex(r'txid_walletId_vout', objects);
|
||||
}
|
||||
|
||||
List<Id> putAllByTxidWalletIdSync(List<UTXO> objects,
|
||||
List<Id> putAllByTxidWalletIdVoutSync(List<UTXO> objects,
|
||||
{bool saveLinks = true}) {
|
||||
return putAllByIndexSync(r'txid_walletId', objects, saveLinks: saveLinks);
|
||||
return putAllByIndexSync(r'txid_walletId_vout', objects,
|
||||
saveLinks: saveLinks);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,29 +503,29 @@ extension UTXOQueryWhere on QueryBuilder<UTXO, UTXO, QWhereClause> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> txidEqualToAnyWalletId(
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> txidEqualToAnyWalletIdVout(
|
||||
String txid) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||
indexName: r'txid_walletId',
|
||||
indexName: r'txid_walletId_vout',
|
||||
value: [txid],
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> txidNotEqualToAnyWalletId(
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> txidNotEqualToAnyWalletIdVout(
|
||||
String txid) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
if (query.whereSort == Sort.asc) {
|
||||
return query
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId',
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [],
|
||||
upper: [txid],
|
||||
includeUpper: false,
|
||||
))
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId',
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid],
|
||||
includeLower: false,
|
||||
upper: [],
|
||||
|
@ -502,13 +533,13 @@ extension UTXOQueryWhere on QueryBuilder<UTXO, UTXO, QWhereClause> {
|
|||
} else {
|
||||
return query
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId',
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid],
|
||||
includeLower: false,
|
||||
upper: [],
|
||||
))
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId',
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [],
|
||||
upper: [txid],
|
||||
includeUpper: false,
|
||||
|
@ -517,29 +548,29 @@ extension UTXOQueryWhere on QueryBuilder<UTXO, UTXO, QWhereClause> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> txidWalletIdEqualTo(
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> txidWalletIdEqualToAnyVout(
|
||||
String txid, String walletId) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||
indexName: r'txid_walletId',
|
||||
indexName: r'txid_walletId_vout',
|
||||
value: [txid, walletId],
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> txidEqualToWalletIdNotEqualTo(
|
||||
String txid, String walletId) {
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause>
|
||||
txidEqualToWalletIdNotEqualToAnyVout(String txid, String walletId) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
if (query.whereSort == Sort.asc) {
|
||||
return query
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId',
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid],
|
||||
upper: [txid, walletId],
|
||||
includeUpper: false,
|
||||
))
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId',
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid, walletId],
|
||||
includeLower: false,
|
||||
upper: [txid],
|
||||
|
@ -547,13 +578,13 @@ extension UTXOQueryWhere on QueryBuilder<UTXO, UTXO, QWhereClause> {
|
|||
} else {
|
||||
return query
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId',
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid, walletId],
|
||||
includeLower: false,
|
||||
upper: [txid],
|
||||
))
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId',
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid],
|
||||
upper: [txid, walletId],
|
||||
includeUpper: false,
|
||||
|
@ -562,6 +593,103 @@ extension UTXOQueryWhere on QueryBuilder<UTXO, UTXO, QWhereClause> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> txidWalletIdVoutEqualTo(
|
||||
String txid, String walletId, int vout) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||
indexName: r'txid_walletId_vout',
|
||||
value: [txid, walletId, vout],
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> txidWalletIdEqualToVoutNotEqualTo(
|
||||
String txid, String walletId, int vout) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
if (query.whereSort == Sort.asc) {
|
||||
return query
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid, walletId],
|
||||
upper: [txid, walletId, vout],
|
||||
includeUpper: false,
|
||||
))
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid, walletId, vout],
|
||||
includeLower: false,
|
||||
upper: [txid, walletId],
|
||||
));
|
||||
} else {
|
||||
return query
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid, walletId, vout],
|
||||
includeLower: false,
|
||||
upper: [txid, walletId],
|
||||
))
|
||||
.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid, walletId],
|
||||
upper: [txid, walletId, vout],
|
||||
includeUpper: false,
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause>
|
||||
txidWalletIdEqualToVoutGreaterThan(
|
||||
String txid,
|
||||
String walletId,
|
||||
int vout, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid, walletId, vout],
|
||||
includeLower: include,
|
||||
upper: [txid, walletId],
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> txidWalletIdEqualToVoutLessThan(
|
||||
String txid,
|
||||
String walletId,
|
||||
int vout, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid, walletId],
|
||||
upper: [txid, walletId, vout],
|
||||
includeUpper: include,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> txidWalletIdEqualToVoutBetween(
|
||||
String txid,
|
||||
String walletId,
|
||||
int lowerVout,
|
||||
int upperVout, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IndexWhereClause.between(
|
||||
indexName: r'txid_walletId_vout',
|
||||
lower: [txid, walletId, lowerVout],
|
||||
includeLower: includeLower,
|
||||
upper: [txid, walletId, upperVout],
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> isBlockedEqualTo(bool isBlocked) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IndexWhereClause.equalTo(
|
||||
|
@ -608,6 +736,150 @@ extension UTXOQueryWhere on QueryBuilder<UTXO, UTXO, QWhereClause> {
|
|||
}
|
||||
|
||||
extension UTXOQueryFilter on QueryBuilder<UTXO, UTXO, QFilterCondition> {
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> addressIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'address',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> addressIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'address',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> addressEqualTo(
|
||||
String? value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'address',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> addressGreaterThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'address',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> addressLessThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'address',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> addressBetween(
|
||||
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'address',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> addressStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'address',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> addressEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'address',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> addressContains(String value,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'address',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> addressMatches(String pattern,
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'address',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> addressIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'address',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> addressIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'address',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> blockHashIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
|
@ -1510,6 +1782,31 @@ extension UTXOQueryFilter on QueryBuilder<UTXO, UTXO, QFilterCondition> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> usedIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'used',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> usedIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'used',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> usedEqualTo(bool? value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'used',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterFilterCondition> valueEqualTo(int value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
|
@ -1749,6 +2046,18 @@ extension UTXOQueryObject on QueryBuilder<UTXO, UTXO, QFilterCondition> {}
|
|||
extension UTXOQueryLinks on QueryBuilder<UTXO, UTXO, QFilterCondition> {}
|
||||
|
||||
extension UTXOQuerySortBy on QueryBuilder<UTXO, UTXO, QSortBy> {
|
||||
QueryBuilder<UTXO, UTXO, QAfterSortBy> sortByAddress() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'address', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterSortBy> sortByAddressDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'address', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterSortBy> sortByBlockHash() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'blockHash', Sort.asc);
|
||||
|
@ -1857,6 +2166,18 @@ extension UTXOQuerySortBy on QueryBuilder<UTXO, UTXO, QSortBy> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterSortBy> sortByUsed() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'used', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterSortBy> sortByUsedDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'used', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterSortBy> sortByValue() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'value', Sort.asc);
|
||||
|
@ -1895,6 +2216,18 @@ extension UTXOQuerySortBy on QueryBuilder<UTXO, UTXO, QSortBy> {
|
|||
}
|
||||
|
||||
extension UTXOQuerySortThenBy on QueryBuilder<UTXO, UTXO, QSortThenBy> {
|
||||
QueryBuilder<UTXO, UTXO, QAfterSortBy> thenByAddress() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'address', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterSortBy> thenByAddressDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'address', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterSortBy> thenByBlockHash() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'blockHash', Sort.asc);
|
||||
|
@ -2015,6 +2348,18 @@ extension UTXOQuerySortThenBy on QueryBuilder<UTXO, UTXO, QSortThenBy> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterSortBy> thenByUsed() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'used', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterSortBy> thenByUsedDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'used', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QAfterSortBy> thenByValue() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'value', Sort.asc);
|
||||
|
@ -2053,6 +2398,13 @@ extension UTXOQuerySortThenBy on QueryBuilder<UTXO, UTXO, QSortThenBy> {
|
|||
}
|
||||
|
||||
extension UTXOQueryWhereDistinct on QueryBuilder<UTXO, UTXO, QDistinct> {
|
||||
QueryBuilder<UTXO, UTXO, QDistinct> distinctByAddress(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'address', caseSensitive: caseSensitive);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QDistinct> distinctByBlockHash(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
@ -2113,6 +2465,12 @@ extension UTXOQueryWhereDistinct on QueryBuilder<UTXO, UTXO, QDistinct> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QDistinct> distinctByUsed() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'used');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, UTXO, QDistinct> distinctByValue() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'value');
|
||||
|
@ -2140,6 +2498,12 @@ extension UTXOQueryProperty on QueryBuilder<UTXO, UTXO, QQueryProperty> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, String?, QQueryOperations> addressProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'address');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, String?, QQueryOperations> blockHashProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'blockHash');
|
||||
|
@ -2194,6 +2558,12 @@ extension UTXOQueryProperty on QueryBuilder<UTXO, UTXO, QQueryProperty> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, bool?, QQueryOperations> usedProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'used');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<UTXO, int, QQueryOperations> valueProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'value');
|
||||
|
|
439
lib/pages/coin_control/coin_control_view.dart
Normal file
439
lib/pages/coin_control/coin_control_view.dart
Normal file
|
@ -0,0 +1,439 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages/coin_control/utxo_card.dart';
|
||||
import 'package:stackwallet/pages/coin_control/utxo_details_view.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/services/mixins/coin_control_interface.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/theme/stack_colors.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/icon_widgets/x_icon.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:stackwallet/widgets/toggle.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
enum CoinControlViewType {
|
||||
manage,
|
||||
use;
|
||||
}
|
||||
|
||||
class CoinControlView extends ConsumerStatefulWidget {
|
||||
const CoinControlView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.type,
|
||||
this.requestedTotal,
|
||||
this.selectedUTXOs,
|
||||
}) : super(key: key);
|
||||
|
||||
static const routeName = "/coinControl";
|
||||
|
||||
final String walletId;
|
||||
final CoinControlViewType type;
|
||||
final int? requestedTotal;
|
||||
final Set<UTXO>? selectedUTXOs;
|
||||
|
||||
@override
|
||||
ConsumerState<CoinControlView> createState() => _CoinControlViewState();
|
||||
}
|
||||
|
||||
class _CoinControlViewState extends ConsumerState<CoinControlView> {
|
||||
bool _showBlocked = false;
|
||||
|
||||
final Set<UTXO> _selectedAvailable = {};
|
||||
final Set<UTXO> _selectedBlocked = {};
|
||||
|
||||
Future<void> _refreshBalance() async {
|
||||
final coinControlInterface = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as CoinControlInterface;
|
||||
await coinControlInterface.refreshBalance(notify: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (widget.selectedUTXOs != null) {
|
||||
_selectedAvailable.addAll(widget.selectedUTXOs!);
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
final coin = ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value
|
||||
.getManager(
|
||||
widget.walletId,
|
||||
)
|
||||
.coin,
|
||||
),
|
||||
);
|
||||
|
||||
final currentChainHeight = ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value
|
||||
.getManager(
|
||||
widget.walletId,
|
||||
)
|
||||
.currentHeight,
|
||||
),
|
||||
);
|
||||
|
||||
final ids = MainDB.instance
|
||||
.getUTXOs(widget.walletId)
|
||||
.filter()
|
||||
.isBlockedEqualTo(_showBlocked)
|
||||
.and()
|
||||
.group(
|
||||
(q) => q.usedIsNull().or().usedEqualTo(false),
|
||||
)
|
||||
.idProperty()
|
||||
.findAllSync();
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
unawaited(_refreshBalance());
|
||||
Navigator.of(context).pop(
|
||||
widget.type == CoinControlViewType.use ? _selectedAvailable : null);
|
||||
return false;
|
||||
},
|
||||
child: Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: widget.type == CoinControlViewType.use &&
|
||||
_selectedAvailable.isNotEmpty
|
||||
? AppBarIconButton(
|
||||
icon: XIcon(
|
||||
width: 24,
|
||||
height: 24,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_selectedAvailable.clear();
|
||||
});
|
||||
},
|
||||
)
|
||||
: AppBarBackButton(
|
||||
onPressed: () {
|
||||
unawaited(_refreshBalance());
|
||||
Navigator.of(context).pop(
|
||||
widget.type == CoinControlViewType.use
|
||||
? _selectedAvailable
|
||||
: null);
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Coin control",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
titleSpacing: 0,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"This option allows you to control, freeze, and utilize "
|
||||
"outputs at your discretion. Tap the output circle to "
|
||||
"select.",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Toggle(
|
||||
key: UniqueKey(),
|
||||
onColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
onText: "Available outputs",
|
||||
offColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
offText: "Frozen outputs",
|
||||
isOn: _showBlocked,
|
||||
onValueChanged: (value) {
|
||||
setState(() {
|
||||
_showBlocked = value;
|
||||
});
|
||||
},
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
itemCount: ids.length,
|
||||
separatorBuilder: (context, _) => const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final utxo = MainDB.instance.isar.utxos
|
||||
.where()
|
||||
.idEqualTo(ids[index])
|
||||
.findFirstSync()!;
|
||||
|
||||
final isSelected = _showBlocked
|
||||
? _selectedBlocked.contains(utxo)
|
||||
: _selectedAvailable.contains(utxo);
|
||||
|
||||
return UtxoCard(
|
||||
key: Key(
|
||||
"${utxo.walletId}_${utxo.id}_$isSelected"),
|
||||
walletId: widget.walletId,
|
||||
utxo: utxo,
|
||||
canSelect: widget.type ==
|
||||
CoinControlViewType.manage ||
|
||||
(widget.type == CoinControlViewType.use &&
|
||||
!_showBlocked &&
|
||||
utxo.isConfirmed(
|
||||
currentChainHeight,
|
||||
coin.requiredConfirmations,
|
||||
)),
|
||||
initialSelectedState: isSelected,
|
||||
onSelectedChanged: (value) {
|
||||
if (value) {
|
||||
_showBlocked
|
||||
? _selectedBlocked.add(utxo)
|
||||
: _selectedAvailable.add(utxo);
|
||||
} else {
|
||||
_showBlocked
|
||||
? _selectedBlocked.remove(utxo)
|
||||
: _selectedAvailable.remove(utxo);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
onPressed: () async {
|
||||
final result =
|
||||
await Navigator.of(context).pushNamed(
|
||||
UtxoDetailsView.routeName,
|
||||
arguments: Tuple2(
|
||||
utxo.id,
|
||||
widget.walletId,
|
||||
),
|
||||
);
|
||||
if (mounted && result == "refresh") {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (((_showBlocked && _selectedBlocked.isNotEmpty) ||
|
||||
(!_showBlocked && _selectedAvailable.isNotEmpty)) &&
|
||||
widget.type == CoinControlViewType.manage)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar,
|
||||
boxShadow: [
|
||||
Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.standardBoxShadow,
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: SecondaryButton(
|
||||
label: _showBlocked ? "Unfreeze" : "Freeze",
|
||||
onPressed: () async {
|
||||
if (_showBlocked) {
|
||||
await MainDB.instance.putUTXOs(_selectedBlocked
|
||||
.map(
|
||||
(e) => e.copyWith(
|
||||
isBlocked: false,
|
||||
),
|
||||
)
|
||||
.toList());
|
||||
_selectedBlocked.clear();
|
||||
} else {
|
||||
await MainDB.instance.putUTXOs(_selectedAvailable
|
||||
.map(
|
||||
(e) => e.copyWith(
|
||||
isBlocked: true,
|
||||
),
|
||||
)
|
||||
.toList());
|
||||
_selectedAvailable.clear();
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!_showBlocked && widget.type == CoinControlViewType.use)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar,
|
||||
boxShadow: [
|
||||
Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.standardBoxShadow,
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Selected amount",
|
||||
style: STextStyles.w600_14(context),
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
int selectedSum =
|
||||
_selectedAvailable.isEmpty
|
||||
? 0
|
||||
: _selectedAvailable
|
||||
.map((e) => e.value)
|
||||
.reduce(
|
||||
(value, element) =>
|
||||
value += element,
|
||||
);
|
||||
return Text(
|
||||
"${Format.satoshisToAmount(
|
||||
selectedSum,
|
||||
coin: coin,
|
||||
).toStringAsFixed(
|
||||
coin.decimals,
|
||||
)} ${coin.ticker}",
|
||||
style: widget.requestedTotal == null
|
||||
? STextStyles.w600_14(context)
|
||||
: STextStyles.w600_14(context).copyWith(
|
||||
color: selectedSum >=
|
||||
widget
|
||||
.requestedTotal!
|
||||
? Theme.of(context)
|
||||
.extension<
|
||||
StackColors>()!
|
||||
.accentColorGreen
|
||||
: Theme.of(context)
|
||||
.extension<
|
||||
StackColors>()!
|
||||
.accentColorRed),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.requestedTotal != null)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 1.5,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar,
|
||||
),
|
||||
if (widget.requestedTotal != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Amount to send",
|
||||
style: STextStyles.w600_14(context),
|
||||
),
|
||||
Text(
|
||||
"${Format.satoshisToAmount(
|
||||
widget.requestedTotal!,
|
||||
coin: coin,
|
||||
).toStringAsFixed(
|
||||
coin.decimals,
|
||||
)} ${coin.ticker}",
|
||||
style: STextStyles.w600_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Use coins",
|
||||
enabled: _selectedAvailable.isNotEmpty,
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop(
|
||||
_selectedAvailable,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
161
lib/pages/coin_control/utxo_card.dart
Normal file
161
lib/pages/coin_control/utxo_card.dart
Normal file
|
@ -0,0 +1,161 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.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/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/utxo_status_icon.dart';
|
||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||
|
||||
class UtxoCard extends ConsumerStatefulWidget {
|
||||
const UtxoCard({
|
||||
Key? key,
|
||||
required this.utxo,
|
||||
required this.walletId,
|
||||
required this.onSelectedChanged,
|
||||
required this.initialSelectedState,
|
||||
required this.canSelect,
|
||||
this.onPressed,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
final UTXO utxo;
|
||||
final void Function(bool) onSelectedChanged;
|
||||
final bool initialSelectedState;
|
||||
final VoidCallback? onPressed;
|
||||
final bool canSelect;
|
||||
|
||||
@override
|
||||
ConsumerState<UtxoCard> createState() => _UtxoCardState();
|
||||
}
|
||||
|
||||
class _UtxoCardState extends ConsumerState<UtxoCard> {
|
||||
late final UTXO utxo;
|
||||
|
||||
late bool _selected;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_selected = widget.initialSelectedState;
|
||||
utxo = widget.utxo;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
final coin = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId).coin));
|
||||
|
||||
final currentChainHeight = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId).currentHeight));
|
||||
|
||||
String? label;
|
||||
if (utxo.address != null) {
|
||||
label = MainDB.instance.isar.addressLabels
|
||||
.where()
|
||||
.addressStringWalletIdEqualTo(utxo.address!, widget.walletId)
|
||||
.findFirstSync()
|
||||
?.value;
|
||||
|
||||
if (label != null && label.isEmpty) {
|
||||
label = null;
|
||||
}
|
||||
}
|
||||
|
||||
return ConditionalParent(
|
||||
condition: widget.onPressed != null,
|
||||
builder: (child) => MaterialButton(
|
||||
padding: const EdgeInsets.all(0),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
elevation: 0,
|
||||
disabledElevation: 0,
|
||||
hoverElevation: 0,
|
||||
focusElevation: 0,
|
||||
highlightElevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(Constants.size.circularBorderRadius),
|
||||
),
|
||||
onPressed: widget.onPressed,
|
||||
child: child,
|
||||
),
|
||||
child: RoundedContainer(
|
||||
color: widget.onPressed == null
|
||||
? Theme.of(context).extension<StackColors>()!.popupBG
|
||||
: Colors.transparent,
|
||||
child: Row(
|
||||
children: [
|
||||
ConditionalParent(
|
||||
condition: widget.canSelect,
|
||||
builder: (child) => GestureDetector(
|
||||
onTap: () {
|
||||
_selected = !_selected;
|
||||
widget.onSelectedChanged(_selected);
|
||||
setState(() {});
|
||||
},
|
||||
child: child,
|
||||
),
|
||||
child: UTXOStatusIcon(
|
||||
blocked: utxo.isBlocked,
|
||||
status: utxo.isConfirmed(
|
||||
currentChainHeight,
|
||||
coin.requiredConfirmations,
|
||||
)
|
||||
? UTXOStatusIconStatus.confirmed
|
||||
: UTXOStatusIconStatus.unconfirmed,
|
||||
background: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
selected: _selected,
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
"${Format.satoshisToAmount(
|
||||
utxo.value,
|
||||
coin: coin,
|
||||
).toStringAsFixed(coin.decimals)} ${coin.ticker}",
|
||||
style: STextStyles.w600_14(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
label ?? utxo.address ?? utxo.txid,
|
||||
style: STextStyles.w500_12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
417
lib/pages/coin_control/utxo_details_view.dart
Normal file
417
lib/pages/coin_control/utxo_details_view.dart
Normal file
|
@ -0,0 +1,417 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.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/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/simple_edit_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class UtxoDetailsView extends ConsumerStatefulWidget {
|
||||
const UtxoDetailsView({
|
||||
Key? key,
|
||||
required this.utxoId,
|
||||
required this.walletId,
|
||||
}) : super(key: key);
|
||||
|
||||
static const routeName = "/utxoDetails";
|
||||
|
||||
final Id utxoId;
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<UtxoDetailsView> createState() => _UtxoDetailsViewState();
|
||||
}
|
||||
|
||||
class _UtxoDetailsViewState extends ConsumerState<UtxoDetailsView> {
|
||||
static const double _spacing = 12;
|
||||
|
||||
late Stream<UTXO?> streamUTXO;
|
||||
UTXO? utxo;
|
||||
|
||||
Stream<AddressLabel?>? streamLabel;
|
||||
AddressLabel? label;
|
||||
|
||||
bool _popWithRefresh = false;
|
||||
|
||||
Future<void> _toggleFreeze() async {
|
||||
_popWithRefresh = true;
|
||||
await MainDB.instance.putUTXO(utxo!.copyWith(isBlocked: !utxo!.isBlocked));
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
utxo = MainDB.instance.isar.utxos
|
||||
.where()
|
||||
.idEqualTo(widget.utxoId)
|
||||
.findFirstSync()!;
|
||||
|
||||
streamUTXO = MainDB.instance.watchUTXO(id: widget.utxoId);
|
||||
|
||||
if (utxo?.address != null) {
|
||||
label = MainDB.instance.getAddressLabelSync(
|
||||
widget.walletId,
|
||||
utxo!.address!,
|
||||
);
|
||||
|
||||
if (label != null) {
|
||||
streamLabel = MainDB.instance.watchAddressLabel(id: label!.id);
|
||||
}
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final coin = ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value.getManager(widget.walletId).coin,
|
||||
),
|
||||
);
|
||||
|
||||
final currentHeight = ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value.getManager(widget.walletId).currentHeight,
|
||||
),
|
||||
);
|
||||
|
||||
final confirmed = utxo!.isConfirmed(
|
||||
currentHeight,
|
||||
coin.requiredConfirmations,
|
||||
);
|
||||
|
||||
return ConditionalParent(
|
||||
condition: !Util.isDesktop,
|
||||
builder: (child) => Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(_popWithRefresh ? "refresh" : null);
|
||||
},
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: StreamBuilder<UTXO?>(
|
||||
stream: streamUTXO,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
utxo = snapshot.data!;
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"${Format.satoshisToAmount(
|
||||
utxo!.value,
|
||||
coin: coin,
|
||||
).toStringAsFixed(
|
||||
coin.decimals,
|
||||
)} ${coin.ticker}",
|
||||
style: STextStyles.pageTitleH2(context),
|
||||
),
|
||||
Text(
|
||||
utxo!.isBlocked
|
||||
? "Frozen"
|
||||
: confirmed
|
||||
? "Available"
|
||||
: "Unconfirmed",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: utxo!.isBlocked
|
||||
? const Color(0xFF7FA2D4) // todo theme
|
||||
: confirmed
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorGreen
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorYellow,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (label != null && label!.value.isNotEmpty)
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
if (label != null && label!.value.isNotEmpty)
|
||||
RoundedWhiteContainer(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Address label",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
SimpleCopyButton(
|
||||
data: label!.value,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
label!.value,
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Address",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
SimpleCopyButton(
|
||||
data: utxo!.address!,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
utxo!.address!,
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Transaction ID",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
SimpleCopyButton(
|
||||
data: utxo!.txid,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
utxo!.txid,
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Confirmations",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
"${utxo!.getConfirmations(currentHeight)}",
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Note",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
SimpleEditButton(
|
||||
editValue: utxo!.name,
|
||||
editLabel: "note",
|
||||
onValueChanged: (newName) {
|
||||
MainDB.instance.putUTXO(
|
||||
utxo!.copyWith(
|
||||
name: newName,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
utxo!.name,
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
if (utxo!.isBlocked)
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
RoundedWhiteContainer(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Freeze reason",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
SimpleEditButton(
|
||||
editValue: utxo!.blockedReason ?? "",
|
||||
editLabel: "freeze reason",
|
||||
onValueChanged: (newReason) {
|
||||
MainDB.instance.putUTXO(
|
||||
utxo!.copyWith(
|
||||
blockedReason: newReason,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
utxo!.blockedReason ?? "",
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
SecondaryButton(
|
||||
label: utxo!.isBlocked ? "Unfreeze" : "Freeze",
|
||||
onPressed: _toggleFreeze,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -377,12 +377,46 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
padding: isDesktop
|
||||
? const EdgeInsets.all(16)
|
||||
: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Status",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Status",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
if (trade.exchangeName ==
|
||||
MajesticBankExchange.exchangeName &&
|
||||
trade.status == "Completed")
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => const StackOkDialog(
|
||||
title: "Trade Info",
|
||||
message:
|
||||
"Majestic Bank does not store order data indefinitely",
|
||||
),
|
||||
);
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.circleInfo,
|
||||
height: 20,
|
||||
width: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemIcons,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
|
@ -395,8 +429,6 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
.colorForStatus(trade.status),
|
||||
),
|
||||
),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -636,11 +668,15 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
text: address,
|
||||
),
|
||||
);
|
||||
unawaited(showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
));
|
||||
if (mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
|
@ -1091,11 +1127,15 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
onTap: () async {
|
||||
final data = ClipboardData(text: trade.tradeId);
|
||||
await clipboard.setData(data);
|
||||
unawaited(showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
));
|
||||
if (mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
|
|
204
lib/pages/generic/single_field_edit_view.dart
Normal file
204
lib/pages/generic/single_field_edit_view.dart
Normal file
|
@ -0,0 +1,204 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_native_splash/cli_commands.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.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/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||
|
||||
class SingleFieldEditView extends StatefulWidget {
|
||||
const SingleFieldEditView({
|
||||
Key? key,
|
||||
required this.initialValue,
|
||||
required this.label,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/singleFieldEdit";
|
||||
|
||||
final String initialValue;
|
||||
final String label;
|
||||
|
||||
@override
|
||||
State<SingleFieldEditView> createState() => _SingleFieldEditViewState();
|
||||
}
|
||||
|
||||
class _SingleFieldEditViewState extends State<SingleFieldEditView> {
|
||||
late final TextEditingController _textController;
|
||||
final _textFocusNode = FocusNode();
|
||||
|
||||
late final bool isDesktop;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isDesktop = Util.isDesktop;
|
||||
_textController = TextEditingController()..text = widget.initialValue;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textController.dispose();
|
||||
_textFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Edit ${widget.label}",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (!isDesktop)
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
if (isDesktop)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
bottom: 12,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Edit ${widget.label}",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: isDesktop
|
||||
? const EdgeInsets.symmetric(
|
||||
horizontal: 32,
|
||||
)
|
||||
: const EdgeInsets.all(0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: Util.isDesktop ? false : true,
|
||||
enableSuggestions: Util.isDesktop ? false : true,
|
||||
controller: _textController,
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveText,
|
||||
height: 1.8,
|
||||
)
|
||||
: STextStyles.field(context),
|
||||
focusNode: _textFocusNode,
|
||||
decoration: standardInputDecoration(
|
||||
widget.label.capitalize(),
|
||||
_textFocusNode,
|
||||
context,
|
||||
desktopMed: isDesktop,
|
||||
).copyWith(
|
||||
contentPadding: isDesktop
|
||||
? const EdgeInsets.only(
|
||||
left: 16,
|
||||
top: 11,
|
||||
bottom: 12,
|
||||
right: 5,
|
||||
)
|
||||
: null,
|
||||
suffixIcon: _textController.text.isNotEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
children: [
|
||||
TextFieldIconButton(
|
||||
child: const XIcon(),
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
_textController.text = "";
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// if (!isDesktop)
|
||||
const Spacer(),
|
||||
|
||||
ConditionalParent(
|
||||
condition: isDesktop,
|
||||
builder: (child) => Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: child,
|
||||
),
|
||||
child: PrimaryButton(
|
||||
label: "Save",
|
||||
onPressed: () {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop(_textController.text);
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -21,13 +21,13 @@ import 'package:stackwallet/widgets/rounded_white_container.dart';
|
|||
class AddressCard extends StatefulWidget {
|
||||
const AddressCard({
|
||||
Key? key,
|
||||
required this.address,
|
||||
required this.addressId,
|
||||
required this.walletId,
|
||||
required this.coin,
|
||||
this.clipboard = const ClipboardWrapper(),
|
||||
}) : super(key: key);
|
||||
|
||||
final Address address;
|
||||
final int addressId;
|
||||
final String walletId;
|
||||
final Coin coin;
|
||||
final ClipboardInterface clipboard;
|
||||
|
@ -38,18 +38,23 @@ class AddressCard extends StatefulWidget {
|
|||
|
||||
class _AddressCardState extends State<AddressCard> {
|
||||
late Stream<AddressLabel?> stream;
|
||||
late final Address address;
|
||||
|
||||
AddressLabel? label;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
label = MainDB.instance
|
||||
.getAddressLabelSync(widget.walletId, widget.address.value);
|
||||
address = MainDB.instance.isar.addresses
|
||||
.where()
|
||||
.idEqualTo(widget.addressId)
|
||||
.findFirstSync()!;
|
||||
|
||||
label = MainDB.instance.getAddressLabelSync(widget.walletId, address.value);
|
||||
Id? id = label?.id;
|
||||
if (id == null) {
|
||||
label = AddressLabel(
|
||||
walletId: widget.walletId,
|
||||
addressString: widget.address.value,
|
||||
addressString: address.value,
|
||||
value: "",
|
||||
);
|
||||
id = MainDB.instance.putAddressLabelSync(label!);
|
||||
|
@ -84,7 +89,7 @@ class _AddressCardState extends State<AddressCard> {
|
|||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
EditAddressLabelView.routeName,
|
||||
arguments: label!,
|
||||
arguments: label!.id,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -97,7 +102,7 @@ class _AddressCardState extends State<AddressCard> {
|
|||
children: [
|
||||
Expanded(
|
||||
child: SelectableText(
|
||||
widget.address.value,
|
||||
address.value,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
)
|
||||
|
@ -119,16 +124,18 @@ class _AddressCardState extends State<AddressCard> {
|
|||
onPressed: () async {
|
||||
await widget.clipboard.setData(
|
||||
ClipboardData(
|
||||
text: widget.address.value,
|
||||
),
|
||||
);
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
text: address.value,
|
||||
),
|
||||
);
|
||||
if (mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -147,7 +154,7 @@ class _AddressCardState extends State<AddressCard> {
|
|||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AddressQrPopup(
|
||||
addressString: widget.address.value,
|
||||
addressString: address.value,
|
||||
coin: widget.coin,
|
||||
clipboard: widget.clipboard,
|
||||
),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/address_label.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
|
@ -18,12 +19,12 @@ import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
|||
class EditAddressLabelView extends ConsumerStatefulWidget {
|
||||
const EditAddressLabelView({
|
||||
Key? key,
|
||||
required this.addressLabel,
|
||||
required this.addressLabelId,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/editAddressLabel";
|
||||
|
||||
final AddressLabel addressLabel;
|
||||
final int addressLabelId;
|
||||
|
||||
@override
|
||||
ConsumerState<EditAddressLabelView> createState() =>
|
||||
|
@ -36,11 +37,17 @@ class _EditAddressLabelViewState extends ConsumerState<EditAddressLabelView> {
|
|||
|
||||
late final bool isDesktop;
|
||||
|
||||
late AddressLabel addressLabel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isDesktop = Util.isDesktop;
|
||||
_labelFieldController = TextEditingController();
|
||||
_labelFieldController.text = widget.addressLabel.value;
|
||||
addressLabel = MainDB.instance.isar.addressLabels
|
||||
.where()
|
||||
.idEqualTo(widget.addressLabelId)
|
||||
.findFirstSync()!;
|
||||
_labelFieldController.text = addressLabel.value;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -195,7 +202,7 @@ class _EditAddressLabelViewState extends ConsumerState<EditAddressLabelView> {
|
|||
label: "Save",
|
||||
onPressed: () async {
|
||||
await MainDB.instance.updateAddressLabel(
|
||||
widget.addressLabel.copyWith(
|
||||
addressLabel.copyWith(
|
||||
label: _labelFieldController.text,
|
||||
),
|
||||
);
|
||||
|
@ -209,7 +216,7 @@ class _EditAddressLabelViewState extends ConsumerState<EditAddressLabelView> {
|
|||
TextButton(
|
||||
onPressed: () async {
|
||||
await MainDB.instance.updateAddressLabel(
|
||||
widget.addressLabel.copyWith(
|
||||
addressLabel.copyWith(
|
||||
label: _labelFieldController.text,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/address_card.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||
|
||||
class ReceivingAddressesView extends ConsumerWidget {
|
||||
const ReceivingAddressesView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.isDesktop,
|
||||
this.clipboard = const ClipboardWrapper(),
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/receivingAddressesView";
|
||||
|
||||
final String walletId;
|
||||
final bool isDesktop;
|
||||
final ClipboardInterface clipboard;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final coin = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).coin));
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.backgroundAppBar,
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Receiving addresses",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: FutureBuilder(
|
||||
future: MainDB.instance
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.receiving)
|
||||
.and()
|
||||
.not()
|
||||
.typeEqualTo(AddressType.nonWallet)
|
||||
.sortByDerivationIndex()
|
||||
.findAll(),
|
||||
builder: (context, AsyncSnapshot<List<Address>> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.data != null) {
|
||||
// listview
|
||||
return ListView.separated(
|
||||
itemCount: snapshot.data!.length,
|
||||
separatorBuilder: (_, __) => Container(
|
||||
height: 10,
|
||||
),
|
||||
itemBuilder: (_, index) => AddressCard(
|
||||
walletId: walletId,
|
||||
address: snapshot.data![index],
|
||||
coin: coin,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
child: LoadingIndicator(
|
||||
height: 200,
|
||||
width: 200,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
147
lib/pages/receive_view/addresses/wallet_addresses_view.dart
Normal file
147
lib/pages/receive_view/addresses/wallet_addresses_view.dart
Normal file
|
@ -0,0 +1,147 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/address_card.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||
import 'package:stackwallet/widgets/toggle.dart';
|
||||
|
||||
class WalletAddressesView extends ConsumerStatefulWidget {
|
||||
const WalletAddressesView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/walletAddressesView";
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<WalletAddressesView> createState() =>
|
||||
_WalletAddressesViewState();
|
||||
}
|
||||
|
||||
class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
|
||||
final bool isDesktop = Util.isDesktop;
|
||||
|
||||
bool _showChange = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final coin = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId).coin));
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.backgroundAppBar,
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
titleSpacing: 0,
|
||||
title: Text(
|
||||
"Wallet addresses",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: isDesktop ? 56 : 48,
|
||||
width: isDesktop ? 490 : null,
|
||||
child: Toggle(
|
||||
key: UniqueKey(),
|
||||
onColor: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
onText: "Receiving",
|
||||
offColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
offText: "Change",
|
||||
isOn: _showChange,
|
||||
onValueChanged: (value) {
|
||||
setState(() {
|
||||
_showChange = value;
|
||||
});
|
||||
},
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 20 : 16,
|
||||
),
|
||||
Expanded(
|
||||
child: FutureBuilder(
|
||||
future: MainDB.instance
|
||||
.getAddresses(widget.walletId)
|
||||
.filter()
|
||||
.group(
|
||||
(q) => _showChange
|
||||
? q.subTypeEqualTo(AddressSubType.change)
|
||||
: q
|
||||
.subTypeEqualTo(AddressSubType.receiving)
|
||||
.or()
|
||||
.subTypeEqualTo(AddressSubType.paynymReceive),
|
||||
)
|
||||
.and()
|
||||
.not()
|
||||
.typeEqualTo(AddressType.nonWallet)
|
||||
.sortByDerivationIndex()
|
||||
.idProperty()
|
||||
.findAll(),
|
||||
builder: (context, AsyncSnapshot<List<int>> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.data != null) {
|
||||
// listview
|
||||
return ListView.separated(
|
||||
itemCount: snapshot.data!.length,
|
||||
separatorBuilder: (_, __) => Container(
|
||||
height: 10,
|
||||
),
|
||||
itemBuilder: (_, index) => AddressCard(
|
||||
walletId: widget.walletId,
|
||||
addressId: snapshot.data![index],
|
||||
coin: coin,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
child: LoadingIndicator(
|
||||
height: 200,
|
||||
width: 200,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/receiving_addresses_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/wallet_addresses_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
|
@ -21,7 +21,6 @@ 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/custom_loading_overlay.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class ReceiveView extends ConsumerStatefulWidget {
|
||||
const ReceiveView({
|
||||
|
@ -182,8 +181,8 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pushNamed(
|
||||
ReceivingAddressesView.routeName,
|
||||
arguments: Tuple2(walletId, false),
|
||||
WalletAddressesView.routeName,
|
||||
arguments: walletId,
|
||||
);
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
|
|
|
@ -7,9 +7,11 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
|
||||
import 'package:stackwallet/pages/address_book_views/address_book_view.dart';
|
||||
import 'package:stackwallet/pages/coin_control/coin_control_view.dart';
|
||||
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
|
||||
import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart';
|
||||
import 'package:stackwallet/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart';
|
||||
|
@ -43,9 +45,11 @@ import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart';
|
|||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class SendView extends ConsumerStatefulWidget {
|
||||
const SendView({
|
||||
|
@ -104,6 +108,8 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
|
||||
Decimal? _cachedBalance;
|
||||
|
||||
Set<UTXO> selectedUTXOs = {};
|
||||
|
||||
void _cryptoAmountChanged() async {
|
||||
if (!_cryptoAmountChangeLock) {
|
||||
final String cryptoAmount = cryptoAmountController.text;
|
||||
|
@ -313,51 +319,59 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
Format.decimalAmountToSatoshis(manager.balance.getSpendable(), coin);
|
||||
}
|
||||
|
||||
// confirm send all
|
||||
if (amount == availableBalance) {
|
||||
final bool? shouldSendAll = await showDialog<bool>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return StackDialog(
|
||||
title: "Confirm send all",
|
||||
message:
|
||||
"You are about to send your entire balance. Would you like to continue?",
|
||||
leftButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonStyle(context),
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
),
|
||||
rightButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonStyle(context),
|
||||
child: Text(
|
||||
"Yes",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
final coinControlEnabled =
|
||||
ref.read(prefsChangeNotifierProvider).enableCoinControl;
|
||||
|
||||
if (shouldSendAll == null || shouldSendAll == false) {
|
||||
// cancel preview
|
||||
return;
|
||||
if (!(manager.hasCoinControlSupport && coinControlEnabled) ||
|
||||
(manager.hasCoinControlSupport &&
|
||||
coinControlEnabled &&
|
||||
selectedUTXOs.isEmpty)) {
|
||||
// confirm send all
|
||||
if (amount == availableBalance) {
|
||||
final bool? shouldSendAll = await showDialog<bool>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return StackDialog(
|
||||
title: "Confirm send all",
|
||||
message:
|
||||
"You are about to send your entire balance. Would you like to continue?",
|
||||
leftButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonStyle(context),
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
),
|
||||
rightButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonStyle(context),
|
||||
child: Text(
|
||||
"Yes",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (shouldSendAll == null || shouldSendAll == false) {
|
||||
// cancel preview
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -393,7 +407,14 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
txData = await wallet.preparePaymentCodeSend(
|
||||
paymentCode: paymentCode,
|
||||
satoshiAmount: amount,
|
||||
args: {"feeRate": feeRate},
|
||||
args: {
|
||||
"feeRate": feeRate,
|
||||
"UTXOs": (manager.hasCoinControlSupport &&
|
||||
coinControlEnabled &&
|
||||
selectedUTXOs.isNotEmpty)
|
||||
? selectedUTXOs
|
||||
: null,
|
||||
},
|
||||
);
|
||||
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||
|
@ -407,7 +428,14 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
txData = await manager.prepareSend(
|
||||
address: _address!,
|
||||
satoshiAmount: amount,
|
||||
args: {"feeRate": ref.read(feeRateTypeStateProvider)},
|
||||
args: {
|
||||
"feeRate": ref.read(feeRateTypeStateProvider),
|
||||
"UTXOs": (manager.hasCoinControlSupport &&
|
||||
coinControlEnabled &&
|
||||
selectedUTXOs.isNotEmpty)
|
||||
? selectedUTXOs
|
||||
: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -565,6 +593,17 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
final String locale = ref.watch(
|
||||
localeServiceChangeNotifierProvider.select((value) => value.locale));
|
||||
|
||||
final showCoinControl = ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value.getManager(walletId).hasCoinControlSupport,
|
||||
),
|
||||
) &&
|
||||
ref.watch(
|
||||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.enableCoinControl,
|
||||
),
|
||||
);
|
||||
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
ref.listen(publicPrivateBalanceStateProvider, (previous, next) {
|
||||
if (_amountToSend == null) {
|
||||
|
@ -1506,6 +1545,56 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (showCoinControl)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (showCoinControl)
|
||||
RoundedWhiteContainer(
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Coin control",
|
||||
style:
|
||||
STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
CustomTextButton(
|
||||
text: selectedUTXOs.isEmpty
|
||||
? "Select coins"
|
||||
: "Selected coins (${selectedUTXOs.length})",
|
||||
onTap: () async {
|
||||
final result =
|
||||
await Navigator.of(context).pushNamed(
|
||||
CoinControlView.routeName,
|
||||
arguments: Tuple4(
|
||||
walletId,
|
||||
CoinControlViewType.use,
|
||||
_amountToSend != null
|
||||
? Format.decimalAmountToSatoshis(
|
||||
_amountToSend!,
|
||||
coin,
|
||||
)
|
||||
: null,
|
||||
selectedUTXOs,
|
||||
),
|
||||
);
|
||||
|
||||
if (result is Set<UTXO>) {
|
||||
setState(() {
|
||||
selectedUTXOs = result;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
|
|
|
@ -121,6 +121,53 @@ class AdvancedSettingsView extends StatelessWidget {
|
|||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return RawMaterialButton(
|
||||
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
onPressed: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Enable coin control",
|
||||
style: STextStyles.titleBold12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 40,
|
||||
child: DraggableSwitchButton(
|
||||
isOn: ref.watch(
|
||||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.enableCoinControl),
|
||||
),
|
||||
onValueChanged: (newValue) {
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.enableCoinControl = newValue;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Consumer(
|
||||
|
|
|
@ -3,9 +3,11 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/pages/coin_control/coin_control_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/paynym_claim_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
||||
import 'package:stackwallet/providers/global/paynym_api_provider.dart';
|
||||
import 'package:stackwallet/providers/global/prefs_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
|
@ -16,6 +18,7 @@ import 'package:stackwallet/utilities/logger.dart';
|
|||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class WalletNavigationBar extends ConsumerStatefulWidget {
|
||||
const WalletNavigationBar({
|
||||
|
@ -50,6 +53,23 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final showMore = ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value.getManager(widget.walletId).hasPaynymSupport,
|
||||
),
|
||||
) ||
|
||||
(ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) =>
|
||||
value.getManager(widget.walletId).hasCoinControlSupport,
|
||||
),
|
||||
) &&
|
||||
ref.watch(
|
||||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.enableCoinControl,
|
||||
),
|
||||
));
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
|
@ -97,62 +117,36 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
|
|||
// const SizedBox(
|
||||
// height: 8,
|
||||
// ),
|
||||
AnimatedOpacity(
|
||||
opacity: scale,
|
||||
duration: duration,
|
||||
child: Consumer(builder: (context, ref, __) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
scale = 0;
|
||||
});
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => const LoadingIndicator(
|
||||
width: 100,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final manager = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId);
|
||||
|
||||
final paynymInterface =
|
||||
manager.wallet as PaynymWalletInterface;
|
||||
|
||||
final code = await paynymInterface.getPaymentCode(
|
||||
DerivePathTypeExt.primaryFor(manager.coin));
|
||||
|
||||
final account = await ref
|
||||
.read(paynymAPIProvider)
|
||||
.nym(code.toString());
|
||||
|
||||
Logging.instance.log(
|
||||
"my nym account: $account",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
if (ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value
|
||||
.getManager(widget.walletId)
|
||||
.hasCoinControlSupport,
|
||||
),
|
||||
) &&
|
||||
ref.watch(
|
||||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.enableCoinControl,
|
||||
),
|
||||
))
|
||||
AnimatedOpacity(
|
||||
opacity: scale,
|
||||
duration: duration,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
// hide more context menu
|
||||
setState(() {
|
||||
scale = 0;
|
||||
});
|
||||
|
||||
// check if account exists and for matching code to see if claimed
|
||||
if (account.value != null &&
|
||||
account.value!.codes.first.claimed) {
|
||||
ref.read(myPaynymAccountStateProvider.state).state =
|
||||
account.value!;
|
||||
|
||||
await Navigator.of(context).pushNamed(
|
||||
PaynymHomeView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).pushNamed(
|
||||
PaynymClaimView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
}
|
||||
Navigator.of(context).pushNamed(
|
||||
CoinControlView.routeName,
|
||||
arguments: Tuple2(
|
||||
widget.walletId,
|
||||
CoinControlViewType.manage,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
|
@ -174,14 +168,14 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Paynym",
|
||||
"Coin control",
|
||||
style: STextStyles.buttonSmall(context),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.robotHead,
|
||||
Assets.svg.coinControl.gamePad,
|
||||
height: 20,
|
||||
width: 20,
|
||||
color: Theme.of(context)
|
||||
|
@ -191,9 +185,129 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
|
|||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value
|
||||
.getManager(widget.walletId)
|
||||
.hasCoinControlSupport,
|
||||
),
|
||||
) &&
|
||||
ref.watch(
|
||||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.enableCoinControl,
|
||||
),
|
||||
) &&
|
||||
ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) =>
|
||||
value.getManager(widget.walletId).hasPaynymSupport,
|
||||
),
|
||||
))
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (ref.watch(walletsChangeNotifierProvider.select((value) =>
|
||||
value.getManager(widget.walletId).hasPaynymSupport)))
|
||||
AnimatedOpacity(
|
||||
opacity: scale,
|
||||
duration: duration,
|
||||
child: Consumer(builder: (context, ref, __) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
scale = 0;
|
||||
});
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => const LoadingIndicator(
|
||||
width: 100,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final manager = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId);
|
||||
|
||||
final paynymInterface =
|
||||
manager.wallet as PaynymWalletInterface;
|
||||
|
||||
final code = await paynymInterface.getPaymentCode(
|
||||
DerivePathTypeExt.primaryFor(manager.coin));
|
||||
|
||||
final account = await ref
|
||||
.read(paynymAPIProvider)
|
||||
.nym(code.toString());
|
||||
|
||||
Logging.instance.log(
|
||||
"my nym account: $account",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
// check if account exists and for matching code to see if claimed
|
||||
if (account.value != null &&
|
||||
account.value!.codes.first.claimed) {
|
||||
ref.read(myPaynymAccountStateProvider.state).state =
|
||||
account.value!;
|
||||
|
||||
await Navigator.of(context).pushNamed(
|
||||
PaynymHomeView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).pushNamed(
|
||||
PaynymClaimView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
width: 146,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
boxShadow: [
|
||||
Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.standardBoxShadow
|
||||
],
|
||||
borderRadius: BorderRadius.circular(
|
||||
widget.height / 2.0,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Paynym",
|
||||
style: STextStyles.buttonSmall(context),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.robotHead,
|
||||
height: 20,
|
||||
width: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.bottomNavIconIcon,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
|
@ -410,8 +524,7 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (ref.watch(walletsChangeNotifierProvider.select((value) =>
|
||||
value.getManager(widget.walletId).hasPaynymSupport)))
|
||||
if (showMore)
|
||||
RawMaterialButton(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 66,
|
||||
|
|
|
@ -48,8 +48,10 @@ class WalletSummary extends StatelessWidget {
|
|||
builder: (_, ref, __) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<StackColors>()!.colorForCoin(ref
|
||||
.watch(managerProvider.select((value) => value.coin))),
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.colorForCoin(ref.watch(
|
||||
managerProvider.select((value) => value.coin))),
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
|
@ -112,7 +114,6 @@ class WalletSummary extends StatelessWidget {
|
|||
padding: const EdgeInsets.all(16.0),
|
||||
child: WalletSummaryInfo(
|
||||
walletId: walletId,
|
||||
managerProvider: managerProvider,
|
||||
initialSyncStatus: initialSyncStatus,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
@ -7,35 +9,32 @@ import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_refresh_button.
|
|||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart';
|
||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||
import 'package:stackwallet/services/coins/manager.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/balance_refreshed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/animated_text.dart';
|
||||
|
||||
class WalletSummaryInfo extends StatefulWidget {
|
||||
class WalletSummaryInfo extends ConsumerStatefulWidget {
|
||||
const WalletSummaryInfo({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.managerProvider,
|
||||
required this.initialSyncStatus,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
final ChangeNotifierProvider<Manager> managerProvider;
|
||||
final WalletSyncStatus initialSyncStatus;
|
||||
|
||||
@override
|
||||
State<WalletSummaryInfo> createState() => _WalletSummaryInfoState();
|
||||
ConsumerState<WalletSummaryInfo> createState() => _WalletSummaryInfoState();
|
||||
}
|
||||
|
||||
class _WalletSummaryInfoState extends State<WalletSummaryInfo> {
|
||||
late final String walletId;
|
||||
late final ChangeNotifierProvider<Manager> managerProvider;
|
||||
class _WalletSummaryInfoState extends ConsumerState<WalletSummaryInfo> {
|
||||
late StreamSubscription<BalanceRefreshedEvent> _balanceUpdated;
|
||||
|
||||
void showSheet() {
|
||||
showModalBottomSheet<dynamic>(
|
||||
|
@ -46,251 +45,154 @@ class _WalletSummaryInfoState extends State<WalletSummaryInfo> {
|
|||
top: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
builder: (_) => WalletBalanceToggleSheet(walletId: walletId),
|
||||
builder: (_) => WalletBalanceToggleSheet(walletId: widget.walletId),
|
||||
);
|
||||
}
|
||||
|
||||
Decimal? _balanceTotalCached;
|
||||
Decimal? _balanceCached;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
walletId = widget.walletId;
|
||||
managerProvider = widget.managerProvider;
|
||||
_balanceUpdated =
|
||||
GlobalEventBus.instance.on<BalanceRefreshedEvent>().listen(
|
||||
(event) async {
|
||||
if (event.walletId == widget.walletId) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_balanceUpdated.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
final externalCalls = ref.watch(
|
||||
prefsChangeNotifierProvider.select((value) => value.externalCalls));
|
||||
final coin = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId).coin));
|
||||
final balance = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId).balance));
|
||||
|
||||
final locale = ref.watch(
|
||||
localeServiceChangeNotifierProvider.select((value) => value.locale));
|
||||
|
||||
final baseCurrency = ref
|
||||
.watch(prefsChangeNotifierProvider.select((value) => value.currency));
|
||||
|
||||
final priceTuple = ref.watch(priceAnd24hChangeNotifierProvider
|
||||
.select((value) => value.getPrice(coin)));
|
||||
|
||||
final _showAvailable =
|
||||
ref.watch(walletBalanceToggleStateProvider.state).state ==
|
||||
WalletBalanceToggleState.available;
|
||||
|
||||
final Decimal totalBalance;
|
||||
final Decimal availableBalance;
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
final firoWallet = ref.watch(walletsChangeNotifierProvider.select(
|
||||
(value) => value.getManager(widget.walletId).wallet)) as FiroWallet;
|
||||
totalBalance = firoWallet.balance.getSpendable();
|
||||
availableBalance = firoWallet.balancePrivate.getSpendable();
|
||||
} else {
|
||||
totalBalance = balance.getTotal();
|
||||
availableBalance = balance.getSpendable();
|
||||
}
|
||||
|
||||
final balanceToShow = _showAvailable ? availableBalance : totalBalance;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
final Coin coin =
|
||||
ref.watch(managerProvider.select((value) => value.coin));
|
||||
final externalCalls = ref.watch(prefsChangeNotifierProvider
|
||||
.select((value) => value.externalCalls));
|
||||
|
||||
Future<Decimal>? totalBalanceFuture;
|
||||
Future<Decimal>? availableBalanceFuture;
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
final firoWallet =
|
||||
ref.watch(managerProvider.select((value) => value.wallet))
|
||||
as FiroWallet;
|
||||
totalBalanceFuture =
|
||||
Future(() => firoWallet.balance.getSpendable());
|
||||
availableBalanceFuture =
|
||||
Future(() => firoWallet.balancePrivate.getSpendable());
|
||||
} else {
|
||||
final manager = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId)));
|
||||
totalBalanceFuture = Future(() => manager.balance.getTotal());
|
||||
availableBalanceFuture =
|
||||
Future(() => manager.balance.getSpendable());
|
||||
}
|
||||
|
||||
final locale = ref.watch(localeServiceChangeNotifierProvider
|
||||
.select((value) => value.locale));
|
||||
|
||||
final baseCurrency = ref.watch(prefsChangeNotifierProvider
|
||||
.select((value) => value.currency));
|
||||
|
||||
final priceTuple = ref.watch(priceAnd24hChangeNotifierProvider
|
||||
.select((value) => value.getPrice(coin)));
|
||||
|
||||
final _showAvailable =
|
||||
ref.watch(walletBalanceToggleStateProvider.state).state ==
|
||||
WalletBalanceToggleState.available;
|
||||
|
||||
return FutureBuilder(
|
||||
future: _showAvailable
|
||||
? availableBalanceFuture
|
||||
: totalBalanceFuture,
|
||||
builder: (fbContext, AsyncSnapshot<Decimal> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.hasData &&
|
||||
snapshot.data != null) {
|
||||
if (_showAvailable) {
|
||||
_balanceCached = snapshot.data!;
|
||||
} else {
|
||||
_balanceTotalCached = snapshot.data!;
|
||||
}
|
||||
}
|
||||
Decimal? balanceToShow =
|
||||
_showAvailable ? _balanceCached : _balanceTotalCached;
|
||||
|
||||
if (balanceToShow != null) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: showSheet,
|
||||
child: Row(
|
||||
children: [
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||
Text(
|
||||
"${_showAvailable ? "Private" : "Public"} Balance",
|
||||
style:
|
||||
STextStyles.subtitle500(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
),
|
||||
if (coin != Coin.firo && coin != Coin.firoTestNet)
|
||||
Text(
|
||||
"${_showAvailable ? "Available" : "Full"} Balance",
|
||||
style:
|
||||
STextStyles.subtitle500(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
width: 8,
|
||||
height: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: showSheet,
|
||||
child: Row(
|
||||
children: [
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||
Text(
|
||||
"${_showAvailable ? "Private" : "Public"} Balance",
|
||||
style: STextStyles.subtitle500(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
const Spacer(),
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: SelectableText(
|
||||
"${Format.localizedStringAsFixed(
|
||||
value: balanceToShow,
|
||||
locale: locale,
|
||||
decimalPlaces: 8,
|
||||
)} ${coin.ticker}",
|
||||
style: STextStyles.pageTitleH1(context).copyWith(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (coin != Coin.firo && coin != Coin.firoTestNet)
|
||||
Text(
|
||||
"${_showAvailable ? "Available" : "Full"} Balance",
|
||||
style: STextStyles.subtitle500(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
if (externalCalls)
|
||||
Text(
|
||||
"${Format.localizedStringAsFixed(
|
||||
value: priceTuple.item1 * balanceToShow,
|
||||
locale: locale,
|
||||
decimalPlaces: 2,
|
||||
)} $baseCurrency",
|
||||
style: STextStyles.subtitle500(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: showSheet,
|
||||
child: Row(
|
||||
children: [
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||
Text(
|
||||
"${_showAvailable ? "Private" : "Public"} Balance",
|
||||
style:
|
||||
STextStyles.subtitle500(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
),
|
||||
if (coin != Coin.firo && coin != Coin.firoTestNet)
|
||||
Text(
|
||||
"${_showAvailable ? "Available" : "Full"} Balance",
|
||||
style:
|
||||
STextStyles.subtitle500(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 8,
|
||||
height: 4,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
AnimatedText(
|
||||
stringsToLoopThrough: const [
|
||||
"Loading balance",
|
||||
"Loading balance.",
|
||||
"Loading balance..",
|
||||
"Loading balance..."
|
||||
],
|
||||
style: STextStyles.pageTitleH1(context).copyWith(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
),
|
||||
AnimatedText(
|
||||
stringsToLoopThrough: const [
|
||||
"Loading balance",
|
||||
"Loading balance.",
|
||||
"Loading balance..",
|
||||
"Loading balance..."
|
||||
],
|
||||
style: STextStyles.subtitle500(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
width: 8,
|
||||
height: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: SelectableText(
|
||||
"${Format.localizedStringAsFixed(
|
||||
value: balanceToShow,
|
||||
locale: locale,
|
||||
decimalPlaces: 8,
|
||||
)} ${coin.ticker}",
|
||||
style: STextStyles.pageTitleH1(context).copyWith(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (externalCalls)
|
||||
Text(
|
||||
"${Format.localizedStringAsFixed(
|
||||
value: priceTuple.item1 * balanceToShow,
|
||||
locale: locale,
|
||||
decimalPlaces: 2,
|
||||
)} $baseCurrency",
|
||||
style: STextStyles.subtitle500(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return SvgPicture.asset(
|
||||
Assets.svg.iconFor(
|
||||
coin: ref.watch(
|
||||
managerProvider.select((value) => value.coin),
|
||||
),
|
||||
),
|
||||
width: 24,
|
||||
height: 24,
|
||||
);
|
||||
},
|
||||
SvgPicture.asset(
|
||||
Assets.svg.iconFor(
|
||||
coin: coin,
|
||||
),
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
const Spacer(),
|
||||
WalletRefreshButton(
|
||||
walletId: walletId,
|
||||
walletId: widget.walletId,
|
||||
initialSyncStatus: widget.initialSyncStatus,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -814,6 +814,8 @@ class _DesktopTransactionCardRowState
|
|||
} else {
|
||||
return "Sending";
|
||||
}
|
||||
} else if (type == TransactionType.sentToSelf) {
|
||||
return "Sent to self";
|
||||
} else {
|
||||
return type.name;
|
||||
}
|
||||
|
|
|
@ -127,6 +127,8 @@ class _TransactionDetailsViewState
|
|||
} else {
|
||||
return "Sending";
|
||||
}
|
||||
} else if (type == TransactionType.sentToSelf) {
|
||||
return "Sent to self";
|
||||
} else {
|
||||
return type.name;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:decimal/decimal.dart';
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/models/buy/response_objects/quote.dart';
|
||||
import 'package:stackwallet/models/contact_address_entry.dart';
|
||||
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
|
||||
|
@ -27,6 +28,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/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';
|
||||
import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_1_view.dart';
|
||||
|
@ -36,6 +39,7 @@ import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_4_view.
|
|||
import 'package:stackwallet/pages/exchange_view/send_from_view.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
|
||||
import 'package:stackwallet/pages/generic/single_field_edit_view.dart';
|
||||
import 'package:stackwallet/pages/home_view/home_view.dart';
|
||||
import 'package:stackwallet/pages/intro_view.dart';
|
||||
import 'package:stackwallet/pages/manage_favorites_view/manage_favorites_view.dart';
|
||||
|
@ -45,7 +49,7 @@ import 'package:stackwallet/pages/paynym/paynym_claim_view.dart';
|
|||
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
||||
import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/edit_address_label_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/receiving_addresses_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/wallet_addresses_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/receive_view.dart';
|
||||
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
|
||||
|
@ -130,8 +134,6 @@ import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
|
|||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import 'models/isar/models/blockchain_data/transaction.dart';
|
||||
|
||||
class RouteGenerator {
|
||||
static const bool useMaterialPageRoute = true;
|
||||
|
||||
|
@ -200,6 +202,65 @@ class RouteGenerator {
|
|||
builder: (_) => const AddWalletView(),
|
||||
settings: RouteSettings(name: settings.name));
|
||||
|
||||
case SingleFieldEditView.routeName:
|
||||
if (args is Tuple2<String, String>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => SingleFieldEditView(
|
||||
initialValue: args.item1,
|
||||
label: args.item2,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case CoinControlView.routeName:
|
||||
if (args is Tuple2<String, CoinControlViewType>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => CoinControlView(
|
||||
walletId: args.item1,
|
||||
type: args.item2,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
} else if (args
|
||||
is Tuple4<String, CoinControlViewType, int?, Set<UTXO>?>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => CoinControlView(
|
||||
walletId: args.item1,
|
||||
type: args.item2,
|
||||
requestedTotal: args.item3,
|
||||
selectedUTXOs: args.item4,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case UtxoDetailsView.routeName:
|
||||
if (args is Tuple2<Id, String>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => UtxoDetailsView(
|
||||
walletId: args.item2,
|
||||
utxoId: args.item1,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case PaynymClaimView.routeName:
|
||||
if (args is String) {
|
||||
return getRoute(
|
||||
|
@ -476,11 +537,11 @@ class RouteGenerator {
|
|||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case EditAddressLabelView.routeName:
|
||||
if (args is AddressLabel) {
|
||||
if (args is int) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => EditAddressLabelView(
|
||||
addressLabel: args,
|
||||
addressLabelId: args,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
|
@ -836,13 +897,12 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case ReceivingAddressesView.routeName:
|
||||
if (args is Tuple2<String, bool>) {
|
||||
case WalletAddressesView.routeName:
|
||||
if (args is String) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => ReceivingAddressesView(
|
||||
walletId: args.item1,
|
||||
isDesktop: args.item2,
|
||||
builder: (_) => WalletAddressesView(
|
||||
walletId: args,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
|
|
|
@ -24,6 +24,7 @@ import 'package:stackwallet/services/event_bus/events/global/refresh_percent_cha
|
|||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_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/electrum_x_parsing.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||
|
@ -94,7 +95,12 @@ String constructDerivePath({
|
|||
}
|
||||
|
||||
class BitcoinWallet extends CoinServiceAPI
|
||||
with WalletCache, WalletDB, ElectrumXParsing, PaynymWalletInterface {
|
||||
with
|
||||
WalletCache,
|
||||
WalletDB,
|
||||
ElectrumXParsing,
|
||||
PaynymWalletInterface,
|
||||
CoinControlInterface {
|
||||
BitcoinWallet({
|
||||
required String walletId,
|
||||
required String walletName,
|
||||
|
@ -114,6 +120,17 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
_secureStore = secureStore;
|
||||
initCache(walletId, coin);
|
||||
initWalletDB(mockableOverride: mockableOverride);
|
||||
initCoinControlInterface(
|
||||
walletId: walletId,
|
||||
walletName: walletName,
|
||||
coin: coin,
|
||||
db: db,
|
||||
getChainHeight: () => chainHeight,
|
||||
refreshedBalanceCallback: (balance) async {
|
||||
_balance = balance;
|
||||
await updateCachedBalance(_balance!);
|
||||
},
|
||||
);
|
||||
initPaynymWalletInterface(
|
||||
walletId: walletId,
|
||||
walletName: walletName,
|
||||
|
@ -1106,6 +1123,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
try {
|
||||
final feeRateType = args?["feeRate"];
|
||||
final feeRateAmount = args?["feeRateAmount"];
|
||||
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
|
||||
if (feeRateType is FeeRateType || feeRateAmount is int) {
|
||||
late final int rate;
|
||||
if (feeRateType is FeeRateType) {
|
||||
|
@ -1133,8 +1151,14 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
isSendAll = true;
|
||||
}
|
||||
|
||||
final txData =
|
||||
await coinSelection(satoshiAmount, rate, address, isSendAll);
|
||||
final txData = await coinSelection(
|
||||
satoshiAmountToSend: satoshiAmount,
|
||||
selectedTxFeeRate: rate,
|
||||
recipientAddress: address,
|
||||
isSendAll: isSendAll,
|
||||
utxos: utxos?.toList(),
|
||||
coinControl: utxos is List<isar_models.UTXO>,
|
||||
);
|
||||
|
||||
Logging.instance.log("prepare send: $txData", level: LogLevel.Info);
|
||||
try {
|
||||
|
@ -1197,6 +1221,11 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex);
|
||||
Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info);
|
||||
|
||||
final utxos = txData["usedUTXOs"] as List<isar_models.UTXO>;
|
||||
|
||||
// mark utxos as used
|
||||
await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList());
|
||||
|
||||
return txHash;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s",
|
||||
|
@ -1790,18 +1819,14 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
}
|
||||
}
|
||||
|
||||
final currentChainHeight = await chainHeight;
|
||||
|
||||
final List<isar_models.UTXO> outputArray = [];
|
||||
int satoshiBalanceTotal = 0;
|
||||
int satoshiBalancePending = 0;
|
||||
int satoshiBalanceSpendable = 0;
|
||||
int satoshiBalanceBlocked = 0;
|
||||
|
||||
for (int i = 0; i < fetchedUtxoList.length; i++) {
|
||||
for (int j = 0; j < fetchedUtxoList[i].length; j++) {
|
||||
final jsonUTXO = fetchedUtxoList[i][j];
|
||||
|
||||
final txn = await cachedElectrumXClient.getTransaction(
|
||||
txHash: fetchedUtxoList[i][j]["tx_hash"] as String,
|
||||
txHash: jsonUTXO["tx_hash"] as String,
|
||||
verbose: true,
|
||||
coin: coin,
|
||||
);
|
||||
|
@ -1809,7 +1834,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
// fetch stored tx to see if paynym notification tx and block utxo
|
||||
final storedTx = await db.getTransaction(
|
||||
walletId,
|
||||
fetchedUtxoList[i][j]["tx_hash"] as String,
|
||||
jsonUTXO["tx_hash"] as String,
|
||||
);
|
||||
|
||||
bool shouldBlock = false;
|
||||
|
@ -1826,32 +1851,35 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
blockReason = "Incoming paynym notification transaction.";
|
||||
}
|
||||
|
||||
final vout = jsonUTXO["tx_pos"] as int;
|
||||
|
||||
final outputs = txn["vout"] as List;
|
||||
|
||||
String? utxoOwnerAddress;
|
||||
// get UTXO owner address
|
||||
for (final output in outputs) {
|
||||
if (output["n"] == vout) {
|
||||
utxoOwnerAddress =
|
||||
output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
}
|
||||
}
|
||||
|
||||
final utxo = isar_models.UTXO(
|
||||
walletId: walletId,
|
||||
txid: txn["txid"] as String,
|
||||
vout: fetchedUtxoList[i][j]["tx_pos"] as int,
|
||||
value: fetchedUtxoList[i][j]["value"] as int,
|
||||
vout: vout,
|
||||
value: jsonUTXO["value"] as int,
|
||||
name: "",
|
||||
isBlocked: shouldBlock,
|
||||
blockedReason: blockReason,
|
||||
isCoinbase: txn["is_coinbase"] as bool? ?? false,
|
||||
blockHash: txn["blockhash"] as String?,
|
||||
blockHeight: fetchedUtxoList[i][j]["height"] as int?,
|
||||
blockHeight: jsonUTXO["height"] as int?,
|
||||
blockTime: txn["blocktime"] as int?,
|
||||
address: utxoOwnerAddress,
|
||||
);
|
||||
|
||||
satoshiBalanceTotal += utxo.value;
|
||||
|
||||
if (utxo.isBlocked) {
|
||||
satoshiBalanceBlocked += utxo.value;
|
||||
} else {
|
||||
if (utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) {
|
||||
satoshiBalanceSpendable += utxo.value;
|
||||
} else {
|
||||
satoshiBalancePending += utxo.value;
|
||||
}
|
||||
}
|
||||
|
||||
outputArray.add(utxo);
|
||||
}
|
||||
}
|
||||
|
@ -1859,27 +1887,20 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
Logging.instance
|
||||
.log('Outputs fetched: $outputArray', level: LogLevel.Info);
|
||||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
await db.updateUTXOs(walletId, outputArray);
|
||||
|
||||
// finally update balance
|
||||
_balance = Balance(
|
||||
coin: coin,
|
||||
total: satoshiBalanceTotal,
|
||||
spendable: satoshiBalanceSpendable,
|
||||
blockedTotal: satoshiBalanceBlocked,
|
||||
pendingSpendable: satoshiBalancePending,
|
||||
);
|
||||
await updateCachedBalance(_balance!);
|
||||
await _updateBalance();
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
await refreshBalance();
|
||||
}
|
||||
|
||||
@override
|
||||
Balance get balance => _balance ??= getCachedBalance();
|
||||
Balance? _balance;
|
||||
|
@ -2252,11 +2273,12 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
/// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return
|
||||
/// a map containing the tx hex along with other important information. If not, then it will return
|
||||
/// an integer (1 or 2)
|
||||
dynamic coinSelection(
|
||||
int satoshiAmountToSend,
|
||||
int selectedTxFeeRate,
|
||||
String _recipientAddress,
|
||||
bool isSendAll, {
|
||||
dynamic coinSelection({
|
||||
required int satoshiAmountToSend,
|
||||
required int selectedTxFeeRate,
|
||||
required String recipientAddress,
|
||||
required bool coinControl,
|
||||
required bool isSendAll,
|
||||
int additionalOutputs = 0,
|
||||
List<isar_models.UTXO>? utxos,
|
||||
}) async {
|
||||
|
@ -2268,18 +2290,26 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
int spendableSatoshiValue = 0;
|
||||
|
||||
// Build list of spendable outputs and totaling their satoshi amount
|
||||
for (var i = 0; i < availableOutputs.length; i++) {
|
||||
if (availableOutputs[i].isBlocked == false &&
|
||||
availableOutputs[i]
|
||||
.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) ==
|
||||
true) {
|
||||
spendableOutputs.add(availableOutputs[i]);
|
||||
spendableSatoshiValue += availableOutputs[i].value;
|
||||
for (final utxo in availableOutputs) {
|
||||
if (utxo.isBlocked == false &&
|
||||
utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) &&
|
||||
utxo.used != true) {
|
||||
spendableOutputs.add(utxo);
|
||||
spendableSatoshiValue += utxo.value;
|
||||
}
|
||||
}
|
||||
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||
if (coinControl) {
|
||||
if (spendableOutputs.length < availableOutputs.length) {
|
||||
throw ArgumentError("Attempted to use an unavailable utxo");
|
||||
}
|
||||
}
|
||||
|
||||
// don't care about sorting if using all utxos
|
||||
if (!coinControl) {
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||
}
|
||||
|
||||
Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}",
|
||||
level: LogLevel.Info);
|
||||
|
@ -2310,19 +2340,26 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
int inputsBeingConsumed = 0;
|
||||
List<isar_models.UTXO> utxoObjectsToUse = [];
|
||||
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += spendableOutputs[i].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
|
||||
inputsBeingConsumed += 1;
|
||||
if (!coinControl) {
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend &&
|
||||
i < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += spendableOutputs[i].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs &&
|
||||
inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
} else {
|
||||
satoshisBeingUsed = spendableSatoshiValue;
|
||||
utxoObjectsToUse = spendableOutputs;
|
||||
}
|
||||
|
||||
Logging.instance
|
||||
|
@ -2333,7 +2370,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
.log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info);
|
||||
|
||||
// numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray
|
||||
List<String> recipientsArray = [_recipientAddress];
|
||||
List<String> recipientsArray = [recipientAddress];
|
||||
List<int> recipientsAmtArray = [satoshiAmountToSend];
|
||||
|
||||
// gather required signing data
|
||||
|
@ -2346,7 +2383,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
recipients: [recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
int feeForOneOutput = estimateTxFee(
|
||||
|
@ -2373,6 +2410,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": amount,
|
||||
"fee": feeForOneOutput,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2382,7 +2420,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
recipients: [recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
} catch (e) {
|
||||
|
@ -2396,7 +2434,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
recipientAddress,
|
||||
await _getCurrentAddressForChain(
|
||||
1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
|
@ -2505,6 +2543,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": feeBeingPaid,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
} else {
|
||||
|
@ -2532,6 +2571,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2561,6 +2601,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2590,6 +2631,7 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": feeForOneOutput,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
} else {
|
||||
|
@ -2601,9 +2643,15 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
level: LogLevel.Warning);
|
||||
// try adding more outputs
|
||||
if (spendableOutputs.length > inputsBeingConsumed) {
|
||||
return coinSelection(satoshiAmountToSend, selectedTxFeeRate,
|
||||
_recipientAddress, isSendAll,
|
||||
additionalOutputs: additionalOutputs + 1, utxos: utxos);
|
||||
return coinSelection(
|
||||
satoshiAmountToSend: satoshiAmountToSend,
|
||||
selectedTxFeeRate: selectedTxFeeRate,
|
||||
recipientAddress: recipientAddress,
|
||||
isSendAll: isSendAll,
|
||||
additionalOutputs: additionalOutputs + 1,
|
||||
utxos: utxos,
|
||||
coinControl: coinControl,
|
||||
);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import 'package:stackwallet/services/event_bus/events/global/refresh_percent_cha
|
|||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_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/wallet_cache.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||
import 'package:stackwallet/services/node_service.dart';
|
||||
|
@ -98,7 +99,8 @@ String constructDerivePath({
|
|||
return "m/$purpose'/$coinType'/$account'/$chain/$index";
|
||||
}
|
||||
|
||||
class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||
class BitcoinCashWallet extends CoinServiceAPI
|
||||
with WalletCache, WalletDB, CoinControlInterface {
|
||||
BitcoinCashWallet({
|
||||
required String walletId,
|
||||
required String walletName,
|
||||
|
@ -118,6 +120,17 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
_secureStore = secureStore;
|
||||
initCache(walletId, coin);
|
||||
initWalletDB(mockableOverride: mockableOverride);
|
||||
initCoinControlInterface(
|
||||
walletId: walletId,
|
||||
walletName: walletName,
|
||||
coin: coin,
|
||||
db: db,
|
||||
getChainHeight: () => chainHeight,
|
||||
refreshedBalanceCallback: (balance) async {
|
||||
_balance = balance;
|
||||
await updateCachedBalance(_balance!);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static const integrationTestFlag =
|
||||
|
@ -1041,6 +1054,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
try {
|
||||
final feeRateType = args?["feeRate"];
|
||||
final feeRateAmount = args?["feeRateAmount"];
|
||||
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
|
||||
if (feeRateType is FeeRateType || feeRateAmount is int) {
|
||||
late final int rate;
|
||||
if (feeRateType is FeeRateType) {
|
||||
|
@ -1067,9 +1081,17 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
isSendAll = true;
|
||||
}
|
||||
|
||||
final result =
|
||||
await coinSelection(satoshiAmount, rate, address, isSendAll);
|
||||
Logging.instance.log("SEND RESULT: $result", level: LogLevel.Info);
|
||||
final result = await coinSelection(
|
||||
satoshiAmountToSend: satoshiAmount,
|
||||
selectedTxFeeRate: rate,
|
||||
recipientAddress: address,
|
||||
isSendAll: isSendAll,
|
||||
utxos: utxos?.toList(),
|
||||
coinControl: utxos is List<isar_models.UTXO>,
|
||||
);
|
||||
|
||||
Logging.instance
|
||||
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
|
||||
if (result is int) {
|
||||
switch (result) {
|
||||
case 1:
|
||||
|
@ -1115,6 +1137,12 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final txHash = await _electrumXClient.broadcastTransaction(
|
||||
rawTx: txData["hex"] as String);
|
||||
Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info);
|
||||
|
||||
final utxos = txData["usedUTXOs"] as List<isar_models.UTXO>;
|
||||
|
||||
// mark utxos as used
|
||||
await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList());
|
||||
|
||||
return txHash;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s",
|
||||
|
@ -1719,49 +1747,47 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
}
|
||||
|
||||
final currentChainHeight = await chainHeight;
|
||||
|
||||
final List<isar_models.UTXO> outputArray = [];
|
||||
int satoshiBalanceTotal = 0;
|
||||
int satoshiBalancePending = 0;
|
||||
int satoshiBalanceSpendable = 0;
|
||||
int satoshiBalanceBlocked = 0;
|
||||
|
||||
for (int i = 0; i < fetchedUtxoList.length; i++) {
|
||||
for (int j = 0; j < fetchedUtxoList[i].length; j++) {
|
||||
final jsonUTXO = fetchedUtxoList[i][j];
|
||||
|
||||
final txn = await cachedElectrumXClient.getTransaction(
|
||||
txHash: fetchedUtxoList[i][j]["tx_hash"] as String,
|
||||
txHash: jsonUTXO["tx_hash"] as String,
|
||||
verbose: true,
|
||||
coin: coin,
|
||||
);
|
||||
|
||||
// todo check here if we should mark as blocked
|
||||
final vout = jsonUTXO["tx_pos"] as int;
|
||||
|
||||
final outputs = txn["vout"] as List;
|
||||
|
||||
String? utxoOwnerAddress;
|
||||
// get UTXO owner address
|
||||
for (final output in outputs) {
|
||||
if (output["n"] == vout) {
|
||||
utxoOwnerAddress =
|
||||
output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
}
|
||||
}
|
||||
|
||||
final utxo = isar_models.UTXO(
|
||||
walletId: walletId,
|
||||
txid: txn["txid"] as String,
|
||||
vout: fetchedUtxoList[i][j]["tx_pos"] as int,
|
||||
value: fetchedUtxoList[i][j]["value"] as int,
|
||||
vout: vout,
|
||||
value: jsonUTXO["value"] as int,
|
||||
name: "",
|
||||
isBlocked: false,
|
||||
blockedReason: null,
|
||||
isCoinbase: txn["is_coinbase"] as bool? ?? false,
|
||||
blockHash: txn["blockhash"] as String?,
|
||||
blockHeight: fetchedUtxoList[i][j]["height"] as int?,
|
||||
blockHeight: jsonUTXO["height"] as int?,
|
||||
blockTime: txn["blocktime"] as int?,
|
||||
address: utxoOwnerAddress,
|
||||
);
|
||||
|
||||
satoshiBalanceTotal += utxo.value;
|
||||
|
||||
if (utxo.isBlocked) {
|
||||
satoshiBalanceBlocked += utxo.value;
|
||||
} else {
|
||||
if (utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) {
|
||||
satoshiBalanceSpendable += utxo.value;
|
||||
} else {
|
||||
satoshiBalancePending += utxo.value;
|
||||
}
|
||||
}
|
||||
|
||||
outputArray.add(utxo);
|
||||
}
|
||||
}
|
||||
|
@ -1769,27 +1795,20 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
Logging.instance
|
||||
.log('Outputs fetched: $outputArray', level: LogLevel.Info);
|
||||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
await db.updateUTXOs(walletId, outputArray);
|
||||
|
||||
// finally update balance
|
||||
_balance = Balance(
|
||||
coin: coin,
|
||||
total: satoshiBalanceTotal,
|
||||
spendable: satoshiBalanceSpendable,
|
||||
blockedTotal: satoshiBalanceBlocked,
|
||||
pendingSpendable: satoshiBalancePending,
|
||||
);
|
||||
await updateCachedBalance(_balance!);
|
||||
await _updateBalance();
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
await refreshBalance();
|
||||
}
|
||||
|
||||
@override
|
||||
Balance get balance => _balance ??= getCachedBalance();
|
||||
Balance? _balance;
|
||||
|
@ -2322,11 +2341,12 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
/// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return
|
||||
/// a map containing the tx hex along with other important information. If not, then it will return
|
||||
/// an integer (1 or 2)
|
||||
dynamic coinSelection(
|
||||
int satoshiAmountToSend,
|
||||
int selectedTxFeeRate,
|
||||
String _recipientAddress,
|
||||
bool isSendAll, {
|
||||
dynamic coinSelection({
|
||||
required int satoshiAmountToSend,
|
||||
required int selectedTxFeeRate,
|
||||
required String recipientAddress,
|
||||
required bool coinControl,
|
||||
required bool isSendAll,
|
||||
int additionalOutputs = 0,
|
||||
List<isar_models.UTXO>? utxos,
|
||||
}) async {
|
||||
|
@ -2338,18 +2358,26 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
int spendableSatoshiValue = 0;
|
||||
|
||||
// Build list of spendable outputs and totaling their satoshi amount
|
||||
for (var i = 0; i < availableOutputs.length; i++) {
|
||||
if (availableOutputs[i].isBlocked == false &&
|
||||
availableOutputs[i]
|
||||
.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) ==
|
||||
true) {
|
||||
spendableOutputs.add(availableOutputs[i]);
|
||||
spendableSatoshiValue += availableOutputs[i].value;
|
||||
for (final utxo in availableOutputs) {
|
||||
if (utxo.isBlocked == false &&
|
||||
utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) &&
|
||||
utxo.used != true) {
|
||||
spendableOutputs.add(utxo);
|
||||
spendableSatoshiValue += utxo.value;
|
||||
}
|
||||
}
|
||||
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||
if (coinControl) {
|
||||
if (spendableOutputs.length < availableOutputs.length) {
|
||||
throw ArgumentError("Attempted to use an unavailable utxo");
|
||||
}
|
||||
}
|
||||
|
||||
// don't care about sorting if using all utxos
|
||||
if (!coinControl) {
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||
}
|
||||
|
||||
Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}",
|
||||
level: LogLevel.Info);
|
||||
|
@ -2378,19 +2406,26 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
int inputsBeingConsumed = 0;
|
||||
List<isar_models.UTXO> utxoObjectsToUse = [];
|
||||
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += spendableOutputs[i].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
|
||||
inputsBeingConsumed += 1;
|
||||
if (!coinControl) {
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend &&
|
||||
i < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += spendableOutputs[i].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs &&
|
||||
inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
} else {
|
||||
satoshisBeingUsed = spendableSatoshiValue;
|
||||
utxoObjectsToUse = spendableOutputs;
|
||||
}
|
||||
|
||||
Logging.instance
|
||||
|
@ -2403,7 +2438,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.log('satoshiAmountToSend $satoshiAmountToSend', level: LogLevel.Info);
|
||||
|
||||
// numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray
|
||||
List<String> recipientsArray = [_recipientAddress];
|
||||
List<String> recipientsArray = [recipientAddress];
|
||||
List<int> recipientsAmtArray = [satoshiAmountToSend];
|
||||
|
||||
// gather required signing data
|
||||
|
@ -2416,7 +2451,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
recipients: [recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
int feeForOneOutput = estimateTxFee(
|
||||
|
@ -2440,6 +2475,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
"recipientAmt": amount,
|
||||
"fee": feeForOneOutput,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2447,14 +2483,14 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
recipients: [recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
recipientAddress,
|
||||
await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
|
@ -2572,6 +2608,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": feeBeingPaid,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
} else {
|
||||
|
@ -2599,6 +2636,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2628,6 +2666,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2657,6 +2696,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": feeForOneOutput,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
} else {
|
||||
|
@ -2668,9 +2708,15 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
level: LogLevel.Warning);
|
||||
// try adding more outputs
|
||||
if (spendableOutputs.length > inputsBeingConsumed) {
|
||||
return coinSelection(satoshiAmountToSend, selectedTxFeeRate,
|
||||
_recipientAddress, isSendAll,
|
||||
additionalOutputs: additionalOutputs + 1, utxos: utxos);
|
||||
return coinSelection(
|
||||
satoshiAmountToSend: satoshiAmountToSend,
|
||||
selectedTxFeeRate: selectedTxFeeRate,
|
||||
recipientAddress: recipientAddress,
|
||||
isSendAll: isSendAll,
|
||||
additionalOutputs: additionalOutputs + 1,
|
||||
utxos: utxos,
|
||||
coinControl: coinControl,
|
||||
);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import 'package:stackwallet/services/event_bus/events/global/refresh_percent_cha
|
|||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_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/electrum_x_parsing.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||
|
@ -85,7 +86,7 @@ String constructDerivePath({
|
|||
}
|
||||
|
||||
class DogecoinWallet extends CoinServiceAPI
|
||||
with WalletCache, WalletDB, ElectrumXParsing {
|
||||
with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface {
|
||||
DogecoinWallet({
|
||||
required String walletId,
|
||||
required String walletName,
|
||||
|
@ -105,6 +106,17 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
_secureStore = secureStore;
|
||||
initCache(walletId, coin);
|
||||
initWalletDB(mockableOverride: mockableOverride);
|
||||
initCoinControlInterface(
|
||||
walletId: walletId,
|
||||
walletName: walletName,
|
||||
coin: coin,
|
||||
db: db,
|
||||
getChainHeight: () => chainHeight,
|
||||
refreshedBalanceCallback: (balance) async {
|
||||
_balance = balance;
|
||||
await updateCachedBalance(_balance!);
|
||||
},
|
||||
);
|
||||
|
||||
// paynym stuff
|
||||
// initPaynymWalletInterface(
|
||||
|
@ -907,6 +919,7 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
try {
|
||||
final feeRateType = args?["feeRate"];
|
||||
final feeRateAmount = args?["feeRateAmount"];
|
||||
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
|
||||
if (feeRateType is FeeRateType || feeRateAmount is int) {
|
||||
late final int rate;
|
||||
if (feeRateType is FeeRateType) {
|
||||
|
@ -933,9 +946,17 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
isSendAll = true;
|
||||
}
|
||||
|
||||
final result =
|
||||
await coinSelection(satoshiAmount, rate, address, isSendAll);
|
||||
Logging.instance.log("SEND RESULT: $result", level: LogLevel.Info);
|
||||
final result = await coinSelection(
|
||||
satoshiAmountToSend: satoshiAmount,
|
||||
selectedTxFeeRate: rate,
|
||||
recipientAddress: address,
|
||||
isSendAll: isSendAll,
|
||||
utxos: utxos?.toList(),
|
||||
coinControl: utxos is List<isar_models.UTXO>,
|
||||
);
|
||||
|
||||
Logging.instance
|
||||
.log("PREPARE SEND RESULT: $result", level: LogLevel.Info);
|
||||
if (result is int) {
|
||||
switch (result) {
|
||||
case 1:
|
||||
|
@ -981,6 +1002,12 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
final txHash = await _electrumXClient.broadcastTransaction(
|
||||
rawTx: txData["hex"] as String);
|
||||
Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info);
|
||||
|
||||
final utxos = txData["usedUTXOs"] as List<isar_models.UTXO>;
|
||||
|
||||
// mark utxos as used
|
||||
await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList());
|
||||
|
||||
return txHash;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s",
|
||||
|
@ -1537,18 +1564,14 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
}
|
||||
}
|
||||
|
||||
final currentChainHeight = await chainHeight;
|
||||
|
||||
final List<isar_models.UTXO> outputArray = [];
|
||||
int satoshiBalanceTotal = 0;
|
||||
int satoshiBalancePending = 0;
|
||||
int satoshiBalanceSpendable = 0;
|
||||
int satoshiBalanceBlocked = 0;
|
||||
|
||||
for (int i = 0; i < fetchedUtxoList.length; i++) {
|
||||
for (int j = 0; j < fetchedUtxoList[i].length; j++) {
|
||||
final jsonUTXO = fetchedUtxoList[i][j];
|
||||
|
||||
final txn = await cachedElectrumXClient.getTransaction(
|
||||
txHash: fetchedUtxoList[i][j]["tx_hash"] as String,
|
||||
txHash: jsonUTXO["tx_hash"] as String,
|
||||
verbose: true,
|
||||
coin: coin,
|
||||
);
|
||||
|
@ -1556,7 +1579,7 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
// fetch stored tx to see if paynym notification tx and block utxo
|
||||
final storedTx = await db.getTransaction(
|
||||
walletId,
|
||||
fetchedUtxoList[i][j]["tx_hash"] as String,
|
||||
jsonUTXO["tx_hash"] as String,
|
||||
);
|
||||
|
||||
bool shouldBlock = false;
|
||||
|
@ -1573,32 +1596,35 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
blockReason = "Incoming paynym notification transaction.";
|
||||
}
|
||||
|
||||
final vout = jsonUTXO["tx_pos"] as int;
|
||||
|
||||
final outputs = txn["vout"] as List;
|
||||
|
||||
String? utxoOwnerAddress;
|
||||
// get UTXO owner address
|
||||
for (final output in outputs) {
|
||||
if (output["n"] == vout) {
|
||||
utxoOwnerAddress =
|
||||
output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
}
|
||||
}
|
||||
|
||||
final utxo = isar_models.UTXO(
|
||||
walletId: walletId,
|
||||
txid: txn["txid"] as String,
|
||||
vout: fetchedUtxoList[i][j]["tx_pos"] as int,
|
||||
value: fetchedUtxoList[i][j]["value"] as int,
|
||||
vout: vout,
|
||||
value: jsonUTXO["value"] as int,
|
||||
name: "",
|
||||
isBlocked: shouldBlock,
|
||||
blockedReason: blockReason,
|
||||
isCoinbase: txn["is_coinbase"] as bool? ?? false,
|
||||
blockHash: txn["blockhash"] as String?,
|
||||
blockHeight: fetchedUtxoList[i][j]["height"] as int?,
|
||||
blockHeight: jsonUTXO["height"] as int?,
|
||||
blockTime: txn["blocktime"] as int?,
|
||||
address: utxoOwnerAddress,
|
||||
);
|
||||
|
||||
satoshiBalanceTotal += utxo.value;
|
||||
|
||||
if (utxo.isBlocked) {
|
||||
satoshiBalanceBlocked += utxo.value;
|
||||
} else {
|
||||
if (utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) {
|
||||
satoshiBalanceSpendable += utxo.value;
|
||||
} else {
|
||||
satoshiBalancePending += utxo.value;
|
||||
}
|
||||
}
|
||||
|
||||
outputArray.add(utxo);
|
||||
}
|
||||
}
|
||||
|
@ -1606,27 +1632,20 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
Logging.instance
|
||||
.log('Outputs fetched: $outputArray', level: LogLevel.Info);
|
||||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
await db.updateUTXOs(walletId, outputArray);
|
||||
|
||||
// finally update balance
|
||||
_balance = Balance(
|
||||
coin: coin,
|
||||
total: satoshiBalanceTotal,
|
||||
spendable: satoshiBalanceSpendable,
|
||||
blockedTotal: satoshiBalanceBlocked,
|
||||
pendingSpendable: satoshiBalancePending,
|
||||
);
|
||||
await updateCachedBalance(_balance!);
|
||||
await _updateBalance();
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
await refreshBalance();
|
||||
}
|
||||
|
||||
@override
|
||||
Balance get balance => _balance ??= getCachedBalance();
|
||||
Balance? _balance;
|
||||
|
@ -2022,11 +2041,12 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
/// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return
|
||||
/// a map containing the tx hex along with other important information. If not, then it will return
|
||||
/// an integer (1 or 2)
|
||||
dynamic coinSelection(
|
||||
int satoshiAmountToSend,
|
||||
int selectedTxFeeRate,
|
||||
String _recipientAddress,
|
||||
bool isSendAll, {
|
||||
dynamic coinSelection({
|
||||
required int satoshiAmountToSend,
|
||||
required int selectedTxFeeRate,
|
||||
required String recipientAddress,
|
||||
required bool coinControl,
|
||||
required bool isSendAll,
|
||||
int additionalOutputs = 0,
|
||||
List<isar_models.UTXO>? utxos,
|
||||
}) async {
|
||||
|
@ -2038,18 +2058,26 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
int spendableSatoshiValue = 0;
|
||||
|
||||
// Build list of spendable outputs and totaling their satoshi amount
|
||||
for (var i = 0; i < availableOutputs.length; i++) {
|
||||
if (availableOutputs[i].isBlocked == false &&
|
||||
availableOutputs[i]
|
||||
.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) ==
|
||||
true) {
|
||||
spendableOutputs.add(availableOutputs[i]);
|
||||
spendableSatoshiValue += availableOutputs[i].value;
|
||||
for (final utxo in availableOutputs) {
|
||||
if (utxo.isBlocked == false &&
|
||||
utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) &&
|
||||
utxo.used != true) {
|
||||
spendableOutputs.add(utxo);
|
||||
spendableSatoshiValue += utxo.value;
|
||||
}
|
||||
}
|
||||
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||
if (coinControl) {
|
||||
if (spendableOutputs.length < availableOutputs.length) {
|
||||
throw ArgumentError("Attempted to use an unavailable utxo");
|
||||
}
|
||||
}
|
||||
|
||||
// don't care about sorting if using all utxos
|
||||
if (!coinControl) {
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||
}
|
||||
|
||||
Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}",
|
||||
level: LogLevel.Info);
|
||||
|
@ -2078,19 +2106,26 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
int inputsBeingConsumed = 0;
|
||||
List<isar_models.UTXO> utxoObjectsToUse = [];
|
||||
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += spendableOutputs[i].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
|
||||
inputsBeingConsumed += 1;
|
||||
if (!coinControl) {
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend &&
|
||||
i < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += spendableOutputs[i].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs &&
|
||||
inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
} else {
|
||||
satoshisBeingUsed = spendableSatoshiValue;
|
||||
utxoObjectsToUse = spendableOutputs;
|
||||
}
|
||||
|
||||
Logging.instance
|
||||
|
@ -2103,7 +2138,7 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
.log('satoshiAmountToSend $satoshiAmountToSend', level: LogLevel.Info);
|
||||
|
||||
// numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray
|
||||
List<String> recipientsArray = [_recipientAddress];
|
||||
List<String> recipientsArray = [recipientAddress];
|
||||
List<int> recipientsAmtArray = [satoshiAmountToSend];
|
||||
|
||||
// gather required signing data
|
||||
|
@ -2116,7 +2151,7 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
recipients: [recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
int feeForOneOutput = estimateTxFee(
|
||||
|
@ -2140,6 +2175,7 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": amount,
|
||||
"fee": feeForOneOutput,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2147,14 +2183,14 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
recipients: [recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
recipientAddress,
|
||||
await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
|
@ -2272,6 +2308,7 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": feeBeingPaid,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
} else {
|
||||
|
@ -2299,6 +2336,7 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2328,6 +2366,7 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2357,6 +2396,7 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": feeForOneOutput,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
} else {
|
||||
|
@ -2368,9 +2408,15 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
level: LogLevel.Warning);
|
||||
// try adding more outputs
|
||||
if (spendableOutputs.length > inputsBeingConsumed) {
|
||||
return coinSelection(satoshiAmountToSend, selectedTxFeeRate,
|
||||
_recipientAddress, isSendAll,
|
||||
additionalOutputs: additionalOutputs + 1, utxos: utxos);
|
||||
return coinSelection(
|
||||
satoshiAmountToSend: satoshiAmountToSend,
|
||||
selectedTxFeeRate: selectedTxFeeRate,
|
||||
recipientAddress: recipientAddress,
|
||||
isSendAll: isSendAll,
|
||||
additionalOutputs: additionalOutputs + 1,
|
||||
utxos: utxos,
|
||||
coinControl: coinControl,
|
||||
);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import 'package:stackwallet/services/event_bus/events/global/refresh_percent_cha
|
|||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_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/electrum_x_parsing.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||
|
@ -90,7 +91,7 @@ String constructDerivePath({
|
|||
}
|
||||
|
||||
class LitecoinWallet extends CoinServiceAPI
|
||||
with WalletCache, WalletDB, ElectrumXParsing {
|
||||
with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface {
|
||||
LitecoinWallet({
|
||||
required String walletId,
|
||||
required String walletName,
|
||||
|
@ -110,6 +111,17 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
_secureStore = secureStore;
|
||||
initCache(walletId, coin);
|
||||
initWalletDB(mockableOverride: mockableOverride);
|
||||
initCoinControlInterface(
|
||||
walletId: walletId,
|
||||
walletName: walletName,
|
||||
coin: coin,
|
||||
db: db,
|
||||
getChainHeight: () => chainHeight,
|
||||
refreshedBalanceCallback: (balance) async {
|
||||
_balance = balance;
|
||||
await updateCachedBalance(_balance!);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static const integrationTestFlag =
|
||||
|
@ -1018,6 +1030,7 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
try {
|
||||
final feeRateType = args?["feeRate"];
|
||||
final feeRateAmount = args?["feeRateAmount"];
|
||||
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
|
||||
if (feeRateType is FeeRateType || feeRateAmount is int) {
|
||||
late final int rate;
|
||||
if (feeRateType is FeeRateType) {
|
||||
|
@ -1045,8 +1058,14 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
isSendAll = true;
|
||||
}
|
||||
|
||||
final txData =
|
||||
await coinSelection(satoshiAmount, rate, address, isSendAll);
|
||||
final txData = await coinSelection(
|
||||
satoshiAmountToSend: satoshiAmount,
|
||||
selectedTxFeeRate: rate,
|
||||
recipientAddress: address,
|
||||
isSendAll: isSendAll,
|
||||
utxos: utxos?.toList(),
|
||||
coinControl: utxos is List<isar_models.UTXO>,
|
||||
);
|
||||
|
||||
Logging.instance.log("prepare send: $txData", level: LogLevel.Info);
|
||||
try {
|
||||
|
@ -1109,6 +1128,11 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex);
|
||||
Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info);
|
||||
|
||||
final utxos = txData["usedUTXOs"] as List<isar_models.UTXO>;
|
||||
|
||||
// mark utxos as used
|
||||
await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList());
|
||||
|
||||
return txHash;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s",
|
||||
|
@ -1724,49 +1748,47 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
}
|
||||
}
|
||||
|
||||
final currentChainHeight = await chainHeight;
|
||||
|
||||
final List<isar_models.UTXO> outputArray = [];
|
||||
int satoshiBalanceTotal = 0;
|
||||
int satoshiBalancePending = 0;
|
||||
int satoshiBalanceSpendable = 0;
|
||||
int satoshiBalanceBlocked = 0;
|
||||
|
||||
for (int i = 0; i < fetchedUtxoList.length; i++) {
|
||||
for (int j = 0; j < fetchedUtxoList[i].length; j++) {
|
||||
final jsonUTXO = fetchedUtxoList[i][j];
|
||||
|
||||
final txn = await cachedElectrumXClient.getTransaction(
|
||||
txHash: fetchedUtxoList[i][j]["tx_hash"] as String,
|
||||
txHash: jsonUTXO["tx_hash"] as String,
|
||||
verbose: true,
|
||||
coin: coin,
|
||||
);
|
||||
|
||||
// todo check here if we should mark as blocked
|
||||
final vout = jsonUTXO["tx_pos"] as int;
|
||||
|
||||
final outputs = txn["vout"] as List;
|
||||
|
||||
String? utxoOwnerAddress;
|
||||
// get UTXO owner address
|
||||
for (final output in outputs) {
|
||||
if (output["n"] == vout) {
|
||||
utxoOwnerAddress =
|
||||
output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
}
|
||||
}
|
||||
|
||||
final utxo = isar_models.UTXO(
|
||||
walletId: walletId,
|
||||
txid: txn["txid"] as String,
|
||||
vout: fetchedUtxoList[i][j]["tx_pos"] as int,
|
||||
value: fetchedUtxoList[i][j]["value"] as int,
|
||||
vout: vout,
|
||||
value: jsonUTXO["value"] as int,
|
||||
name: "",
|
||||
isBlocked: false,
|
||||
blockedReason: null,
|
||||
isCoinbase: txn["is_coinbase"] as bool? ?? false,
|
||||
blockHash: txn["blockhash"] as String?,
|
||||
blockHeight: fetchedUtxoList[i][j]["height"] as int?,
|
||||
blockHeight: jsonUTXO["height"] as int?,
|
||||
blockTime: txn["blocktime"] as int?,
|
||||
address: utxoOwnerAddress,
|
||||
);
|
||||
|
||||
satoshiBalanceTotal += utxo.value;
|
||||
|
||||
if (utxo.isBlocked) {
|
||||
satoshiBalanceBlocked += utxo.value;
|
||||
} else {
|
||||
if (utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) {
|
||||
satoshiBalanceSpendable += utxo.value;
|
||||
} else {
|
||||
satoshiBalancePending += utxo.value;
|
||||
}
|
||||
}
|
||||
|
||||
outputArray.add(utxo);
|
||||
}
|
||||
}
|
||||
|
@ -1774,27 +1796,20 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
Logging.instance
|
||||
.log('Outputs fetched: $outputArray', level: LogLevel.Info);
|
||||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
await db.updateUTXOs(walletId, outputArray);
|
||||
|
||||
// finally update balance
|
||||
_balance = Balance(
|
||||
coin: coin,
|
||||
total: satoshiBalanceTotal,
|
||||
spendable: satoshiBalanceSpendable,
|
||||
blockedTotal: satoshiBalanceBlocked,
|
||||
pendingSpendable: satoshiBalancePending,
|
||||
);
|
||||
await updateCachedBalance(_balance!);
|
||||
await _updateBalance();
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
await refreshBalance();
|
||||
}
|
||||
|
||||
@override
|
||||
Balance get balance => _balance ??= getCachedBalance();
|
||||
Balance? _balance;
|
||||
|
@ -2214,11 +2229,12 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
/// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return
|
||||
/// a map containing the tx hex along with other important information. If not, then it will return
|
||||
/// an integer (1 or 2)
|
||||
dynamic coinSelection(
|
||||
int satoshiAmountToSend,
|
||||
int selectedTxFeeRate,
|
||||
String _recipientAddress,
|
||||
bool isSendAll, {
|
||||
dynamic coinSelection({
|
||||
required int satoshiAmountToSend,
|
||||
required int selectedTxFeeRate,
|
||||
required String recipientAddress,
|
||||
required bool coinControl,
|
||||
required bool isSendAll,
|
||||
int additionalOutputs = 0,
|
||||
List<isar_models.UTXO>? utxos,
|
||||
}) async {
|
||||
|
@ -2230,18 +2246,26 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
int spendableSatoshiValue = 0;
|
||||
|
||||
// Build list of spendable outputs and totaling their satoshi amount
|
||||
for (var i = 0; i < availableOutputs.length; i++) {
|
||||
if (availableOutputs[i].isBlocked == false &&
|
||||
availableOutputs[i]
|
||||
.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) ==
|
||||
true) {
|
||||
spendableOutputs.add(availableOutputs[i]);
|
||||
spendableSatoshiValue += availableOutputs[i].value;
|
||||
for (final utxo in availableOutputs) {
|
||||
if (utxo.isBlocked == false &&
|
||||
utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) &&
|
||||
utxo.used != true) {
|
||||
spendableOutputs.add(utxo);
|
||||
spendableSatoshiValue += utxo.value;
|
||||
}
|
||||
}
|
||||
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||
if (coinControl) {
|
||||
if (spendableOutputs.length < availableOutputs.length) {
|
||||
throw ArgumentError("Attempted to use an unavailable utxo");
|
||||
}
|
||||
}
|
||||
|
||||
// don't care about sorting if using all utxos
|
||||
if (!coinControl) {
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||
}
|
||||
|
||||
Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}",
|
||||
level: LogLevel.Info);
|
||||
|
@ -2270,19 +2294,26 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
int inputsBeingConsumed = 0;
|
||||
List<isar_models.UTXO> utxoObjectsToUse = [];
|
||||
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += spendableOutputs[i].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
|
||||
inputsBeingConsumed += 1;
|
||||
if (!coinControl) {
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend &&
|
||||
i < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += spendableOutputs[i].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs &&
|
||||
inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
} else {
|
||||
satoshisBeingUsed = spendableSatoshiValue;
|
||||
utxoObjectsToUse = spendableOutputs;
|
||||
}
|
||||
|
||||
Logging.instance
|
||||
|
@ -2293,7 +2324,7 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
.log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info);
|
||||
|
||||
// numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray
|
||||
List<String> recipientsArray = [_recipientAddress];
|
||||
List<String> recipientsArray = [recipientAddress];
|
||||
List<int> recipientsAmtArray = [satoshiAmountToSend];
|
||||
|
||||
// gather required signing data
|
||||
|
@ -2306,7 +2337,7 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
recipients: [recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
int feeForOneOutput = estimateTxFee(
|
||||
|
@ -2333,6 +2364,7 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": amount,
|
||||
"fee": feeForOneOutput,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2340,14 +2372,14 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
recipients: [recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
recipientAddress,
|
||||
await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
|
@ -2451,6 +2483,7 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": feeBeingPaid,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
} else {
|
||||
|
@ -2478,6 +2511,7 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2507,6 +2541,7 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2536,6 +2571,7 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": feeForOneOutput,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
} else {
|
||||
|
@ -2547,9 +2583,15 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
level: LogLevel.Warning);
|
||||
// try adding more outputs
|
||||
if (spendableOutputs.length > inputsBeingConsumed) {
|
||||
return coinSelection(satoshiAmountToSend, selectedTxFeeRate,
|
||||
_recipientAddress, isSendAll,
|
||||
additionalOutputs: additionalOutputs + 1, utxos: utxos);
|
||||
return coinSelection(
|
||||
satoshiAmountToSend: satoshiAmountToSend,
|
||||
selectedTxFeeRate: selectedTxFeeRate,
|
||||
recipientAddress: recipientAddress,
|
||||
isSendAll: isSendAll,
|
||||
additionalOutputs: additionalOutputs + 1,
|
||||
utxos: utxos,
|
||||
coinControl: coinControl,
|
||||
);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:stackwallet/services/coins/coin_service.dart';
|
|||
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
||||
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/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
@ -229,6 +230,8 @@ class Manager with ChangeNotifier {
|
|||
|
||||
bool get hasPaynymSupport => _currentWallet is PaynymWalletInterface;
|
||||
|
||||
bool get hasCoinControlSupport => _currentWallet is CoinControlInterface;
|
||||
|
||||
int get rescanOnOpenVersion =>
|
||||
DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNameDBInfo,
|
||||
|
|
|
@ -23,6 +23,7 @@ import 'package:stackwallet/services/event_bus/events/global/refresh_percent_cha
|
|||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_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/electrum_x_parsing.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||
|
@ -87,7 +88,7 @@ String constructDerivePath({
|
|||
}
|
||||
|
||||
class NamecoinWallet extends CoinServiceAPI
|
||||
with WalletCache, WalletDB, ElectrumXParsing {
|
||||
with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface {
|
||||
NamecoinWallet({
|
||||
required String walletId,
|
||||
required String walletName,
|
||||
|
@ -107,6 +108,17 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
_secureStore = secureStore;
|
||||
initCache(walletId, coin);
|
||||
initWalletDB(mockableOverride: mockableOverride);
|
||||
initCoinControlInterface(
|
||||
walletId: walletId,
|
||||
walletName: walletName,
|
||||
coin: coin,
|
||||
db: db,
|
||||
getChainHeight: () => chainHeight,
|
||||
refreshedBalanceCallback: (balance) async {
|
||||
_balance = balance;
|
||||
await updateCachedBalance(_balance!);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static const integrationTestFlag =
|
||||
|
@ -1009,6 +1021,7 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
try {
|
||||
final feeRateType = args?["feeRate"];
|
||||
final feeRateAmount = args?["feeRateAmount"];
|
||||
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
|
||||
if (feeRateType is FeeRateType || feeRateAmount is int) {
|
||||
late final int rate;
|
||||
if (feeRateType is FeeRateType) {
|
||||
|
@ -1036,8 +1049,14 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
isSendAll = true;
|
||||
}
|
||||
|
||||
final txData =
|
||||
await coinSelection(satoshiAmount, rate, address, isSendAll);
|
||||
final txData = await coinSelection(
|
||||
satoshiAmountToSend: satoshiAmount,
|
||||
selectedTxFeeRate: rate,
|
||||
recipientAddress: address,
|
||||
isSendAll: isSendAll,
|
||||
utxos: utxos?.toList(),
|
||||
coinControl: utxos is List<isar_models.UTXO>,
|
||||
);
|
||||
|
||||
Logging.instance.log("prepare send: $txData", level: LogLevel.Info);
|
||||
try {
|
||||
|
@ -1100,6 +1119,11 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex);
|
||||
Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info);
|
||||
|
||||
final utxos = txData["usedUTXOs"] as List<isar_models.UTXO>;
|
||||
|
||||
// mark utxos as used
|
||||
await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList());
|
||||
|
||||
return txHash;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s",
|
||||
|
@ -1709,49 +1733,47 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
}
|
||||
}
|
||||
|
||||
final currentChainHeight = await chainHeight;
|
||||
|
||||
final List<isar_models.UTXO> outputArray = [];
|
||||
int satoshiBalanceTotal = 0;
|
||||
int satoshiBalancePending = 0;
|
||||
int satoshiBalanceSpendable = 0;
|
||||
int satoshiBalanceBlocked = 0;
|
||||
|
||||
for (int i = 0; i < fetchedUtxoList.length; i++) {
|
||||
for (int j = 0; j < fetchedUtxoList[i].length; j++) {
|
||||
final jsonUTXO = fetchedUtxoList[i][j];
|
||||
|
||||
final txn = await cachedElectrumXClient.getTransaction(
|
||||
txHash: fetchedUtxoList[i][j]["tx_hash"] as String,
|
||||
txHash: jsonUTXO["tx_hash"] as String,
|
||||
verbose: true,
|
||||
coin: coin,
|
||||
);
|
||||
|
||||
// todo check here if we should mark as blocked
|
||||
final vout = jsonUTXO["tx_pos"] as int;
|
||||
|
||||
final outputs = txn["vout"] as List;
|
||||
|
||||
String? utxoOwnerAddress;
|
||||
// get UTXO owner address
|
||||
for (final output in outputs) {
|
||||
if (output["n"] == vout) {
|
||||
utxoOwnerAddress =
|
||||
output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
}
|
||||
}
|
||||
|
||||
final utxo = isar_models.UTXO(
|
||||
walletId: walletId,
|
||||
txid: txn["txid"] as String,
|
||||
vout: fetchedUtxoList[i][j]["tx_pos"] as int,
|
||||
value: fetchedUtxoList[i][j]["value"] as int,
|
||||
vout: vout,
|
||||
value: jsonUTXO["value"] as int,
|
||||
name: "",
|
||||
isBlocked: false,
|
||||
blockedReason: null,
|
||||
isCoinbase: txn["is_coinbase"] as bool? ?? false,
|
||||
blockHash: txn["blockhash"] as String?,
|
||||
blockHeight: fetchedUtxoList[i][j]["height"] as int?,
|
||||
blockHeight: jsonUTXO["height"] as int?,
|
||||
blockTime: txn["blocktime"] as int?,
|
||||
address: utxoOwnerAddress,
|
||||
);
|
||||
|
||||
satoshiBalanceTotal += utxo.value;
|
||||
|
||||
if (utxo.isBlocked) {
|
||||
satoshiBalanceBlocked += utxo.value;
|
||||
} else {
|
||||
if (utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) {
|
||||
satoshiBalanceSpendable += utxo.value;
|
||||
} else {
|
||||
satoshiBalancePending += utxo.value;
|
||||
}
|
||||
}
|
||||
|
||||
outputArray.add(utxo);
|
||||
}
|
||||
}
|
||||
|
@ -1759,27 +1781,20 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
Logging.instance
|
||||
.log('Outputs fetched: $outputArray', level: LogLevel.Info);
|
||||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
await db.updateUTXOs(walletId, outputArray);
|
||||
|
||||
// finally update balance
|
||||
_balance = Balance(
|
||||
coin: coin,
|
||||
total: satoshiBalanceTotal,
|
||||
spendable: satoshiBalanceSpendable,
|
||||
blockedTotal: satoshiBalanceBlocked,
|
||||
pendingSpendable: satoshiBalancePending,
|
||||
);
|
||||
await updateCachedBalance(_balance!);
|
||||
await _updateBalance();
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
await refreshBalance();
|
||||
}
|
||||
|
||||
@override
|
||||
Balance get balance => _balance ??= getCachedBalance();
|
||||
Balance? _balance;
|
||||
|
@ -2207,11 +2222,12 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
/// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return
|
||||
/// a map containing the tx hex along with other important information. If not, then it will return
|
||||
/// an integer (1 or 2)
|
||||
dynamic coinSelection(
|
||||
int satoshiAmountToSend,
|
||||
int selectedTxFeeRate,
|
||||
String _recipientAddress,
|
||||
bool isSendAll, {
|
||||
dynamic coinSelection({
|
||||
required int satoshiAmountToSend,
|
||||
required int selectedTxFeeRate,
|
||||
required String recipientAddress,
|
||||
required bool coinControl,
|
||||
required bool isSendAll,
|
||||
int additionalOutputs = 0,
|
||||
List<isar_models.UTXO>? utxos,
|
||||
}) async {
|
||||
|
@ -2223,18 +2239,26 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
int spendableSatoshiValue = 0;
|
||||
|
||||
// Build list of spendable outputs and totaling their satoshi amount
|
||||
for (var i = 0; i < availableOutputs.length; i++) {
|
||||
if (availableOutputs[i].isBlocked == false &&
|
||||
availableOutputs[i]
|
||||
.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) ==
|
||||
true) {
|
||||
spendableOutputs.add(availableOutputs[i]);
|
||||
spendableSatoshiValue += availableOutputs[i].value;
|
||||
for (final utxo in availableOutputs) {
|
||||
if (utxo.isBlocked == false &&
|
||||
utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) &&
|
||||
utxo.used != true) {
|
||||
spendableOutputs.add(utxo);
|
||||
spendableSatoshiValue += utxo.value;
|
||||
}
|
||||
}
|
||||
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||
if (coinControl) {
|
||||
if (spendableOutputs.length < availableOutputs.length) {
|
||||
throw ArgumentError("Attempted to use an unavailable utxo");
|
||||
}
|
||||
}
|
||||
|
||||
// don't care about sorting if using all utxos
|
||||
if (!coinControl) {
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||
}
|
||||
|
||||
Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}",
|
||||
level: LogLevel.Info);
|
||||
|
@ -2263,19 +2287,26 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
int inputsBeingConsumed = 0;
|
||||
List<isar_models.UTXO> utxoObjectsToUse = [];
|
||||
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += spendableOutputs[i].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
|
||||
inputsBeingConsumed += 1;
|
||||
if (!coinControl) {
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend &&
|
||||
i < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += spendableOutputs[i].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs &&
|
||||
inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
} else {
|
||||
satoshisBeingUsed = spendableSatoshiValue;
|
||||
utxoObjectsToUse = spendableOutputs;
|
||||
}
|
||||
|
||||
Logging.instance
|
||||
|
@ -2286,7 +2317,7 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
.log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info);
|
||||
|
||||
// numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray
|
||||
List<String> recipientsArray = [_recipientAddress];
|
||||
List<String> recipientsArray = [recipientAddress];
|
||||
List<int> recipientsAmtArray = [satoshiAmountToSend];
|
||||
|
||||
// gather required signing data
|
||||
|
@ -2299,7 +2330,7 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
recipients: [recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
int feeForOneOutput = estimateTxFee(
|
||||
|
@ -2326,6 +2357,7 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": amount,
|
||||
"fee": feeForOneOutput,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2333,14 +2365,14 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
recipients: [recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
recipientAddress,
|
||||
await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
|
@ -2444,6 +2476,7 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": feeBeingPaid,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
} else {
|
||||
|
@ -2471,6 +2504,7 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2500,6 +2534,7 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2529,6 +2564,7 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": feeForOneOutput,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
} else {
|
||||
|
@ -2540,9 +2576,15 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
level: LogLevel.Warning);
|
||||
// try adding more outputs
|
||||
if (spendableOutputs.length > inputsBeingConsumed) {
|
||||
return coinSelection(satoshiAmountToSend, selectedTxFeeRate,
|
||||
_recipientAddress, isSendAll,
|
||||
additionalOutputs: additionalOutputs + 1, utxos: utxos);
|
||||
return coinSelection(
|
||||
satoshiAmountToSend: satoshiAmountToSend,
|
||||
selectedTxFeeRate: selectedTxFeeRate,
|
||||
recipientAddress: recipientAddress,
|
||||
isSendAll: isSendAll,
|
||||
additionalOutputs: additionalOutputs + 1,
|
||||
utxos: utxos,
|
||||
coinControl: coinControl,
|
||||
);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import 'package:stackwallet/services/event_bus/events/global/refresh_percent_cha
|
|||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_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/wallet_cache.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||
import 'package:stackwallet/services/node_service.dart';
|
||||
|
@ -81,7 +82,8 @@ String constructDerivePath({
|
|||
return "m/$purpose'/$coinType'/$account'/$chain/$index";
|
||||
}
|
||||
|
||||
class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||
class ParticlWallet extends CoinServiceAPI
|
||||
with WalletCache, WalletDB, CoinControlInterface {
|
||||
ParticlWallet({
|
||||
required String walletId,
|
||||
required String walletName,
|
||||
|
@ -101,6 +103,17 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
_secureStore = secureStore;
|
||||
initCache(walletId, coin);
|
||||
initWalletDB(mockableOverride: mockableOverride);
|
||||
initCoinControlInterface(
|
||||
walletId: walletId,
|
||||
walletName: walletName,
|
||||
coin: coin,
|
||||
db: db,
|
||||
getChainHeight: () => chainHeight,
|
||||
refreshedBalanceCallback: (balance) async {
|
||||
_balance = balance;
|
||||
await updateCachedBalance(_balance!);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static const integrationTestFlag =
|
||||
|
@ -935,6 +948,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
try {
|
||||
final feeRateType = args?["feeRate"];
|
||||
final feeRateAmount = args?["feeRateAmount"];
|
||||
final utxos = args?["UTXOs"] as Set<isar_models.UTXO>?;
|
||||
if (feeRateType is FeeRateType || feeRateAmount is int) {
|
||||
late final int rate;
|
||||
if (feeRateType is FeeRateType) {
|
||||
|
@ -962,8 +976,14 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
isSendAll = true;
|
||||
}
|
||||
|
||||
final txData =
|
||||
await coinSelection(satoshiAmount, rate, address, isSendAll);
|
||||
final txData = await coinSelection(
|
||||
satoshiAmountToSend: satoshiAmount,
|
||||
selectedTxFeeRate: rate,
|
||||
recipientAddress: address,
|
||||
isSendAll: isSendAll,
|
||||
utxos: utxos?.toList(),
|
||||
coinControl: utxos is List<isar_models.UTXO>,
|
||||
);
|
||||
|
||||
Logging.instance.log("prepare send: $txData", level: LogLevel.Info);
|
||||
try {
|
||||
|
@ -1026,6 +1046,11 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex);
|
||||
Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info);
|
||||
|
||||
final utxos = txData["usedUTXOs"] as List<isar_models.UTXO>;
|
||||
|
||||
// mark utxos as used
|
||||
await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList());
|
||||
|
||||
return txHash;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s",
|
||||
|
@ -1595,49 +1620,47 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
}
|
||||
|
||||
final currentChainHeight = await chainHeight;
|
||||
|
||||
final List<isar_models.UTXO> outputArray = [];
|
||||
int satoshiBalanceTotal = 0;
|
||||
int satoshiBalancePending = 0;
|
||||
int satoshiBalanceSpendable = 0;
|
||||
int satoshiBalanceBlocked = 0;
|
||||
|
||||
for (int i = 0; i < fetchedUtxoList.length; i++) {
|
||||
for (int j = 0; j < fetchedUtxoList[i].length; j++) {
|
||||
final jsonUTXO = fetchedUtxoList[i][j];
|
||||
|
||||
final txn = await cachedElectrumXClient.getTransaction(
|
||||
txHash: fetchedUtxoList[i][j]["tx_hash"] as String,
|
||||
txHash: jsonUTXO["tx_hash"] as String,
|
||||
verbose: true,
|
||||
coin: coin,
|
||||
);
|
||||
|
||||
// todo check here if we should mark as blocked
|
||||
final vout = jsonUTXO["tx_pos"] as int;
|
||||
|
||||
final outputs = txn["vout"] as List;
|
||||
|
||||
String? utxoOwnerAddress;
|
||||
// get UTXO owner address
|
||||
for (final output in outputs) {
|
||||
if (output["n"] == vout) {
|
||||
utxoOwnerAddress =
|
||||
output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
}
|
||||
}
|
||||
|
||||
final utxo = isar_models.UTXO(
|
||||
walletId: walletId,
|
||||
txid: txn["txid"] as String,
|
||||
vout: fetchedUtxoList[i][j]["tx_pos"] as int,
|
||||
value: fetchedUtxoList[i][j]["value"] as int,
|
||||
vout: vout,
|
||||
value: jsonUTXO["value"] as int,
|
||||
name: "",
|
||||
isBlocked: false,
|
||||
blockedReason: null,
|
||||
isCoinbase: txn["is_coinbase"] as bool? ?? false,
|
||||
blockHash: txn["blockhash"] as String?,
|
||||
blockHeight: fetchedUtxoList[i][j]["height"] as int?,
|
||||
blockHeight: jsonUTXO["height"] as int?,
|
||||
blockTime: txn["blocktime"] as int?,
|
||||
address: utxoOwnerAddress,
|
||||
);
|
||||
|
||||
satoshiBalanceTotal += utxo.value;
|
||||
|
||||
if (utxo.isBlocked) {
|
||||
satoshiBalanceBlocked += utxo.value;
|
||||
} else {
|
||||
if (utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) {
|
||||
satoshiBalanceSpendable += utxo.value;
|
||||
} else {
|
||||
satoshiBalancePending += utxo.value;
|
||||
}
|
||||
}
|
||||
|
||||
outputArray.add(utxo);
|
||||
}
|
||||
}
|
||||
|
@ -1645,27 +1668,20 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
Logging.instance
|
||||
.log('Outputs fetched: $outputArray', level: LogLevel.Info);
|
||||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
await db.updateUTXOs(walletId, outputArray);
|
||||
|
||||
// finally update balance
|
||||
_balance = Balance(
|
||||
coin: coin,
|
||||
total: satoshiBalanceTotal,
|
||||
spendable: satoshiBalanceSpendable,
|
||||
blockedTotal: satoshiBalanceBlocked,
|
||||
pendingSpendable: satoshiBalancePending,
|
||||
);
|
||||
await updateCachedBalance(_balance!);
|
||||
await _updateBalance();
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
await refreshBalance();
|
||||
}
|
||||
|
||||
@override
|
||||
Balance get balance => _balance ??= getCachedBalance();
|
||||
Balance? _balance;
|
||||
|
@ -2367,11 +2383,12 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
/// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return
|
||||
/// a map containing the tx hex along with other important information. If not, then it will return
|
||||
/// an integer (1 or 2)
|
||||
dynamic coinSelection(
|
||||
int satoshiAmountToSend,
|
||||
int selectedTxFeeRate,
|
||||
String _recipientAddress,
|
||||
bool isSendAll, {
|
||||
dynamic coinSelection({
|
||||
required int satoshiAmountToSend,
|
||||
required int selectedTxFeeRate,
|
||||
required String recipientAddress,
|
||||
required bool coinControl,
|
||||
required bool isSendAll,
|
||||
int additionalOutputs = 0,
|
||||
List<isar_models.UTXO>? utxos,
|
||||
}) async {
|
||||
|
@ -2381,19 +2398,28 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final currentChainHeight = await chainHeight;
|
||||
final List<isar_models.UTXO> spendableOutputs = [];
|
||||
int spendableSatoshiValue = 0;
|
||||
|
||||
// Build list of spendable outputs and totaling their satoshi amount
|
||||
for (var i = 0; i < availableOutputs.length; i++) {
|
||||
if (availableOutputs[i].isBlocked == false &&
|
||||
availableOutputs[i]
|
||||
.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) ==
|
||||
true) {
|
||||
spendableOutputs.add(availableOutputs[i]);
|
||||
spendableSatoshiValue += availableOutputs[i].value;
|
||||
for (final utxo in availableOutputs) {
|
||||
if (utxo.isBlocked == false &&
|
||||
utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) &&
|
||||
utxo.used != true) {
|
||||
spendableOutputs.add(utxo);
|
||||
spendableSatoshiValue += utxo.value;
|
||||
}
|
||||
}
|
||||
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||
if (coinControl) {
|
||||
if (spendableOutputs.length < availableOutputs.length) {
|
||||
throw ArgumentError("Attempted to use an unavailable utxo");
|
||||
}
|
||||
}
|
||||
|
||||
// don't care about sorting if using all utxos
|
||||
if (!coinControl) {
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||
}
|
||||
|
||||
Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}",
|
||||
level: LogLevel.Info);
|
||||
|
@ -2422,19 +2448,26 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
int inputsBeingConsumed = 0;
|
||||
List<isar_models.UTXO> utxoObjectsToUse = [];
|
||||
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += spendableOutputs[i].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
|
||||
inputsBeingConsumed += 1;
|
||||
if (!coinControl) {
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend &&
|
||||
i < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += spendableOutputs[i].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs &&
|
||||
inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value;
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
} else {
|
||||
satoshisBeingUsed = spendableSatoshiValue;
|
||||
utxoObjectsToUse = spendableOutputs;
|
||||
}
|
||||
|
||||
Logging.instance
|
||||
|
@ -2445,7 +2478,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info);
|
||||
|
||||
// numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray
|
||||
List<String> recipientsArray = [_recipientAddress];
|
||||
List<String> recipientsArray = [recipientAddress];
|
||||
List<int> recipientsAmtArray = [satoshiAmountToSend];
|
||||
|
||||
// gather required signing data
|
||||
|
@ -2458,7 +2491,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
recipients: [recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
int feeForOneOutput = estimateTxFee(
|
||||
|
@ -2485,6 +2518,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
"recipientAmt": amount,
|
||||
"fee": feeForOneOutput,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2492,14 +2526,14 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
recipients: [recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
recipientAddress,
|
||||
await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
|
@ -2603,6 +2637,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": feeBeingPaid,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
} else {
|
||||
|
@ -2630,6 +2665,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2659,6 +2695,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
}
|
||||
|
@ -2688,6 +2725,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
"recipientAmt": recipientsAmtArray[0],
|
||||
"fee": feeForOneOutput,
|
||||
"vSize": txn["vSize"],
|
||||
"usedUTXOs": utxoObjectsToUse,
|
||||
};
|
||||
return transactionObject;
|
||||
} else {
|
||||
|
@ -2699,9 +2737,15 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
level: LogLevel.Warning);
|
||||
// try adding more outputs
|
||||
if (spendableOutputs.length > inputsBeingConsumed) {
|
||||
return coinSelection(satoshiAmountToSend, selectedTxFeeRate,
|
||||
_recipientAddress, isSendAll,
|
||||
additionalOutputs: additionalOutputs + 1, utxos: utxos);
|
||||
return coinSelection(
|
||||
satoshiAmountToSend: satoshiAmountToSend,
|
||||
selectedTxFeeRate: selectedTxFeeRate,
|
||||
recipientAddress: recipientAddress,
|
||||
isSendAll: isSendAll,
|
||||
additionalOutputs: additionalOutputs + 1,
|
||||
utxos: utxos,
|
||||
coinControl: coinControl,
|
||||
);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
class BalanceRefreshedEvent {
|
||||
final String walletId;
|
||||
|
||||
BalanceRefreshedEvent(this.walletId) {
|
||||
Logging.instance.log(
|
||||
"BalanceRefreshedEvent fired on $walletId",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
|
||||
import 'package:stackwallet/exceptions/exchange/majestic_bank/mb_exception.dart';
|
||||
import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart';
|
||||
import 'package:stackwallet/models/exchange/majestic_bank/mb_limit.dart';
|
||||
import 'package:stackwallet/models/exchange/majestic_bank/mb_order.dart';
|
||||
|
@ -335,6 +336,15 @@ class MajesticBankAPI {
|
|||
final jsonObject = await _makeGetRequest(uri);
|
||||
final json = Map<String, dynamic>.from(jsonObject as Map);
|
||||
|
||||
if (json.length == 2) {
|
||||
return ExchangeResponse(
|
||||
exception: MBException(
|
||||
json["status"] as String,
|
||||
ExchangeExceptionType.orderNotFound,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final status = MBOrderStatus(
|
||||
orderId: json["trx"] as String,
|
||||
status: json["status"] as String,
|
||||
|
|
|
@ -282,6 +282,33 @@ class MajesticBankExchange extends Exchange {
|
|||
|
||||
return ExchangeResponse(value: updatedTrade);
|
||||
} else {
|
||||
if (response.exception?.type == ExchangeExceptionType.orderNotFound) {
|
||||
final updatedTrade = Trade(
|
||||
uuid: trade.uuid,
|
||||
tradeId: trade.tradeId,
|
||||
rateType: trade.rateType,
|
||||
direction: trade.direction,
|
||||
timestamp: trade.timestamp,
|
||||
updatedAt: DateTime.now(),
|
||||
payInCurrency: trade.payInCurrency,
|
||||
payInAmount: trade.payInAmount,
|
||||
payInAddress: trade.payInAddress,
|
||||
payInNetwork: trade.payInNetwork,
|
||||
payInExtraId: trade.payInExtraId,
|
||||
payInTxid: trade.payInTxid,
|
||||
payOutCurrency: trade.payOutCurrency,
|
||||
payOutAmount: trade.payOutAmount,
|
||||
payOutAddress: trade.payOutAddress,
|
||||
payOutNetwork: trade.payOutNetwork,
|
||||
payOutExtraId: trade.payOutExtraId,
|
||||
payOutTxid: trade.payOutTxid,
|
||||
refundAddress: trade.refundAddress,
|
||||
refundExtraId: trade.refundExtraId,
|
||||
status: "Completed",
|
||||
exchangeName: exchangeName,
|
||||
);
|
||||
return ExchangeResponse(value: updatedTrade);
|
||||
}
|
||||
return ExchangeResponse(exception: response.exception);
|
||||
}
|
||||
}
|
||||
|
|
78
lib/services/mixins/coin_control_interface.dart
Normal file
78
lib/services/mixins/coin_control_interface.dart
Normal file
|
@ -0,0 +1,78 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/models/balance.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/balance_refreshed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
mixin CoinControlInterface {
|
||||
late final String _walletId;
|
||||
late final String _walletName;
|
||||
late final Coin _coin;
|
||||
late final MainDB _db;
|
||||
late final Future<int> Function() _getChainHeight;
|
||||
late final Future<void> Function(Balance) _refreshedBalanceCallback;
|
||||
|
||||
void initCoinControlInterface({
|
||||
required String walletId,
|
||||
required String walletName,
|
||||
required Coin coin,
|
||||
required MainDB db,
|
||||
required Future<int> Function() getChainHeight,
|
||||
required Future<void> Function(Balance) refreshedBalanceCallback,
|
||||
}) {
|
||||
_walletId = walletId;
|
||||
_walletName = walletName;
|
||||
_coin = coin;
|
||||
_db = db;
|
||||
_getChainHeight = getChainHeight;
|
||||
_refreshedBalanceCallback = refreshedBalanceCallback;
|
||||
}
|
||||
|
||||
Future<void> refreshBalance({bool notify = false}) async {
|
||||
final utxos = await _db.getUTXOs(_walletId).findAll();
|
||||
final currentChainHeight = await _getChainHeight();
|
||||
|
||||
int satoshiBalanceTotal = 0;
|
||||
int satoshiBalancePending = 0;
|
||||
int satoshiBalanceSpendable = 0;
|
||||
int satoshiBalanceBlocked = 0;
|
||||
|
||||
for (final utxo in utxos) {
|
||||
satoshiBalanceTotal += utxo.value;
|
||||
|
||||
if (utxo.isBlocked) {
|
||||
satoshiBalanceBlocked += utxo.value;
|
||||
} else {
|
||||
if (utxo.isConfirmed(
|
||||
currentChainHeight,
|
||||
_coin.requiredConfirmations,
|
||||
)) {
|
||||
satoshiBalanceSpendable += utxo.value;
|
||||
} else {
|
||||
satoshiBalancePending += utxo.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final balance = Balance(
|
||||
coin: _coin,
|
||||
total: satoshiBalanceTotal,
|
||||
spendable: satoshiBalanceSpendable,
|
||||
blockedTotal: satoshiBalanceBlocked,
|
||||
pendingSpendable: satoshiBalancePending,
|
||||
);
|
||||
|
||||
await _refreshedBalanceCallback(balance);
|
||||
|
||||
if (notify) {
|
||||
GlobalEventBus.instance.fire(
|
||||
BalanceRefreshedEvent(
|
||||
_walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -223,6 +223,9 @@ class NotificationsService extends ChangeNotifier {
|
|||
case "expired":
|
||||
case "Finished":
|
||||
case "finished":
|
||||
case "Completed":
|
||||
case "completed":
|
||||
case "Not found":
|
||||
shouldWatchForUpdates = false;
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -48,8 +48,20 @@ class _BUY {
|
|||
}
|
||||
}
|
||||
|
||||
class _COIN_CONTROL {
|
||||
const _COIN_CONTROL();
|
||||
|
||||
String get blocked => "assets/svg/coin_control/frozen.svg";
|
||||
String get unBlocked => "assets/svg/coin_control/unfrozen.svg";
|
||||
String get gamePad => "assets/svg/coin_control/gamepad.svg";
|
||||
String get selected => "assets/svg/coin_control/selected.svg";
|
||||
}
|
||||
|
||||
class _SVG {
|
||||
const _SVG();
|
||||
|
||||
final coinControl = const _COIN_CONTROL();
|
||||
|
||||
String? background(BuildContext context) {
|
||||
switch (Theme.of(context).extension<StackColors>()!.themeType) {
|
||||
case ThemeType.light:
|
||||
|
|
|
@ -14,6 +14,7 @@ import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'
|
|||
import 'package:stackwallet/services/coins/particl/particl_wallet.dart'
|
||||
as particl;
|
||||
import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart' as wow;
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
|
||||
enum Coin {
|
||||
bitcoin,
|
||||
|
@ -232,6 +233,8 @@ extension CoinExt on Coin {
|
|||
return nmc.MINIMUM_CONFIRMATIONS;
|
||||
}
|
||||
}
|
||||
|
||||
int get decimals => Constants.decimalPlacesForCoin(this);
|
||||
}
|
||||
|
||||
Coin coinFromPrettyName(String name) {
|
||||
|
|
|
@ -40,6 +40,7 @@ class Prefs extends ChangeNotifier {
|
|||
_familiarity = await _getHasFamiliarity();
|
||||
_userId = await _getUserId();
|
||||
_signupEpoch = await _getSignupEpoch();
|
||||
_enableCoinControl = await _getEnableCoinControl();
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
@ -645,4 +646,27 @@ class Prefs extends ChangeNotifier {
|
|||
boxName: DB.boxNamePrefs, key: "signupEpoch", value: _signupEpoch);
|
||||
// notifyListeners();
|
||||
}
|
||||
|
||||
// show testnet coins
|
||||
|
||||
bool _enableCoinControl = false;
|
||||
|
||||
bool get enableCoinControl => _enableCoinControl;
|
||||
|
||||
set enableCoinControl(bool enableCoinControl) {
|
||||
if (_enableCoinControl != enableCoinControl) {
|
||||
DB.instance.put<dynamic>(
|
||||
boxName: DB.boxNamePrefs,
|
||||
key: "enableCoinControl",
|
||||
value: enableCoinControl);
|
||||
_enableCoinControl = enableCoinControl;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _getEnableCoinControl() async {
|
||||
return await DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNamePrefs, key: "enableCoinControl") as bool? ??
|
||||
false;
|
||||
}
|
||||
}
|
||||
|
|
53
lib/widgets/custom_buttons/simple_copy_button.dart
Normal file
53
lib/widgets/custom_buttons/simple_copy_button.dart
Normal file
|
@ -0,0 +1,53 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
|
||||
class SimpleCopyButton extends StatelessWidget {
|
||||
const SimpleCopyButton({
|
||||
Key? key,
|
||||
required this.data,
|
||||
}) : super(key: key);
|
||||
|
||||
final String data;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
await Clipboard.setData(ClipboardData(text: data));
|
||||
if (context.mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
width: 10,
|
||||
height: 10,
|
||||
color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
Text(
|
||||
"Copy",
|
||||
style: STextStyles.link2(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
55
lib/widgets/custom_buttons/simple_edit_button.dart
Normal file
55
lib/widgets/custom_buttons/simple_edit_button.dart
Normal file
|
@ -0,0 +1,55 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/pages/generic/single_field_edit_view.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class SimpleEditButton extends StatelessWidget {
|
||||
const SimpleEditButton({
|
||||
Key? key,
|
||||
required this.editValue,
|
||||
required this.editLabel,
|
||||
required this.onValueChanged,
|
||||
}) : super(key: key);
|
||||
|
||||
final String editValue;
|
||||
final String editLabel;
|
||||
final void Function(String) onValueChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final result = await Navigator.of(context).pushNamed(
|
||||
SingleFieldEditView.routeName,
|
||||
arguments: Tuple2(
|
||||
editValue,
|
||||
editLabel,
|
||||
),
|
||||
);
|
||||
if (result is String && result != editValue) {
|
||||
onValueChanged(result);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.pencil,
|
||||
width: 10,
|
||||
height: 10,
|
||||
color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
Text(
|
||||
"Edit",
|
||||
style: STextStyles.link2(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
99
lib/widgets/icon_widgets/utxo_status_icon.dart
Normal file
99
lib/widgets/icon_widgets/utxo_status_icon.dart
Normal file
|
@ -0,0 +1,99 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||
|
||||
import '../../utilities/theme/stack_colors.dart';
|
||||
|
||||
enum UTXOStatusIconStatus {
|
||||
confirmed,
|
||||
unconfirmed;
|
||||
}
|
||||
|
||||
class UTXOStatusIcon extends StatelessWidget {
|
||||
const UTXOStatusIcon({
|
||||
Key? key,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.blocked,
|
||||
required this.selected,
|
||||
required this.status,
|
||||
required this.background,
|
||||
}) : super(key: key);
|
||||
|
||||
final double width;
|
||||
final double height;
|
||||
final bool blocked;
|
||||
final bool selected;
|
||||
final UTXOStatusIconStatus status;
|
||||
final Color background;
|
||||
|
||||
final _availableColor = const Color(0xFFF7931A);
|
||||
final _blockedColor = const Color(0xFF96B0D6);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConditionalParent(
|
||||
condition: status == UTXOStatusIconStatus.unconfirmed,
|
||||
builder: (child) => Stack(
|
||||
children: [
|
||||
child,
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Stack(
|
||||
children: [
|
||||
RoundedContainer(
|
||||
radiusMultiplier: 100,
|
||||
color: background,
|
||||
width: width / 2.8,
|
||||
height: height / 2.8,
|
||||
),
|
||||
Positioned(
|
||||
right: width / 2.8 - width / 3,
|
||||
left: width / 2.8 - width / 3,
|
||||
top: height / 2.8 - height / 3,
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.pending,
|
||||
width: width / 3,
|
||||
height: height / 3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
RoundedContainer(
|
||||
radiusMultiplier: 100,
|
||||
color: selected
|
||||
? Theme.of(context).extension<StackColors>()!.infoItemIcons
|
||||
: blocked
|
||||
? _blockedColor.withOpacity(0.3)
|
||||
: _availableColor.withOpacity(0.2),
|
||||
width: width,
|
||||
height: height,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
selected
|
||||
? Assets.svg.coinControl.selected
|
||||
: blocked
|
||||
? Assets.svg.coinControl.blocked
|
||||
: Assets.svg.coinControl.unBlocked,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: selected
|
||||
? Colors.white
|
||||
: blocked
|
||||
? _blockedColor
|
||||
: _availableColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -82,6 +82,8 @@ class _TransactionCardState extends ConsumerState<TransactionCard> {
|
|||
} else {
|
||||
return "Sending";
|
||||
}
|
||||
} else if (type == TransactionType.sentToSelf) {
|
||||
return "Sent to self";
|
||||
} else {
|
||||
return type.name;
|
||||
}
|
||||
|
|
|
@ -324,6 +324,9 @@ flutter:
|
|||
# coin icons
|
||||
- assets/svg/coin_icons/
|
||||
|
||||
# coin control icons
|
||||
- assets/svg/coin_control/
|
||||
|
||||
# lottie animations
|
||||
- assets/lottie/test.json
|
||||
- assets/lottie/test2.json
|
||||
|
|
|
@ -652,6 +652,19 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get enableCoinControl => (super.noSuchMethod(
|
||||
Invocation.getter(#enableCoinControl),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#enableCoinControl,
|
||||
enableCoinControl,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get hasListeners => (super.noSuchMethod(
|
||||
Invocation.getter(#hasListeners),
|
||||
returnValue: false,
|
||||
|
|
|
@ -373,6 +373,19 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get enableCoinControl => (super.noSuchMethod(
|
||||
Invocation.getter(#enableCoinControl),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#enableCoinControl,
|
||||
enableCoinControl,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get hasListeners => (super.noSuchMethod(
|
||||
Invocation.getter(#hasListeners),
|
||||
returnValue: false,
|
||||
|
|
|
@ -52,6 +52,7 @@ void main() {
|
|||
|
||||
when(mockLocaleService.locale).thenAnswer((_) => "en_US");
|
||||
when(mockPrefs.currency).thenAnswer((_) => "USD");
|
||||
when(mockPrefs.enableCoinControl).thenAnswer((_) => false);
|
||||
when(wallet.validateAddress("send to address"))
|
||||
.thenAnswer((realInvocation) => true);
|
||||
|
||||
|
@ -114,6 +115,7 @@ void main() {
|
|||
|
||||
when(mockLocaleService.locale).thenAnswer((_) => "en_US");
|
||||
when(mockPrefs.currency).thenAnswer((_) => "USD");
|
||||
when(mockPrefs.enableCoinControl).thenAnswer((_) => false);
|
||||
when(wallet.validateAddress("send to address"))
|
||||
.thenAnswer((realInvocation) => false);
|
||||
|
||||
|
|
|
@ -1415,23 +1415,24 @@ class MockBitcoinWallet extends _i1.Mock implements _i26.BitcoinWallet {
|
|||
returnValue: 0,
|
||||
) as int);
|
||||
@override
|
||||
dynamic coinSelection(
|
||||
int? satoshiAmountToSend,
|
||||
int? selectedTxFeeRate,
|
||||
String? _recipientAddress,
|
||||
bool? isSendAll, {
|
||||
dynamic coinSelection({
|
||||
required int? satoshiAmountToSend,
|
||||
required int? selectedTxFeeRate,
|
||||
required String? recipientAddress,
|
||||
required bool? coinControl,
|
||||
required bool? isSendAll,
|
||||
int? additionalOutputs = 0,
|
||||
List<_i16.UTXO>? utxos,
|
||||
}) =>
|
||||
super.noSuchMethod(Invocation.method(
|
||||
#coinSelection,
|
||||
[
|
||||
satoshiAmountToSend,
|
||||
selectedTxFeeRate,
|
||||
_recipientAddress,
|
||||
isSendAll,
|
||||
],
|
||||
[],
|
||||
{
|
||||
#satoshiAmountToSend: satoshiAmountToSend,
|
||||
#selectedTxFeeRate: selectedTxFeeRate,
|
||||
#recipientAddress: recipientAddress,
|
||||
#coinControl: coinControl,
|
||||
#isSendAll: isSendAll,
|
||||
#additionalOutputs: additionalOutputs,
|
||||
#utxos: utxos,
|
||||
},
|
||||
|
@ -1988,6 +1989,22 @@ class MockBitcoinWallet extends _i1.Mock implements _i26.BitcoinWallet {
|
|||
returnValue: _i22.Future<_i18.PaymentCode?>.value(),
|
||||
) as _i22.Future<_i18.PaymentCode?>);
|
||||
@override
|
||||
_i22.Future<_i18.PaymentCode?> unBlindedPaymentCodeFromTransactionBad({
|
||||
required _i16.Transaction? transaction,
|
||||
required _i16.Address? myNotificationAddress,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#unBlindedPaymentCodeFromTransactionBad,
|
||||
[],
|
||||
{
|
||||
#transaction: transaction,
|
||||
#myNotificationAddress: myNotificationAddress,
|
||||
},
|
||||
),
|
||||
returnValue: _i22.Future<_i18.PaymentCode?>.value(),
|
||||
) as _i22.Future<_i18.PaymentCode?>);
|
||||
@override
|
||||
_i22.Future<List<_i18.PaymentCode>>
|
||||
getAllPaymentCodesFromNotificationTransactions() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -2152,6 +2169,41 @@ class MockBitcoinWallet extends _i1.Mock implements _i26.BitcoinWallet {
|
|||
),
|
||||
returnValue: _i22.Future<String>.value(''),
|
||||
) as _i22.Future<String>);
|
||||
@override
|
||||
void initCoinControlInterface({
|
||||
required String? walletId,
|
||||
required String? walletName,
|
||||
required _i21.Coin? coin,
|
||||
required _i13.MainDB? db,
|
||||
required _i22.Future<int> Function()? getChainHeight,
|
||||
required _i22.Future<void> Function(_i12.Balance)? refreshedBalanceCallback,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#initCoinControlInterface,
|
||||
[],
|
||||
{
|
||||
#walletId: walletId,
|
||||
#walletName: walletName,
|
||||
#coin: coin,
|
||||
#db: db,
|
||||
#getChainHeight: getChainHeight,
|
||||
#refreshedBalanceCallback: refreshedBalanceCallback,
|
||||
},
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i22.Future<void> refreshBalance({bool? notify = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#refreshBalance,
|
||||
[],
|
||||
{#notify: notify},
|
||||
),
|
||||
returnValue: _i22.Future<void>.value(),
|
||||
returnValueForMissingStub: _i22.Future<void>.value(),
|
||||
) as _i22.Future<void>);
|
||||
}
|
||||
|
||||
/// A class which mocks [LocaleService].
|
||||
|
@ -2483,6 +2535,19 @@ class MockPrefs extends _i1.Mock implements _i23.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get enableCoinControl => (super.noSuchMethod(
|
||||
Invocation.getter(#enableCoinControl),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#enableCoinControl,
|
||||
enableCoinControl,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get hasListeners => (super.noSuchMethod(
|
||||
Invocation.getter(#hasListeners),
|
||||
returnValue: false,
|
||||
|
@ -2712,6 +2777,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -373,6 +373,11 @@ class MockManager extends _i1.Mock implements _i11.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -334,6 +334,11 @@ class MockManager extends _i1.Mock implements _i9.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -332,6 +332,11 @@ class MockManager extends _i1.Mock implements _i9.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -322,6 +322,19 @@ class MockPrefs extends _i1.Mock implements _i3.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get enableCoinControl => (super.noSuchMethod(
|
||||
Invocation.getter(#enableCoinControl),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#enableCoinControl,
|
||||
enableCoinControl,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get hasListeners => (super.noSuchMethod(
|
||||
Invocation.getter(#hasListeners),
|
||||
returnValue: false,
|
||||
|
|
|
@ -641,6 +641,11 @@ class MockManager extends _i1.Mock implements _i12.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -428,6 +428,11 @@ class MockManager extends _i1.Mock implements _i9.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -428,6 +428,11 @@ class MockManager extends _i1.Mock implements _i9.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -428,6 +428,11 @@ class MockManager extends _i1.Mock implements _i9.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -203,6 +203,11 @@ class MockManager extends _i1.Mock implements _i5.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -426,6 +426,11 @@ class MockManager extends _i1.Mock implements _i9.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -641,6 +641,11 @@ class MockManager extends _i1.Mock implements _i12.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -482,6 +482,11 @@ class MockManager extends _i1.Mock implements _i12.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -203,6 +203,11 @@ class MockManager extends _i1.Mock implements _i5.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -203,6 +203,11 @@ class MockManager extends _i1.Mock implements _i5.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -418,6 +418,11 @@ class MockManager extends _i1.Mock implements _i11.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -418,6 +418,11 @@ class MockManager extends _i1.Mock implements _i11.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -203,6 +203,11 @@ class MockManager extends _i1.Mock implements _i5.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -203,6 +203,11 @@ class MockManager extends _i1.Mock implements _i5.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -426,6 +426,11 @@ class MockManager extends _i1.Mock implements _i9.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -683,6 +683,11 @@ class MockManager extends _i1.Mock implements _i15.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -426,6 +426,11 @@ class MockManager extends _i1.Mock implements _i9.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -205,6 +205,11 @@ class MockManager extends _i1.Mock implements _i5.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -204,6 +204,11 @@ class MockManager extends _i1.Mock implements _i5.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -203,6 +203,11 @@ class MockManager extends _i1.Mock implements _i5.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -245,6 +245,11 @@ class MockManager extends _i1.Mock implements _i8.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -205,6 +205,11 @@ class MockManager extends _i1.Mock implements _i5.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -1207,23 +1207,24 @@ class MockBitcoinWallet extends _i1.Mock implements _i25.BitcoinWallet {
|
|||
returnValue: 0,
|
||||
) as int);
|
||||
@override
|
||||
dynamic coinSelection(
|
||||
int? satoshiAmountToSend,
|
||||
int? selectedTxFeeRate,
|
||||
String? _recipientAddress,
|
||||
bool? isSendAll, {
|
||||
dynamic coinSelection({
|
||||
required int? satoshiAmountToSend,
|
||||
required int? selectedTxFeeRate,
|
||||
required String? recipientAddress,
|
||||
required bool? coinControl,
|
||||
required bool? isSendAll,
|
||||
int? additionalOutputs = 0,
|
||||
List<_i15.UTXO>? utxos,
|
||||
}) =>
|
||||
super.noSuchMethod(Invocation.method(
|
||||
#coinSelection,
|
||||
[
|
||||
satoshiAmountToSend,
|
||||
selectedTxFeeRate,
|
||||
_recipientAddress,
|
||||
isSendAll,
|
||||
],
|
||||
[],
|
||||
{
|
||||
#satoshiAmountToSend: satoshiAmountToSend,
|
||||
#selectedTxFeeRate: selectedTxFeeRate,
|
||||
#recipientAddress: recipientAddress,
|
||||
#coinControl: coinControl,
|
||||
#isSendAll: isSendAll,
|
||||
#additionalOutputs: additionalOutputs,
|
||||
#utxos: utxos,
|
||||
},
|
||||
|
@ -1780,6 +1781,22 @@ class MockBitcoinWallet extends _i1.Mock implements _i25.BitcoinWallet {
|
|||
returnValue: _i22.Future<_i17.PaymentCode?>.value(),
|
||||
) as _i22.Future<_i17.PaymentCode?>);
|
||||
@override
|
||||
_i22.Future<_i17.PaymentCode?> unBlindedPaymentCodeFromTransactionBad({
|
||||
required _i15.Transaction? transaction,
|
||||
required _i15.Address? myNotificationAddress,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#unBlindedPaymentCodeFromTransactionBad,
|
||||
[],
|
||||
{
|
||||
#transaction: transaction,
|
||||
#myNotificationAddress: myNotificationAddress,
|
||||
},
|
||||
),
|
||||
returnValue: _i22.Future<_i17.PaymentCode?>.value(),
|
||||
) as _i22.Future<_i17.PaymentCode?>);
|
||||
@override
|
||||
_i22.Future<List<_i17.PaymentCode>>
|
||||
getAllPaymentCodesFromNotificationTransactions() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -1944,6 +1961,41 @@ class MockBitcoinWallet extends _i1.Mock implements _i25.BitcoinWallet {
|
|||
),
|
||||
returnValue: _i22.Future<String>.value(''),
|
||||
) as _i22.Future<String>);
|
||||
@override
|
||||
void initCoinControlInterface({
|
||||
required String? walletId,
|
||||
required String? walletName,
|
||||
required _i21.Coin? coin,
|
||||
required _i12.MainDB? db,
|
||||
required _i22.Future<int> Function()? getChainHeight,
|
||||
required _i22.Future<void> Function(_i11.Balance)? refreshedBalanceCallback,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#initCoinControlInterface,
|
||||
[],
|
||||
{
|
||||
#walletId: walletId,
|
||||
#walletName: walletName,
|
||||
#coin: coin,
|
||||
#db: db,
|
||||
#getChainHeight: getChainHeight,
|
||||
#refreshedBalanceCallback: refreshedBalanceCallback,
|
||||
},
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i22.Future<void> refreshBalance({bool? notify = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#refreshBalance,
|
||||
[],
|
||||
{#notify: notify},
|
||||
),
|
||||
returnValue: _i22.Future<void>.value(),
|
||||
returnValueForMissingStub: _i22.Future<void>.value(),
|
||||
) as _i22.Future<void>);
|
||||
}
|
||||
|
||||
/// A class which mocks [LocaleService].
|
||||
|
@ -2355,6 +2407,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -561,6 +561,19 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get enableCoinControl => (super.noSuchMethod(
|
||||
Invocation.getter(#enableCoinControl),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#enableCoinControl,
|
||||
enableCoinControl,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get hasListeners => (super.noSuchMethod(
|
||||
Invocation.getter(#hasListeners),
|
||||
returnValue: false,
|
||||
|
|
|
@ -1194,23 +1194,24 @@ class MockBitcoinWallet extends _i1.Mock implements _i24.BitcoinWallet {
|
|||
returnValue: 0,
|
||||
) as int);
|
||||
@override
|
||||
dynamic coinSelection(
|
||||
int? satoshiAmountToSend,
|
||||
int? selectedTxFeeRate,
|
||||
String? _recipientAddress,
|
||||
bool? isSendAll, {
|
||||
dynamic coinSelection({
|
||||
required int? satoshiAmountToSend,
|
||||
required int? selectedTxFeeRate,
|
||||
required String? recipientAddress,
|
||||
required bool? coinControl,
|
||||
required bool? isSendAll,
|
||||
int? additionalOutputs = 0,
|
||||
List<_i15.UTXO>? utxos,
|
||||
}) =>
|
||||
super.noSuchMethod(Invocation.method(
|
||||
#coinSelection,
|
||||
[
|
||||
satoshiAmountToSend,
|
||||
selectedTxFeeRate,
|
||||
_recipientAddress,
|
||||
isSendAll,
|
||||
],
|
||||
[],
|
||||
{
|
||||
#satoshiAmountToSend: satoshiAmountToSend,
|
||||
#selectedTxFeeRate: selectedTxFeeRate,
|
||||
#recipientAddress: recipientAddress,
|
||||
#coinControl: coinControl,
|
||||
#isSendAll: isSendAll,
|
||||
#additionalOutputs: additionalOutputs,
|
||||
#utxos: utxos,
|
||||
},
|
||||
|
@ -1767,6 +1768,22 @@ class MockBitcoinWallet extends _i1.Mock implements _i24.BitcoinWallet {
|
|||
returnValue: _i21.Future<_i17.PaymentCode?>.value(),
|
||||
) as _i21.Future<_i17.PaymentCode?>);
|
||||
@override
|
||||
_i21.Future<_i17.PaymentCode?> unBlindedPaymentCodeFromTransactionBad({
|
||||
required _i15.Transaction? transaction,
|
||||
required _i15.Address? myNotificationAddress,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#unBlindedPaymentCodeFromTransactionBad,
|
||||
[],
|
||||
{
|
||||
#transaction: transaction,
|
||||
#myNotificationAddress: myNotificationAddress,
|
||||
},
|
||||
),
|
||||
returnValue: _i21.Future<_i17.PaymentCode?>.value(),
|
||||
) as _i21.Future<_i17.PaymentCode?>);
|
||||
@override
|
||||
_i21.Future<List<_i17.PaymentCode>>
|
||||
getAllPaymentCodesFromNotificationTransactions() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -1931,6 +1948,41 @@ class MockBitcoinWallet extends _i1.Mock implements _i24.BitcoinWallet {
|
|||
),
|
||||
returnValue: _i21.Future<String>.value(''),
|
||||
) as _i21.Future<String>);
|
||||
@override
|
||||
void initCoinControlInterface({
|
||||
required String? walletId,
|
||||
required String? walletName,
|
||||
required _i20.Coin? coin,
|
||||
required _i12.MainDB? db,
|
||||
required _i21.Future<int> Function()? getChainHeight,
|
||||
required _i21.Future<void> Function(_i11.Balance)? refreshedBalanceCallback,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#initCoinControlInterface,
|
||||
[],
|
||||
{
|
||||
#walletId: walletId,
|
||||
#walletName: walletName,
|
||||
#coin: coin,
|
||||
#db: db,
|
||||
#getChainHeight: getChainHeight,
|
||||
#refreshedBalanceCallback: refreshedBalanceCallback,
|
||||
},
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i21.Future<void> refreshBalance({bool? notify = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#refreshBalance,
|
||||
[],
|
||||
{#notify: notify},
|
||||
),
|
||||
returnValue: _i21.Future<void>.value(),
|
||||
returnValueForMissingStub: _i21.Future<void>.value(),
|
||||
) as _i21.Future<void>);
|
||||
}
|
||||
|
||||
/// A class which mocks [Manager].
|
||||
|
@ -2080,6 +2132,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -549,6 +549,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
@ -2322,6 +2327,19 @@ class MockPrefs extends _i1.Mock implements _i19.Prefs {
|
|||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get enableCoinControl => (super.noSuchMethod(
|
||||
Invocation.getter(#enableCoinControl),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod(
|
||||
Invocation.setter(
|
||||
#enableCoinControl,
|
||||
enableCoinControl,
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
bool get hasListeners => (super.noSuchMethod(
|
||||
Invocation.getter(#hasListeners),
|
||||
returnValue: false,
|
||||
|
|
|
@ -957,23 +957,24 @@ class MockBitcoinWallet extends _i1.Mock implements _i23.BitcoinWallet {
|
|||
returnValue: 0,
|
||||
) as int);
|
||||
@override
|
||||
dynamic coinSelection(
|
||||
int? satoshiAmountToSend,
|
||||
int? selectedTxFeeRate,
|
||||
String? _recipientAddress,
|
||||
bool? isSendAll, {
|
||||
dynamic coinSelection({
|
||||
required int? satoshiAmountToSend,
|
||||
required int? selectedTxFeeRate,
|
||||
required String? recipientAddress,
|
||||
required bool? coinControl,
|
||||
required bool? isSendAll,
|
||||
int? additionalOutputs = 0,
|
||||
List<_i15.UTXO>? utxos,
|
||||
}) =>
|
||||
super.noSuchMethod(Invocation.method(
|
||||
#coinSelection,
|
||||
[
|
||||
satoshiAmountToSend,
|
||||
selectedTxFeeRate,
|
||||
_recipientAddress,
|
||||
isSendAll,
|
||||
],
|
||||
[],
|
||||
{
|
||||
#satoshiAmountToSend: satoshiAmountToSend,
|
||||
#selectedTxFeeRate: selectedTxFeeRate,
|
||||
#recipientAddress: recipientAddress,
|
||||
#coinControl: coinControl,
|
||||
#isSendAll: isSendAll,
|
||||
#additionalOutputs: additionalOutputs,
|
||||
#utxos: utxos,
|
||||
},
|
||||
|
@ -1530,6 +1531,22 @@ class MockBitcoinWallet extends _i1.Mock implements _i23.BitcoinWallet {
|
|||
returnValue: _i20.Future<_i17.PaymentCode?>.value(),
|
||||
) as _i20.Future<_i17.PaymentCode?>);
|
||||
@override
|
||||
_i20.Future<_i17.PaymentCode?> unBlindedPaymentCodeFromTransactionBad({
|
||||
required _i15.Transaction? transaction,
|
||||
required _i15.Address? myNotificationAddress,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#unBlindedPaymentCodeFromTransactionBad,
|
||||
[],
|
||||
{
|
||||
#transaction: transaction,
|
||||
#myNotificationAddress: myNotificationAddress,
|
||||
},
|
||||
),
|
||||
returnValue: _i20.Future<_i17.PaymentCode?>.value(),
|
||||
) as _i20.Future<_i17.PaymentCode?>);
|
||||
@override
|
||||
_i20.Future<List<_i17.PaymentCode>>
|
||||
getAllPaymentCodesFromNotificationTransactions() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -1694,6 +1711,41 @@ class MockBitcoinWallet extends _i1.Mock implements _i23.BitcoinWallet {
|
|||
),
|
||||
returnValue: _i20.Future<String>.value(''),
|
||||
) as _i20.Future<String>);
|
||||
@override
|
||||
void initCoinControlInterface({
|
||||
required String? walletId,
|
||||
required String? walletName,
|
||||
required _i19.Coin? coin,
|
||||
required _i12.MainDB? db,
|
||||
required _i20.Future<int> Function()? getChainHeight,
|
||||
required _i20.Future<void> Function(_i11.Balance)? refreshedBalanceCallback,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#initCoinControlInterface,
|
||||
[],
|
||||
{
|
||||
#walletId: walletId,
|
||||
#walletName: walletName,
|
||||
#coin: coin,
|
||||
#db: db,
|
||||
#getChainHeight: getChainHeight,
|
||||
#refreshedBalanceCallback: refreshedBalanceCallback,
|
||||
},
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i20.Future<void> refreshBalance({bool? notify = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#refreshBalance,
|
||||
[],
|
||||
{#notify: notify},
|
||||
),
|
||||
returnValue: _i20.Future<void>.value(),
|
||||
returnValueForMissingStub: _i20.Future<void>.value(),
|
||||
) as _i20.Future<void>);
|
||||
}
|
||||
|
||||
/// A class which mocks [LocaleService].
|
||||
|
|
|
@ -1206,23 +1206,24 @@ class MockBitcoinWallet extends _i1.Mock implements _i25.BitcoinWallet {
|
|||
returnValue: 0,
|
||||
) as int);
|
||||
@override
|
||||
dynamic coinSelection(
|
||||
int? satoshiAmountToSend,
|
||||
int? selectedTxFeeRate,
|
||||
String? _recipientAddress,
|
||||
bool? isSendAll, {
|
||||
dynamic coinSelection({
|
||||
required int? satoshiAmountToSend,
|
||||
required int? selectedTxFeeRate,
|
||||
required String? recipientAddress,
|
||||
required bool? coinControl,
|
||||
required bool? isSendAll,
|
||||
int? additionalOutputs = 0,
|
||||
List<_i15.UTXO>? utxos,
|
||||
}) =>
|
||||
super.noSuchMethod(Invocation.method(
|
||||
#coinSelection,
|
||||
[
|
||||
satoshiAmountToSend,
|
||||
selectedTxFeeRate,
|
||||
_recipientAddress,
|
||||
isSendAll,
|
||||
],
|
||||
[],
|
||||
{
|
||||
#satoshiAmountToSend: satoshiAmountToSend,
|
||||
#selectedTxFeeRate: selectedTxFeeRate,
|
||||
#recipientAddress: recipientAddress,
|
||||
#coinControl: coinControl,
|
||||
#isSendAll: isSendAll,
|
||||
#additionalOutputs: additionalOutputs,
|
||||
#utxos: utxos,
|
||||
},
|
||||
|
@ -1779,6 +1780,22 @@ class MockBitcoinWallet extends _i1.Mock implements _i25.BitcoinWallet {
|
|||
returnValue: _i22.Future<_i17.PaymentCode?>.value(),
|
||||
) as _i22.Future<_i17.PaymentCode?>);
|
||||
@override
|
||||
_i22.Future<_i17.PaymentCode?> unBlindedPaymentCodeFromTransactionBad({
|
||||
required _i15.Transaction? transaction,
|
||||
required _i15.Address? myNotificationAddress,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#unBlindedPaymentCodeFromTransactionBad,
|
||||
[],
|
||||
{
|
||||
#transaction: transaction,
|
||||
#myNotificationAddress: myNotificationAddress,
|
||||
},
|
||||
),
|
||||
returnValue: _i22.Future<_i17.PaymentCode?>.value(),
|
||||
) as _i22.Future<_i17.PaymentCode?>);
|
||||
@override
|
||||
_i22.Future<List<_i17.PaymentCode>>
|
||||
getAllPaymentCodesFromNotificationTransactions() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -1943,6 +1960,41 @@ class MockBitcoinWallet extends _i1.Mock implements _i25.BitcoinWallet {
|
|||
),
|
||||
returnValue: _i22.Future<String>.value(''),
|
||||
) as _i22.Future<String>);
|
||||
@override
|
||||
void initCoinControlInterface({
|
||||
required String? walletId,
|
||||
required String? walletName,
|
||||
required _i21.Coin? coin,
|
||||
required _i12.MainDB? db,
|
||||
required _i22.Future<int> Function()? getChainHeight,
|
||||
required _i22.Future<void> Function(_i11.Balance)? refreshedBalanceCallback,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#initCoinControlInterface,
|
||||
[],
|
||||
{
|
||||
#walletId: walletId,
|
||||
#walletName: walletName,
|
||||
#coin: coin,
|
||||
#db: db,
|
||||
#getChainHeight: getChainHeight,
|
||||
#refreshedBalanceCallback: refreshedBalanceCallback,
|
||||
},
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i22.Future<void> refreshBalance({bool? notify = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#refreshBalance,
|
||||
[],
|
||||
{#notify: notify},
|
||||
),
|
||||
returnValue: _i22.Future<void>.value(),
|
||||
returnValueForMissingStub: _i22.Future<void>.value(),
|
||||
) as _i22.Future<void>);
|
||||
}
|
||||
|
||||
/// A class which mocks [NodeService].
|
||||
|
@ -2292,6 +2344,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
|
@ -1206,23 +1206,24 @@ class MockBitcoinWallet extends _i1.Mock implements _i25.BitcoinWallet {
|
|||
returnValue: 0,
|
||||
) as int);
|
||||
@override
|
||||
dynamic coinSelection(
|
||||
int? satoshiAmountToSend,
|
||||
int? selectedTxFeeRate,
|
||||
String? _recipientAddress,
|
||||
bool? isSendAll, {
|
||||
dynamic coinSelection({
|
||||
required int? satoshiAmountToSend,
|
||||
required int? selectedTxFeeRate,
|
||||
required String? recipientAddress,
|
||||
required bool? coinControl,
|
||||
required bool? isSendAll,
|
||||
int? additionalOutputs = 0,
|
||||
List<_i15.UTXO>? utxos,
|
||||
}) =>
|
||||
super.noSuchMethod(Invocation.method(
|
||||
#coinSelection,
|
||||
[
|
||||
satoshiAmountToSend,
|
||||
selectedTxFeeRate,
|
||||
_recipientAddress,
|
||||
isSendAll,
|
||||
],
|
||||
[],
|
||||
{
|
||||
#satoshiAmountToSend: satoshiAmountToSend,
|
||||
#selectedTxFeeRate: selectedTxFeeRate,
|
||||
#recipientAddress: recipientAddress,
|
||||
#coinControl: coinControl,
|
||||
#isSendAll: isSendAll,
|
||||
#additionalOutputs: additionalOutputs,
|
||||
#utxos: utxos,
|
||||
},
|
||||
|
@ -1779,6 +1780,22 @@ class MockBitcoinWallet extends _i1.Mock implements _i25.BitcoinWallet {
|
|||
returnValue: _i22.Future<_i17.PaymentCode?>.value(),
|
||||
) as _i22.Future<_i17.PaymentCode?>);
|
||||
@override
|
||||
_i22.Future<_i17.PaymentCode?> unBlindedPaymentCodeFromTransactionBad({
|
||||
required _i15.Transaction? transaction,
|
||||
required _i15.Address? myNotificationAddress,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#unBlindedPaymentCodeFromTransactionBad,
|
||||
[],
|
||||
{
|
||||
#transaction: transaction,
|
||||
#myNotificationAddress: myNotificationAddress,
|
||||
},
|
||||
),
|
||||
returnValue: _i22.Future<_i17.PaymentCode?>.value(),
|
||||
) as _i22.Future<_i17.PaymentCode?>);
|
||||
@override
|
||||
_i22.Future<List<_i17.PaymentCode>>
|
||||
getAllPaymentCodesFromNotificationTransactions() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
@ -1943,6 +1960,41 @@ class MockBitcoinWallet extends _i1.Mock implements _i25.BitcoinWallet {
|
|||
),
|
||||
returnValue: _i22.Future<String>.value(''),
|
||||
) as _i22.Future<String>);
|
||||
@override
|
||||
void initCoinControlInterface({
|
||||
required String? walletId,
|
||||
required String? walletName,
|
||||
required _i21.Coin? coin,
|
||||
required _i12.MainDB? db,
|
||||
required _i22.Future<int> Function()? getChainHeight,
|
||||
required _i22.Future<void> Function(_i11.Balance)? refreshedBalanceCallback,
|
||||
}) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#initCoinControlInterface,
|
||||
[],
|
||||
{
|
||||
#walletId: walletId,
|
||||
#walletName: walletName,
|
||||
#coin: coin,
|
||||
#db: db,
|
||||
#getChainHeight: getChainHeight,
|
||||
#refreshedBalanceCallback: refreshedBalanceCallback,
|
||||
},
|
||||
),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
@override
|
||||
_i22.Future<void> refreshBalance({bool? notify = false}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#refreshBalance,
|
||||
[],
|
||||
{#notify: notify},
|
||||
),
|
||||
returnValue: _i22.Future<void>.value(),
|
||||
returnValueForMissingStub: _i22.Future<void>.value(),
|
||||
) as _i22.Future<void>);
|
||||
}
|
||||
|
||||
/// A class which mocks [NodeService].
|
||||
|
@ -2292,6 +2344,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasCoinControlSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasCoinControlSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
int get rescanOnOpenVersion => (super.noSuchMethod(
|
||||
Invocation.getter(#rescanOnOpenVersion),
|
||||
returnValue: 0,
|
||||
|
|
Loading…
Reference in a new issue