> 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(
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 col, Id id, UTXO object) {
}
extension UTXOByIndex on IsarCollection {
- Future getByTxidWalletId(String txid, String walletId) {
- return getByIndex(r'txid_walletId', [txid, walletId]);
+ Future 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 deleteByTxidWalletId(String txid, String walletId) {
- return deleteByIndex(r'txid_walletId', [txid, walletId]);
+ Future 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> getAllByTxidWalletId(
- List txidValues, List walletIdValues) {
+ Future> getAllByTxidWalletIdVout(List txidValues,
+ List walletIdValues, List 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 = >[];
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 getAllByTxidWalletIdSync(
- List txidValues, List walletIdValues) {
+ List getAllByTxidWalletIdVoutSync(List txidValues,
+ List walletIdValues, List 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 = >[];
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 deleteAllByTxidWalletId(
- List txidValues, List walletIdValues) {
+ Future deleteAllByTxidWalletIdVout(List txidValues,
+ List walletIdValues, List 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 = >[];
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 txidValues, List walletIdValues) {
+ int deleteAllByTxidWalletIdVoutSync(List txidValues,
+ List walletIdValues, List 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 = >[];
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 putByTxidWalletId(UTXO object) {
- return putByIndex(r'txid_walletId', object);
+ Future 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> putAllByTxidWalletId(List objects) {
- return putAllByIndex(r'txid_walletId', objects);
+ Future> putAllByTxidWalletIdVout(List objects) {
+ return putAllByIndex(r'txid_walletId_vout', objects);
}
- List putAllByTxidWalletIdSync(List objects,
+ List putAllByTxidWalletIdVoutSync(List 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 {
});
}
- QueryBuilder txidEqualToAnyWalletId(
+ QueryBuilder 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 txidNotEqualToAnyWalletId(
+ QueryBuilder 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 {
} 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 {
});
}
- QueryBuilder txidWalletIdEqualTo(
+ QueryBuilder 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 txidEqualToWalletIdNotEqualTo(
- String txid, String walletId) {
+ QueryBuilder
+ 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 {
} 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 {
});
}
+ QueryBuilder 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 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
+ 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 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 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 isBlockedEqualTo(bool isBlocked) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
@@ -608,6 +736,150 @@ extension UTXOQueryWhere on QueryBuilder {
}
extension UTXOQueryFilter on QueryBuilder {
+ QueryBuilder addressIsNull() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(const FilterCondition.isNull(
+ property: r'address',
+ ));
+ });
+ }
+
+ QueryBuilder addressIsNotNull() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(const FilterCondition.isNotNull(
+ property: r'address',
+ ));
+ });
+ }
+
+ QueryBuilder addressEqualTo(
+ String? value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'address',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder 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 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 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 addressStartsWith(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.startsWith(
+ property: r'address',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder addressEndsWith(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.endsWith(
+ property: r'address',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder addressContains(String value,
+ {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.contains(
+ property: r'address',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder addressMatches(String pattern,
+ {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.matches(
+ property: r'address',
+ wildcard: pattern,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder addressIsEmpty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'address',
+ value: '',
+ ));
+ });
+ }
+
+ QueryBuilder addressIsNotEmpty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.greaterThan(
+ property: r'address',
+ value: '',
+ ));
+ });
+ }
+
QueryBuilder blockHashIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
@@ -1510,6 +1782,31 @@ extension UTXOQueryFilter on QueryBuilder {
});
}
+ QueryBuilder usedIsNull() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(const FilterCondition.isNull(
+ property: r'used',
+ ));
+ });
+ }
+
+ QueryBuilder usedIsNotNull() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(const FilterCondition.isNotNull(
+ property: r'used',
+ ));
+ });
+ }
+
+ QueryBuilder usedEqualTo(bool? value) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'used',
+ value: value,
+ ));
+ });
+ }
+
QueryBuilder valueEqualTo(int value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
@@ -1749,6 +2046,18 @@ extension UTXOQueryObject on QueryBuilder {}
extension UTXOQueryLinks on QueryBuilder {}
extension UTXOQuerySortBy on QueryBuilder {
+ QueryBuilder sortByAddress() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'address', Sort.asc);
+ });
+ }
+
+ QueryBuilder sortByAddressDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'address', Sort.desc);
+ });
+ }
+
QueryBuilder sortByBlockHash() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'blockHash', Sort.asc);
@@ -1857,6 +2166,18 @@ extension UTXOQuerySortBy on QueryBuilder {
});
}
+ QueryBuilder sortByUsed() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'used', Sort.asc);
+ });
+ }
+
+ QueryBuilder sortByUsedDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'used', Sort.desc);
+ });
+ }
+
QueryBuilder sortByValue() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'value', Sort.asc);
@@ -1895,6 +2216,18 @@ extension UTXOQuerySortBy on QueryBuilder {
}
extension UTXOQuerySortThenBy on QueryBuilder {
+ QueryBuilder thenByAddress() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'address', Sort.asc);
+ });
+ }
+
+ QueryBuilder thenByAddressDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'address', Sort.desc);
+ });
+ }
+
QueryBuilder thenByBlockHash() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'blockHash', Sort.asc);
@@ -2015,6 +2348,18 @@ extension UTXOQuerySortThenBy on QueryBuilder {
});
}
+ QueryBuilder thenByUsed() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'used', Sort.asc);
+ });
+ }
+
+ QueryBuilder thenByUsedDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'used', Sort.desc);
+ });
+ }
+
QueryBuilder thenByValue() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'value', Sort.asc);
@@ -2053,6 +2398,13 @@ extension UTXOQuerySortThenBy on QueryBuilder {
}
extension UTXOQueryWhereDistinct on QueryBuilder {
+ QueryBuilder distinctByAddress(
+ {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addDistinctBy(r'address', caseSensitive: caseSensitive);
+ });
+ }
+
QueryBuilder distinctByBlockHash(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
@@ -2113,6 +2465,12 @@ extension UTXOQueryWhereDistinct on QueryBuilder {
});
}
+ QueryBuilder distinctByUsed() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addDistinctBy(r'used');
+ });
+ }
+
QueryBuilder distinctByValue() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'value');
@@ -2140,6 +2498,12 @@ extension UTXOQueryProperty on QueryBuilder {
});
}
+ QueryBuilder addressProperty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addPropertyName(r'address');
+ });
+ }
+
QueryBuilder blockHashProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'blockHash');
@@ -2194,6 +2558,12 @@ extension UTXOQueryProperty on QueryBuilder {
});
}
+ QueryBuilder usedProperty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addPropertyName(r'used');
+ });
+ }
+
QueryBuilder valueProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'value');
diff --git a/lib/models/signing_data.dart b/lib/models/signing_data.dart
new file mode 100644
index 000000000..bb933976c
--- /dev/null
+++ b/lib/models/signing_data.dart
@@ -0,0 +1,21 @@
+import 'dart:typed_data';
+
+import 'package:bitcoindart/bitcoindart.dart';
+import 'package:stackwallet/models/isar/models/isar_models.dart';
+import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
+
+class SigningData {
+ SigningData({
+ required this.derivePathType,
+ required this.utxo,
+ this.output,
+ this.keyPair,
+ this.redeemScript,
+ });
+
+ final DerivePathType derivePathType;
+ final UTXO utxo;
+ Uint8List? output;
+ ECPair? keyPair;
+ Uint8List? redeemScript;
+}
diff --git a/lib/models/type_adaptors/epicbox_config_model.g.dart b/lib/models/type_adaptors/epicbox_config_model.g.dart
new file mode 100644
index 000000000..70d066370
--- /dev/null
+++ b/lib/models/type_adaptors/epicbox_config_model.g.dart
@@ -0,0 +1,56 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../epicbox_config_model.dart';
+
+// **************************************************************************
+// TypeAdapterGenerator
+// **************************************************************************
+
+class EpicBoxConfigModelAdapter extends TypeAdapter {
+ @override
+ final int typeId = 72;
+
+ @override
+ EpicBoxConfigModel read(BinaryReader reader) {
+ final numOfFields = reader.readByte();
+ final fields = {
+ for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
+ };
+ return EpicBoxConfigModel(
+ host: fields[1] as String,
+ port: fields[2] as int,
+ protocolInsecure: fields[3] as bool,
+ addressIndex: fields[4] as int,
+ // name: fields[5] as String,
+ // id: fields[6] as String,
+ );
+ }
+
+ @override
+ void write(BinaryWriter writer, EpicBoxConfigModel obj) {
+ writer
+ ..writeByte(4)
+ ..writeByte(0)
+ ..write(obj.host)
+ ..writeByte(1)
+ ..write(obj.port)
+ ..writeByte(2)
+ ..write(obj.protocolInsecure)
+ ..writeByte(3)
+ ..write(obj.addressIndex);
+ // ..writeByte(4)
+ // ..write(obj.id)
+ // ..writeByte(5)
+ // ..write(obj.name)
+ }
+
+ @override
+ int get hashCode => typeId.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is EpicBoxConfigModelAdapter &&
+ runtimeType == other.runtimeType &&
+ typeId == other.typeId;
+}
diff --git a/lib/models/type_adaptors/epicbox_server_model.g.dart b/lib/models/type_adaptors/epicbox_server_model.g.dart
new file mode 100644
index 000000000..cc741bf83
--- /dev/null
+++ b/lib/models/type_adaptors/epicbox_server_model.g.dart
@@ -0,0 +1,62 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../epicbox_server_model.dart';
+
+// **************************************************************************
+// TypeAdapterGenerator
+// **************************************************************************
+
+class EpicBoxServerModelAdapter extends TypeAdapter {
+ @override
+ final int typeId = 71;
+
+ @override
+ EpicBoxServerModel read(BinaryReader reader) {
+ final numOfFields = reader.readByte();
+ final fields = {
+ for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
+ };
+ return EpicBoxServerModel(
+ host: fields[1] as String,
+ port: fields[2] as int,
+ name: fields[3] as String,
+ id: fields[0] as String,
+ useSSL: fields[4] as bool,
+ enabled: fields[5] as bool,
+ isFailover: fields[6] as bool,
+ isDown: fields[7] as bool,
+ );
+ }
+
+ @override
+ void write(BinaryWriter writer, EpicBoxServerModel obj) {
+ writer
+ ..writeByte(8)
+ ..writeByte(0)
+ ..write(obj.id)
+ ..writeByte(1)
+ ..write(obj.host)
+ ..writeByte(2)
+ ..write(obj.port)
+ ..writeByte(3)
+ ..write(obj.name)
+ ..writeByte(4)
+ ..write(obj.useSSL)
+ ..writeByte(5)
+ ..write(obj.enabled)
+ ..writeByte(6)
+ ..write(obj.isFailover)
+ ..writeByte(7)
+ ..write(obj.isDown);
+ }
+
+ @override
+ int get hashCode => typeId.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is EpicBoxServerModelAdapter &&
+ runtimeType == other.runtimeType &&
+ typeId == other.typeId;
+}
diff --git a/lib/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart b/lib/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart
index 32c4b239e..565636a92 100644
--- a/lib/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart
+++ b/lib/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart
@@ -61,7 +61,10 @@ class CreateOrRestoreWalletView extends StatelessWidget {
),
CoinImage(
coin: entity.coin,
- isDesktop: isDesktop,
+ width:
+ isDesktop ? 324 : MediaQuery.of(context).size.width / 1.6,
+ height:
+ isDesktop ? null : MediaQuery.of(context).size.width / 1.6,
),
const SizedBox(
height: 32,
@@ -89,41 +92,45 @@ class CreateOrRestoreWalletView extends StatelessWidget {
},
),
),
- body: Container(
- color: Theme.of(context).extension()!.background,
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- Padding(
- padding: const EdgeInsets.all(31),
- child: CoinImage(
+ body: SafeArea(
+ child: Container(
+ color: Theme.of(context).extension()!.background,
+ child: Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ CoinImage(
+ coin: entity.coin,
+ width: isDesktop
+ ? 324
+ : MediaQuery.of(context).size.width / 1.6,
+ height: isDesktop
+ ? null
+ : MediaQuery.of(context).size.width / 1.6,
+ ),
+ const Spacer(
+ flex: 2,
+ ),
+ CreateRestoreWalletTitle(
coin: entity.coin,
isDesktop: isDesktop,
),
- ),
- const Spacer(
- flex: 2,
- ),
- CreateRestoreWalletTitle(
- coin: entity.coin,
- isDesktop: isDesktop,
- ),
- const SizedBox(
- height: 8,
- ),
- CreateRestoreWalletSubTitle(
- isDesktop: isDesktop,
- ),
- const Spacer(
- flex: 5,
- ),
- CreateWalletButtonGroup(
- coin: entity.coin,
- isDesktop: isDesktop,
- ),
- ],
+ const SizedBox(
+ height: 8,
+ ),
+ CreateRestoreWalletSubTitle(
+ isDesktop: isDesktop,
+ ),
+ const Spacer(
+ flex: 5,
+ ),
+ CreateWalletButtonGroup(
+ coin: entity.coin,
+ isDesktop: isDesktop,
+ ),
+ ],
+ ),
),
),
),
diff --git a/lib/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart b/lib/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart
index d991fc669..fb282d9ca 100644
--- a/lib/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart
+++ b/lib/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart
@@ -1,46 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
-import 'package:stackwallet/providers/ui/color_theme_provider.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/theme/color_theme.dart';
+import 'package:stackwallet/utilities/theme/stack_colors.dart';
class CoinImage extends ConsumerWidget {
const CoinImage({
Key? key,
required this.coin,
- required this.isDesktop,
+ this.width,
+ this.height,
}) : super(key: key);
final Coin coin;
- final bool isDesktop;
+ final double? width;
+ final double? height;
@override
Widget build(BuildContext context, WidgetRef ref) {
- final bool isSorbet = ref.read(colorThemeProvider.state).state.themeType ==
- ThemeType.fruitSorbet;
- final bool isForest =
- ref.read(colorThemeProvider.state).state.themeType == ThemeType.forest;
-
- return ((isSorbet &&
- coin != Coin.epicCash &&
- coin != Coin.monero &&
- coin != Coin.namecoin) ||
- (isForest && coin != Coin.dogecoin))
- ? SvgPicture.asset(
- Assets.svg.imageFor(coin: coin, context: context),
- width: isDesktop ? 324 : MediaQuery.of(context).size.width,
- )
- // : Image(
- // image: AssetImage(
- // Assets.png.imageFor(coin: coin, context: context),
- // )))
- : Image(
- image: AssetImage(
- Assets.png.imageFor(coin: coin, context: context),
- ),
- width: isDesktop ? 324 : MediaQuery.of(context).size.width / 3,
- );
+ if (Theme.of(context).extension()!.themeType ==
+ ThemeType.chan) {
+ return SizedBox(
+ width: width,
+ height: height,
+ child: Image(
+ image: AssetImage(
+ Assets.gif.plain(coin),
+ ),
+ ),
+ );
+ } else {
+ return SvgPicture.asset(
+ Assets.svg.imageFor(coin: coin, context: context),
+ width: width,
+ height: height,
+ );
+ }
}
}
diff --git a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart
index 31af6b3bd..6a3f28318 100644
--- a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart
+++ b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart
@@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
+import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart';
import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart';
import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
@@ -165,11 +166,10 @@ class _NameYourWalletViewState extends ConsumerState {
flex: 1,
),
if (!isDesktop)
- Image(
- image: AssetImage(
- Assets.png.imageFor(coin: coin, context: context),
- ),
+ CoinImage(
+ coin: coin,
height: 100,
+ width: 100,
),
SizedBox(
height: isDesktop ? 0 : 16,
diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
index 2afd1c9b1..a83fa5bd2 100644
--- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
+++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart
@@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_rounded_date_picker/flutter_rounded_date_picker.dart';
import 'package:flutter_svg/svg.dart';
import 'package:google_fonts/google_fonts.dart';
+import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart';
import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/mobile_mnemonic_length_selector.dart';
import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_from_date_picker.dart';
import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_options_next_button.dart';
@@ -282,11 +283,10 @@ class _RestoreOptionsViewState extends ConsumerState {
flex: isDesktop ? 10 : 1,
),
if (!isDesktop)
- Image(
- image: AssetImage(
- Assets.png.imageFor(coin: coin, context: context),
- ),
+ CoinImage(
+ coin: coin,
height: 100,
+ width: 100,
),
SizedBox(
height: isDesktop ? 0 : 16,
diff --git a/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart b/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart
index dfaec0dec..09ee82a79 100644
--- a/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart
+++ b/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart
@@ -76,7 +76,7 @@ class _FiatSelectionViewState extends State {
@override
Widget build(BuildContext context) {
Locale locale = Localizations.localeOf(context);
- var format = NumberFormat.simpleCurrency(locale: locale.toString());
+ final format = NumberFormat.simpleCurrency(locale: locale.toString());
// See https://stackoverflow.com/a/67055685
final isDesktop = Util.isDesktop;
@@ -186,80 +186,178 @@ class _FiatSelectionViewState extends State {
height: 12,
),
Flexible(
- child: RoundedWhiteContainer(
- padding: const EdgeInsets.all(0),
- child: ListView.builder(
- shrinkWrap: true,
- primary: isDesktop ? false : null,
- itemCount: _fiats.length,
- itemBuilder: (builderContext, index) {
- return Padding(
- padding: const EdgeInsets.symmetric(vertical: 4),
- child: GestureDetector(
- onTap: () {
- Navigator.of(context).pop(_fiats[index]);
- },
- child: RoundedWhiteContainer(
- child: Row(
+ child: SingleChildScrollView(
+ child: RoundedWhiteContainer(
+ padding: const EdgeInsets.all(0),
+ child: Table(
+ columnWidths: const {
+ 0: IntrinsicColumnWidth(),
+ 1: FlexColumnWidth(),
+ },
+ defaultVerticalAlignment: TableCellVerticalAlignment.middle,
+ children: [
+ ..._fiats.map(
+ (e) {
+ return TableRow(
children: [
- Container(
- padding: const EdgeInsets.all(7.5),
- decoration: BoxDecoration(
- color: Theme.of(context)
- .extension()!
- .currencyListItemBG,
- borderRadius: BorderRadius.circular(4),
- ),
- child: Text(
- format.simpleCurrencySymbol(
- _fiats[index].ticker.toUpperCase()),
- style: STextStyles.subtitle(context).apply(
- fontSizeFactor: (1 /
- format
- .simpleCurrencySymbol(_fiats[index]
- .ticker
- .toUpperCase())
- .length * // Couldn't get pow() working here
- format
- .simpleCurrencySymbol(_fiats[index]
- .ticker
- .toUpperCase())
- .length)),
- textAlign: TextAlign.center,
+ TableCell(
+ verticalAlignment:
+ TableCellVerticalAlignment.fill,
+ child: GestureDetector(
+ onTap: () => Navigator.of(context).pop(e),
+ child: Container(
+ color: Colors.transparent,
+ padding: const EdgeInsets.only(left: 12),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment:
+ CrossAxisAlignment.stretch,
+ children: [
+ Container(
+ padding: const EdgeInsets.all(7.5),
+ decoration: BoxDecoration(
+ color: Theme.of(context)
+ .extension()!
+ .currencyListItemBG,
+ borderRadius:
+ BorderRadius.circular(4),
+ ),
+ child: Text(
+ format.simpleCurrencySymbol(
+ e.ticker.toUpperCase()),
+ style: STextStyles.subtitle(context)
+ .apply(
+ fontSizeFactor: (1 /
+ format
+ .simpleCurrencySymbol(
+ e.ticker.toUpperCase())
+ .length * // Couldn't get pow() working here
+ format
+ .simpleCurrencySymbol(
+ e.ticker.toUpperCase())
+ .length),
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ ],
+ ),
+ ),
),
),
- const SizedBox(
- width: 10,
- ),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- _fiats[index].name,
- style: STextStyles.largeMedium14(context),
+ GestureDetector(
+ onTap: () => Navigator.of(context).pop(e),
+ child: Container(
+ color: Colors.transparent,
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.start,
+ children: [
+ Text(
+ e.name,
+ style:
+ STextStyles.largeMedium14(context),
+ ),
+ const SizedBox(
+ height: 2,
+ ),
+ Text(
+ e.ticker.toUpperCase(),
+ style: STextStyles.smallMed12(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ ),
+ ],
),
- const SizedBox(
- height: 2,
- ),
- Text(
- _fiats[index].ticker.toUpperCase(),
- style: STextStyles.smallMed12(context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textSubtitle1,
- ),
- ),
- ],
+ ),
),
),
],
- ),
- ),
+ );
+ },
),
- );
- },
+ ],
+ ),
+
+ // child: ListView.builder(
+ // shrinkWrap: true,
+ // primary: isDesktop ? false : null,
+ // itemCount: _fiats.length,
+ // itemBuilder: (builderContext, index) {
+ // return Padding(
+ // padding: const EdgeInsets.symmetric(vertical: 4),
+ // child: GestureDetector(
+ // onTap: () {
+ // Navigator.of(context).pop(_fiats[index]);
+ // },
+ // child: RoundedWhiteContainer(
+ // child: Row(
+ // children: [
+ // Container(
+ // padding: const EdgeInsets.all(7.5),
+ // decoration: BoxDecoration(
+ // color: Theme.of(context)
+ // .extension()!
+ // .currencyListItemBG,
+ // borderRadius: BorderRadius.circular(4),
+ // ),
+ // child: Text(
+ // format.simpleCurrencySymbol(
+ // _fiats[index].ticker.toUpperCase()),
+ // style: STextStyles.subtitle(context).apply(
+ // fontSizeFactor: (1 /
+ // format
+ // .simpleCurrencySymbol(_fiats[index]
+ // .ticker
+ // .toUpperCase())
+ // .length * // Couldn't get pow() working here
+ // format
+ // .simpleCurrencySymbol(_fiats[index]
+ // .ticker
+ // .toUpperCase())
+ // .length)),
+ // textAlign: TextAlign.center,
+ // ),
+ // ),
+ // const SizedBox(
+ // width: 10,
+ // ),
+ // Expanded(
+ // child: Column(
+ // crossAxisAlignment: CrossAxisAlignment.start,
+ // children: [
+ // Text(
+ // _fiats[index].name,
+ // style: STextStyles.largeMedium14(context),
+ // ),
+ // const SizedBox(
+ // height: 2,
+ // ),
+ // Text(
+ // _fiats[index].ticker.toUpperCase(),
+ // style: STextStyles.smallMed12(context)
+ // .copyWith(
+ // color: Theme.of(context)
+ // .extension()!
+ // .textSubtitle1,
+ // ),
+ // ),
+ // ],
+ // ),
+ // ),
+ // ],
+ // ),
+ // ),
+ // ),
+ // );
+ // },
+ // ),
),
),
),
diff --git a/lib/pages/coin_control/coin_control_view.dart b/lib/pages/coin_control/coin_control_view.dart
new file mode 100644
index 000000000..0eeff1089
--- /dev/null
+++ b/lib/pages/coin_control/coin_control_view.dart
@@ -0,0 +1,772 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/flutter_svg.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/assets.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/app_bar_field.dart';
+import 'package:stackwallet/widgets/background.dart';
+import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
+import 'package:stackwallet/widgets/custom_buttons/dropdown_button.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/desktop/secondary_button.dart';
+import 'package:stackwallet/widgets/expandable2.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';
+
+import '../../widgets/animated_widgets/rotate_icon.dart';
+import '../../widgets/rounded_container.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? selectedUTXOs;
+
+ @override
+ ConsumerState createState() => _CoinControlViewState();
+}
+
+class _CoinControlViewState extends ConsumerState {
+ final searchController = TextEditingController();
+ final searchFocus = FocusNode();
+
+ bool _isSearching = false;
+ bool _showBlocked = false;
+
+ CCSortDescriptor _sort = CCSortDescriptor.age;
+
+ Map>? _map;
+ List? _list;
+
+ final Set _selectedAvailable = {};
+ final Set _selectedBlocked = {};
+
+ Future _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!);
+ }
+ searchController.addListener(() {
+ WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+ setState(() {});
+ });
+ });
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ searchController.dispose();
+ searchFocus.dispose();
+ super.dispose();
+ }
+
+ @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,
+ ),
+ );
+
+ if (_sort == CCSortDescriptor.address && !_isSearching) {
+ _list = null;
+ _map = MainDB.instance.queryUTXOsGroupedByAddressSync(
+ walletId: widget.walletId,
+ filter: CCFilter.all,
+ sort: _sort,
+ searchTerm: "",
+ coin: coin,
+ );
+ } else {
+ _map = null;
+ _list = MainDB.instance.queryUTXOsSync(
+ walletId: widget.walletId,
+ filter: _isSearching
+ ? CCFilter.all
+ : _showBlocked
+ ? CCFilter.frozen
+ : CCFilter.available,
+ sort: _sort,
+ searchTerm: _isSearching ? searchController.text : "",
+ coin: coin,
+ );
+ }
+
+ 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()!.background,
+ appBar: AppBar(
+ automaticallyImplyLeading: false,
+ leading: _isSearching
+ ? null
+ : widget.type == CoinControlViewType.use &&
+ _selectedAvailable.isNotEmpty
+ ? AppBarIconButton(
+ icon: XIcon(
+ width: 24,
+ height: 24,
+ color: Theme.of(context)
+ .extension()!
+ .topNavIconPrimary,
+ ),
+ onPressed: () {
+ setState(() {
+ _selectedAvailable.clear();
+ });
+ },
+ )
+ : AppBarBackButton(
+ onPressed: () {
+ unawaited(_refreshBalance());
+ Navigator.of(context).pop(
+ widget.type == CoinControlViewType.use
+ ? _selectedAvailable
+ : null);
+ },
+ ),
+ title: _isSearching
+ ? AppBarSearchField(
+ controller: searchController,
+ focusNode: searchFocus,
+ )
+ : Text(
+ "Coin control",
+ style: STextStyles.navBarTitle(context),
+ ),
+ titleSpacing: 0,
+ actions: _isSearching
+ ? [
+ AspectRatio(
+ aspectRatio: 1,
+ child: AppBarIconButton(
+ size: 36,
+ icon: SvgPicture.asset(
+ Assets.svg.x,
+ width: 20,
+ height: 20,
+ color: Theme.of(context)
+ .extension()!
+ .topNavIconPrimary,
+ ),
+ onPressed: () {
+ // show search
+ setState(() {
+ _isSearching = false;
+ });
+ },
+ ),
+ ),
+ ]
+ : [
+ AspectRatio(
+ aspectRatio: 1,
+ child: AppBarIconButton(
+ size: 36,
+ icon: SvgPicture.asset(
+ Assets.svg.search,
+ width: 20,
+ height: 20,
+ color: Theme.of(context)
+ .extension()!
+ .topNavIconPrimary,
+ ),
+ onPressed: () {
+ // show search
+ setState(() {
+ _isSearching = true;
+ });
+ },
+ ),
+ ),
+ AspectRatio(
+ aspectRatio: 1,
+ child: JDropdownIconButton(
+ mobileAppBar: true,
+ groupValue: _sort,
+ items: CCSortDescriptor.values.toSet(),
+ onSelectionChanged: (CCSortDescriptor? newValue) {
+ if (newValue != null && newValue != _sort) {
+ setState(() {
+ _sort = newValue;
+ });
+ }
+ },
+ displayPrefix: "Sort by",
+ ),
+ ),
+ ],
+ ),
+ body: SafeArea(
+ child: Column(
+ children: [
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ ),
+ child: Column(
+ children: [
+ const SizedBox(
+ height: 10,
+ ),
+ if (!_isSearching)
+ 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()!
+ .textSubtitle1,
+ ),
+ ),
+ ),
+ if (!_isSearching)
+ const SizedBox(
+ height: 10,
+ ),
+ if (!(_isSearching || _map != null))
+ SizedBox(
+ height: 48,
+ child: Toggle(
+ key: UniqueKey(),
+ onColor: Theme.of(context)
+ .extension()!
+ .popupBG,
+ onText: "Available outputs",
+ offColor: Theme.of(context)
+ .extension()!
+ .textFieldDefaultBG,
+ offText: "Frozen outputs",
+ isOn: _showBlocked,
+ onValueChanged: (value) {
+ setState(() {
+ _showBlocked = value;
+ });
+ },
+ decoration: BoxDecoration(
+ color: Colors.transparent,
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ ),
+ ),
+ if (!_isSearching)
+ const SizedBox(
+ height: 10,
+ ),
+ if (_isSearching)
+ Expanded(
+ child: ListView.separated(
+ itemCount: _list!.length,
+ separatorBuilder: (context, _) => const SizedBox(
+ height: 10,
+ ),
+ itemBuilder: (context, index) {
+ final utxo = MainDB.instance.isar.utxos
+ .where()
+ .idEqualTo(_list![index])
+ .findFirstSync()!;
+
+ final isSelected =
+ _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 &&
+ !utxo.isBlocked &&
+ utxo.isConfirmed(
+ currentChainHeight,
+ coin.requiredConfirmations,
+ )),
+ initialSelectedState: isSelected,
+ onSelectedChanged: (value) {
+ if (value) {
+ utxo.isBlocked
+ ? _selectedBlocked.add(utxo)
+ : _selectedAvailable.add(utxo);
+ } else {
+ utxo.isBlocked
+ ? _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 (!_isSearching)
+ _list != null
+ ? Expanded(
+ child: ListView.separated(
+ itemCount: _list!.length,
+ separatorBuilder: (context, _) =>
+ const SizedBox(
+ height: 10,
+ ),
+ itemBuilder: (context, index) {
+ final utxo = MainDB.instance.isar.utxos
+ .where()
+ .idEqualTo(_list![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(() {});
+ }
+ },
+ );
+ },
+ ),
+ )
+ : Expanded(
+ child: ListView.separated(
+ itemCount: _map!.entries.length,
+ separatorBuilder: (context, _) =>
+ const SizedBox(
+ height: 10,
+ ),
+ itemBuilder: (context, index) {
+ final entry =
+ _map!.entries.elementAt(index);
+ final _controller =
+ RotateIconController();
+
+ return Expandable2(
+ border: Theme.of(context)
+ .extension()!
+ .backgroundAppBar,
+ background: Theme.of(context)
+ .extension()!
+ .popupBG,
+ animationDurationMultiplier:
+ 0.2 * entry.value.length,
+ onExpandWillChange: (state) {
+ if (state ==
+ Expandable2State.expanded) {
+ _controller.forward?.call();
+ } else {
+ _controller.reverse?.call();
+ }
+ },
+ header: RoundedContainer(
+ padding: const EdgeInsets.all(14),
+ color: Colors.transparent,
+ child: Row(
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.start,
+ children: [
+ Text(
+ entry.key,
+ style:
+ STextStyles.w600_14(
+ context),
+ ),
+ const SizedBox(
+ height: 2,
+ ),
+ Text(
+ "${entry.value.length} "
+ "output${entry.value.length > 1 ? "s" : ""}",
+ style:
+ STextStyles.w500_12(
+ context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension<
+ StackColors>()!
+ .textSubtitle1,
+ ),
+ ),
+ ],
+ ),
+ ),
+ RotateIcon(
+ animationDurationMultiplier:
+ 0.2 * entry.value.length,
+ icon: SvgPicture.asset(
+ Assets.svg.chevronDown,
+ width: 14,
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ curve: Curves.easeInOut,
+ controller: _controller,
+ ),
+ ],
+ ),
+ ),
+ children: entry.value.map(
+ (id) {
+ final utxo = MainDB
+ .instance.isar.utxos
+ .where()
+ .idEqualTo(id)
+ .findFirstSync()!;
+
+ final isSelected = _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 &&
+ !utxo.isBlocked &&
+ utxo.isConfirmed(
+ currentChainHeight,
+ coin.requiredConfirmations,
+ )),
+ initialSelectedState: isSelected,
+ onSelectedChanged: (value) {
+ if (value) {
+ utxo.isBlocked
+ ? _selectedBlocked
+ .add(utxo)
+ : _selectedAvailable
+ .add(utxo);
+ } else {
+ utxo.isBlocked
+ ? _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(() {});
+ }
+ },
+ );
+ },
+ ).toList(),
+ );
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ if (((_showBlocked && _selectedBlocked.isNotEmpty) ||
+ (!_showBlocked && _selectedAvailable.isNotEmpty)) &&
+ widget.type == CoinControlViewType.manage)
+ Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context)
+ .extension()!
+ .backgroundAppBar,
+ boxShadow: [
+ Theme.of(context)
+ .extension()!
+ .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()!
+ .backgroundAppBar,
+ boxShadow: [
+ Theme.of(context)
+ .extension()!
+ .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()!
+ .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 {
+ if (searchFocus.hasFocus) {
+ searchFocus.unfocus();
+ }
+ Navigator.of(context).pop(
+ _selectedAvailable,
+ );
+ },
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/coin_control/utxo_card.dart b/lib/pages/coin_control/utxo_card.dart
new file mode 100644
index 000000000..e2900a48b
--- /dev/null
+++ b/lib/pages/coin_control/utxo_card.dart
@@ -0,0 +1,160 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.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 createState() => _UtxoCardState();
+}
+
+class _UtxoCardState extends ConsumerState {
+ late Stream stream;
+ late UTXO utxo;
+
+ late bool _selected;
+
+ @override
+ void initState() {
+ _selected = widget.initialSelectedState;
+ utxo = widget.utxo;
+
+ stream = MainDB.instance.watchUTXO(id: utxo.id);
+ 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));
+
+ return ConditionalParent(
+ condition: widget.onPressed != null,
+ builder: (child) => MaterialButton(
+ padding: const EdgeInsets.all(0),
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ color: Theme.of(context).extension()!.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()!.popupBG
+ : Colors.transparent,
+ child: StreamBuilder(
+ stream: stream,
+ builder: (context, snapshot) {
+ if (snapshot.hasData) {
+ utxo = snapshot.data!;
+ }
+ return 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()!.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(
+ utxo.name.isNotEmpty
+ ? utxo.name
+ : utxo.address ?? utxo.txid,
+ style: STextStyles.w500_12(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/coin_control/utxo_details_view.dart b/lib/pages/coin_control/utxo_details_view.dart
new file mode 100644
index 000000000..e4b31c63d
--- /dev/null
+++ b/lib/pages/coin_control/utxo_details_view.dart
@@ -0,0 +1,565 @@
+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/wallet_view/transaction_views/transaction_details_view.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/desktop_dialog.dart';
+import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
+import 'package:stackwallet/widgets/desktop/secondary_button.dart';
+import 'package:stackwallet/widgets/icon_widgets/utxo_status_icon.dart';
+import 'package:stackwallet/widgets/rounded_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 createState() => _UtxoDetailsViewState();
+}
+
+class _UtxoDetailsViewState extends ConsumerState {
+ final isDesktop = Util.isDesktop;
+
+ late Stream streamUTXO;
+ UTXO? utxo;
+
+ Stream? streamLabel;
+ AddressLabel? label;
+
+ bool _popWithRefresh = false;
+
+ Future _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: !isDesktop,
+ builder: (child) => Background(
+ child: Scaffold(
+ backgroundColor:
+ Theme.of(context).extension()!.background,
+ appBar: AppBar(
+ backgroundColor:
+ Theme.of(context).extension()!.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(
+ stream: streamUTXO,
+ builder: (context, snapshot) {
+ if (snapshot.hasData) {
+ utxo = snapshot.data!;
+ }
+ return ConditionalParent(
+ condition: isDesktop,
+ builder: (child) {
+ return DesktopDialog(
+ maxHeight: double.infinity,
+ child: Column(
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(left: 32),
+ child: Text(
+ "Output details",
+ style: STextStyles.desktopH3(context),
+ ),
+ ),
+ DesktopDialogCloseButton(
+ onPressedOverride: () {
+ Navigator.of(context)
+ .pop(_popWithRefresh ? "refresh" : null);
+ },
+ ),
+ ],
+ ),
+ IntrinsicHeight(
+ child: Padding(
+ padding: const EdgeInsets.only(
+ left: 32,
+ right: 32,
+ bottom: 32,
+ top: 10,
+ ),
+ child: Column(
+ children: [
+ IntrinsicHeight(
+ child: RoundedContainer(
+ padding: EdgeInsets.zero,
+ color: Colors.transparent,
+ borderColor: Theme.of(context)
+ .extension()!
+ .textFieldDefaultBG,
+ child: child,
+ ),
+ ),
+ const SizedBox(
+ height: 20,
+ ),
+ SecondaryButton(
+ buttonHeight: ButtonHeight.l,
+ label: utxo!.isBlocked ? "Unfreeze" : "Freeze",
+ onPressed: _toggleFreeze,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ if (!isDesktop)
+ const SizedBox(
+ height: 10,
+ ),
+ RoundedContainer(
+ padding: const EdgeInsets.all(12),
+ color: isDesktop
+ ? Colors.transparent
+ : Theme.of(context).extension()!.popupBG,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ if (isDesktop)
+ UTXOStatusIcon(
+ blocked: utxo!.isBlocked,
+ status: confirmed
+ ? UTXOStatusIconStatus.confirmed
+ : UTXOStatusIconStatus.unconfirmed,
+ background: Theme.of(context)
+ .extension()!
+ .popupBG,
+ selected: false,
+ width: 32,
+ height: 32,
+ ),
+ if (isDesktop)
+ const SizedBox(
+ width: 16,
+ ),
+ 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()!
+ .accentColorGreen
+ : Theme.of(context)
+ .extension()!
+ .accentColorYellow,
+ ),
+ ),
+ ],
+ ),
+ ),
+ const _Div(),
+ RoundedContainer(
+ padding: isDesktop
+ ? const EdgeInsets.all(16)
+ : const EdgeInsets.all(12),
+ color: isDesktop
+ ? Colors.transparent
+ : Theme.of(context).extension()!.popupBG,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ "Label",
+ style: STextStyles.w500_14(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ ),
+ SimpleEditButton(
+ editValue: utxo!.name,
+ editLabel: "label",
+ onValueChanged: (newName) {
+ MainDB.instance.putUTXO(
+ utxo!.copyWith(
+ name: newName,
+ ),
+ );
+ },
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 4,
+ ),
+ Text(
+ utxo!.name,
+ style: STextStyles.w500_14(context),
+ ),
+ ],
+ ),
+ ),
+ const _Div(),
+ RoundedContainer(
+ padding: isDesktop
+ ? const EdgeInsets.all(16)
+ : const EdgeInsets.all(12),
+ color: isDesktop
+ ? Colors.transparent
+ : Theme.of(context).extension()!.popupBG,
+ 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()!
+ .textSubtitle1,
+ ),
+ ),
+ isDesktop
+ ? IconCopyButton(
+ data: utxo!.address!,
+ )
+ : SimpleCopyButton(
+ data: utxo!.address!,
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 4,
+ ),
+ Text(
+ utxo!.address!,
+ style: STextStyles.w500_14(context),
+ ),
+ ],
+ ),
+ ),
+ if (label != null && label!.value.isNotEmpty) const _Div(),
+ if (label != null && label!.value.isNotEmpty)
+ RoundedContainer(
+ padding: isDesktop
+ ? const EdgeInsets.all(16)
+ : const EdgeInsets.all(12),
+ color: isDesktop
+ ? Colors.transparent
+ : Theme.of(context).extension()!.popupBG,
+ 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()!
+ .textSubtitle1,
+ ),
+ ),
+ isDesktop
+ ? IconCopyButton(
+ data: utxo!.address!,
+ )
+ : SimpleCopyButton(
+ data: label!.value,
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 4,
+ ),
+ Text(
+ label!.value,
+ style: STextStyles.w500_14(context),
+ ),
+ ],
+ ),
+ ),
+ const _Div(),
+ RoundedContainer(
+ padding: isDesktop
+ ? const EdgeInsets.all(16)
+ : const EdgeInsets.all(12),
+ color: isDesktop
+ ? Colors.transparent
+ : Theme.of(context).extension()!.popupBG,
+ 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()!
+ .textSubtitle1,
+ ),
+ ),
+ isDesktop
+ ? IconCopyButton(
+ data: utxo!.address!,
+ )
+ : SimpleCopyButton(
+ data: utxo!.txid,
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 4,
+ ),
+ Text(
+ utxo!.txid,
+ style: STextStyles.w500_14(context),
+ ),
+ ],
+ ),
+ ),
+ const _Div(),
+ RoundedContainer(
+ padding: isDesktop
+ ? const EdgeInsets.all(16)
+ : const EdgeInsets.all(12),
+ color: isDesktop
+ ? Colors.transparent
+ : Theme.of(context).extension()!.popupBG,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Confirmations",
+ style: STextStyles.w500_14(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ ),
+ const SizedBox(
+ height: 4,
+ ),
+ Text(
+ "${utxo!.getConfirmations(currentHeight)}",
+ style: STextStyles.w500_14(context),
+ ),
+ ],
+ ),
+ ),
+ if (utxo!.isBlocked) const _Div(),
+ if (utxo!.isBlocked)
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ RoundedContainer(
+ padding: isDesktop
+ ? const EdgeInsets.all(16)
+ : const EdgeInsets.all(12),
+ color: isDesktop
+ ? Colors.transparent
+ : Theme.of(context)
+ .extension()!
+ .popupBG,
+ 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()!
+ .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),
+ ),
+ ],
+ ),
+ ),
+ if (!isDesktop) const _Div(),
+ ],
+ ),
+ if (!isDesktop) const Spacer(),
+ if (!isDesktop)
+ SecondaryButton(
+ label: utxo!.isBlocked ? "Unfreeze" : "Freeze",
+ onPressed: _toggleFreeze,
+ ),
+ if (!isDesktop)
+ const SizedBox(
+ height: 16,
+ ),
+ ],
+ ),
+ );
+ }),
+ );
+ }
+}
+
+class _Div extends StatelessWidget {
+ const _Div({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ if (Util.isDesktop) {
+ return Container(
+ width: double.infinity,
+ height: 1.0,
+ color: Theme.of(context).extension()!.textFieldDefaultBG,
+ );
+ } else {
+ return const SizedBox(
+ height: 12,
+ );
+ }
+ }
+}
diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart
index 808a0417f..23712f0fd 100644
--- a/lib/pages/exchange_view/confirm_change_now_send.dart
+++ b/lib/pages/exchange_view/confirm_change_now_send.dart
@@ -64,33 +64,49 @@ class _ConfirmChangeNowSendViewState
final isDesktop = Util.isDesktop;
Future _attemptSend(BuildContext context) async {
+ final manager =
+ ref.read(walletsChangeNotifierProvider).getManager(walletId);
unawaited(
showDialog(
context: context,
useSafeArea: false,
barrierDismissible: false,
builder: (context) {
- return const SendingTransactionDialog();
+ return SendingTransactionDialog(
+ coin: manager.coin,
+ );
},
),
);
+ final time = Future.delayed(
+ const Duration(
+ milliseconds: 2500,
+ ),
+ );
+
+ late String txid;
+ Future txidFuture;
+
final String note = transactionInfo["note"] as String? ?? "";
- final manager =
- ref.read(walletsChangeNotifierProvider).getManager(walletId);
try {
- late final String txid;
-
if (widget.shouldSendPublicFiroFunds == true) {
- txid = await (manager.wallet as FiroWallet)
+ txidFuture = (manager.wallet as FiroWallet)
.confirmSendPublic(txData: transactionInfo);
} else {
- txid = await manager.confirmSend(txData: transactionInfo);
+ txidFuture = manager.confirmSend(txData: transactionInfo);
}
unawaited(manager.refresh());
+ final results = await Future.wait([
+ txidFuture,
+ time,
+ ]);
+
+ txid = results.first as String;
+
// save note
await ref
.read(notesServiceChangeNotifierProvider(walletId))
diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart
index c04360bf6..ab7cab56e 100644
--- a/lib/pages/exchange_view/exchange_form.dart
+++ b/lib/pages/exchange_view/exchange_form.dart
@@ -618,6 +618,16 @@ class _ExchangeFormState extends ConsumerState {
if (walletInitiated) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
ref.read(exchangeFormStateProvider).reset(shouldNotifyListeners: true);
+ ExchangeDataLoadingService.instance
+ .getAggregateCurrency(
+ coin!.ticker,
+ ExchangeRateType.estimated,
+ )
+ .then((value) {
+ if (value != null) {
+ ref.read(exchangeFormStateProvider).updateSendCurrency(value, true);
+ }
+ });
});
} else {
_sendController.text =
@@ -848,7 +858,7 @@ class _ExchangeFormState extends ConsumerState {
enabled: ref.watch(
exchangeFormStateProvider.select((value) => value.canExchange)),
onPressed: onExchangePressed,
- label: "Exchange",
+ label: "Swap",
)
],
);
diff --git a/lib/pages/exchange_view/exchange_step_views/step_1_view.dart b/lib/pages/exchange_view/exchange_step_views/step_1_view.dart
index c57e0acad..b866de448 100644
--- a/lib/pages/exchange_view/exchange_step_views/step_1_view.dart
+++ b/lib/pages/exchange_view/exchange_step_views/step_1_view.dart
@@ -56,7 +56,7 @@ class _Step1ViewState extends State {
},
),
title: Text(
- "Exchange",
+ "Swap",
style: STextStyles.navBarTitle(context),
),
),
diff --git a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart
index 61f3517ce..a32df6e74 100644
--- a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart
+++ b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart
@@ -144,7 +144,7 @@ class _Step2ViewState extends ConsumerState {
},
),
title: Text(
- "Exchange",
+ "Swap",
style: STextStyles.navBarTitle(context),
),
),
diff --git a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart
index 0c5eaa8c9..22c356b5d 100644
--- a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart
+++ b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart
@@ -72,7 +72,7 @@ class _Step3ViewState extends ConsumerState {
},
),
title: Text(
- "Exchange",
+ "Swap",
style: STextStyles.navBarTitle(context),
),
),
diff --git a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart
index 2507ac2d4..30d40f008 100644
--- a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart
+++ b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart
@@ -152,7 +152,7 @@ class _Step4ViewState extends ConsumerState {
),
),
title: Text(
- "Exchange",
+ "Swap",
style: STextStyles.navBarTitle(context),
),
),
@@ -536,24 +536,34 @@ class _Step4ViewState extends ConsumerState {
try {
bool wasCancelled = false;
- unawaited(showDialog(
- context: context,
- useSafeArea: false,
- barrierDismissible: false,
- builder: (context) {
- return BuildingTransactionDialog(
- onCancel: () {
- wasCancelled = true;
+ unawaited(
+ showDialog(
+ context: context,
+ useSafeArea: false,
+ barrierDismissible: false,
+ builder: (context) {
+ return BuildingTransactionDialog(
+ coin: manager.coin,
+ onCancel: () {
+ wasCancelled = true;
- Navigator.of(context)
- .pop();
- },
- );
- },
- ));
+ Navigator.of(context)
+ .pop();
+ },
+ );
+ },
+ ),
+ );
- final txData =
- await manager.prepareSend(
+ final time =
+ Future.delayed(
+ const Duration(
+ milliseconds: 2500,
+ ),
+ );
+
+ final txDataFuture =
+ manager.prepareSend(
address: address,
satoshiAmount: amount,
args: {
@@ -563,6 +573,15 @@ class _Step4ViewState extends ConsumerState {
},
);
+ final results =
+ await Future.wait([
+ txDataFuture,
+ time,
+ ]);
+
+ final txData = results.last
+ as Map;
+
if (!wasCancelled) {
// pop building dialog
diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart
index 41df723da..780a88d92 100644
--- a/lib/pages/exchange_view/send_from_view.dart
+++ b/lib/pages/exchange_view/send_from_view.dart
@@ -240,6 +240,7 @@ class _SendFromCardState extends ConsumerState {
),
),
child: BuildingTransactionDialog(
+ coin: manager.coin,
onCancel: () {
wasCancelled = true;
@@ -251,11 +252,18 @@ class _SendFromCardState extends ConsumerState {
),
);
- late Map txData;
+ final time = Future.delayed(
+ const Duration(
+ milliseconds: 2500,
+ ),
+ );
+
+ Map txData;
+ Future