mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-30 22:25:54 +00:00
Merge branch 'isar-transactions' into isar_migrate
# Conflicts: # lib/services/coins/bitcoin/bitcoin_wallet.dart # lib/services/coins/bitcoincash/bitcoincash_wallet.dart # lib/services/coins/coin_service.dart # lib/services/coins/dogecoin/dogecoin_wallet.dart # lib/services/coins/firo/firo_wallet.dart # lib/services/coins/litecoin/litecoin_wallet.dart # lib/services/coins/monero/monero_wallet.dart # lib/services/coins/namecoin/namecoin_wallet.dart # lib/services/coins/particl/particl_wallet.dart # lib/services/coins/wownero/wownero_wallet.dart # pubspec.yaml
This commit is contained in:
commit
830fd58754
184 changed files with 37377 additions and 26934 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -55,3 +55,4 @@ libcw_monero.dll
|
||||||
libcw_wownero.dll
|
libcw_wownero.dll
|
||||||
libepic_cash_wallet.dll
|
libepic_cash_wallet.dll
|
||||||
libmobileliblelantus.dll
|
libmobileliblelantus.dll
|
||||||
|
/libisar.so
|
||||||
|
|
3
assets/svg/circle-plus-filled.svg
Normal file
3
assets/svg/circle-plus-filled.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="M0 10C0 4.47656 4.47656 0 10 0C15.5234 0 20 4.47656 20 10C20 15.5234 15.5234 20 10 20C4.47656 20 0 15.5234 0 10ZM10 14.375C10.5195 14.375 10.9375 13.957 10.9375 13.4375V10.9375H13.4375C13.957 10.9375 14.375 10.5195 14.375 10C14.375 9.48047 13.957 9.0625 13.4375 9.0625H10.9375V6.5625C10.9375 6.04297 10.5195 5.625 10 5.625C9.48047 5.625 9.0625 6.04297 9.0625 6.5625V9.0625H6.5625C6.04297 9.0625 5.625 9.48047 5.625 10C5.625 10.5195 6.04297 10.9375 6.5625 10.9375H9.0625V13.4375C9.0625 13.957 9.48047 14.375 10 14.375Z" fill="#232323"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 648 B |
3
assets/svg/user-minus.svg
Normal file
3
assets/svg/user-minus.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4.29219 6.25098H2.70781C1.21266 6.25098 0 7.46348 0 8.95879C0 9.25879 0.2425 9.50098 0.541562 9.50098H6.45875C6.75781 9.50098 7 9.25879 7 8.95879C7 7.46348 5.7875 6.25098 4.29219 6.25098ZM3.5 5.50098C4.60469 5.50098 5.5 4.60551 5.5 3.50098C5.5 2.39645 4.60469 1.50098 3.5 1.50098C2.39531 1.50098 1.5 2.39645 1.5 3.50098C1.5 4.60551 2.39531 5.50098 3.5 5.50098ZM9.625 4.62598H7.375C7.16875 4.62598 7 4.79473 7 5.00098C7 5.20723 7.16797 5.37598 7.375 5.37598H9.625C9.83281 5.37598 10 5.20879 10 5.00098C10 4.79316 9.83281 4.62598 9.625 4.62598Z" fill="#232323"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 674 B |
3
assets/svg/user-plus.svg
Normal file
3
assets/svg/user-plus.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.4 12.0004C11.0513 12.0004 13.2 9.85127 13.2 7.20039C13.2 4.54952 11.0513 2.40039 8.4 2.40039C5.74875 2.40039 3.6 4.54952 3.6 7.20039C3.6 9.85127 5.74875 12.0004 8.4 12.0004ZM10.3013 13.8004H6.49875C2.91038 13.8004 0 16.7104 0 20.2991C0 21.0191 0.582 21.6004 1.29975 21.6004H15.501C16.2188 21.6004 16.8 21.0191 16.8 20.2991C16.8 16.7104 13.89 13.8004 10.3013 13.8004ZM23.1 9.90039H21.3V8.10039C21.3 7.60539 20.8988 7.20039 20.4 7.20039C19.9013 7.20039 19.5 7.60352 19.5 8.10039V9.90039H17.7C17.205 9.90039 16.8 10.3054 16.8 10.8004C16.8 11.2954 17.2031 11.7004 17.7 11.7004H19.5V13.5004C19.5 13.9991 19.905 14.4004 20.4 14.4004C20.895 14.4004 21.3 13.9973 21.3 13.5004V11.7004H23.1C23.5988 11.7004 24 11.2991 24 10.8004C24 10.3016 23.5988 9.90039 23.1 9.90039Z" fill="#232323"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 893 B |
|
@ -1 +1 @@
|
||||||
Subproject commit 3aff42511ec8107163a3d1e0b42f2c83c21f6896
|
Subproject commit f1031db5bb67b38d028187f0ead192acb3e9ba55
|
236
lib/db/main_db.dart
Normal file
236
lib/db/main_db.dart
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
|
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
class MainDB {
|
||||||
|
MainDB._();
|
||||||
|
static MainDB? _instance;
|
||||||
|
static MainDB get instance => _instance ??= MainDB._();
|
||||||
|
|
||||||
|
Isar? _isar;
|
||||||
|
|
||||||
|
Isar get isar => _isar!;
|
||||||
|
|
||||||
|
Future<bool> isarInit({Isar? mock}) async {
|
||||||
|
if (mock != null) {
|
||||||
|
_isar = mock;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_isar != null && isar.isOpen) return false;
|
||||||
|
_isar = await Isar.open(
|
||||||
|
[
|
||||||
|
TransactionSchema,
|
||||||
|
TransactionNoteSchema,
|
||||||
|
InputSchema,
|
||||||
|
OutputSchema,
|
||||||
|
UTXOSchema,
|
||||||
|
AddressSchema,
|
||||||
|
],
|
||||||
|
directory: (await StackFileSystem.applicationIsarDirectory()).path,
|
||||||
|
inspector: true,
|
||||||
|
name: "wallet_data",
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// addresses
|
||||||
|
QueryBuilder<Address, Address, QAfterWhereClause> getAddresses(
|
||||||
|
String walletId) =>
|
||||||
|
isar.addresses.where().walletIdEqualTo(walletId);
|
||||||
|
|
||||||
|
Future<void> putAddress(Address address) => isar.writeTxn(() async {
|
||||||
|
await isar.addresses.put(address);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> putAddresses(List<Address> addresses) => isar.writeTxn(() async {
|
||||||
|
await isar.addresses.putAll(addresses);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> updateAddress(Address oldAddress, Address newAddress) =>
|
||||||
|
isar.writeTxn(() async {
|
||||||
|
newAddress.id = oldAddress.id;
|
||||||
|
await oldAddress.transactions.load();
|
||||||
|
final txns = oldAddress.transactions.toList();
|
||||||
|
await isar.addresses.delete(oldAddress.id);
|
||||||
|
await isar.addresses.put(newAddress);
|
||||||
|
newAddress.transactions.addAll(txns);
|
||||||
|
await newAddress.transactions.save();
|
||||||
|
});
|
||||||
|
|
||||||
|
// transactions
|
||||||
|
QueryBuilder<Transaction, Transaction, QAfterWhereClause> getTransactions(
|
||||||
|
String walletId) =>
|
||||||
|
isar.transactions.where().walletIdEqualTo(walletId);
|
||||||
|
|
||||||
|
Future<void> putTransaction(Transaction transaction) =>
|
||||||
|
isar.writeTxn(() async {
|
||||||
|
await isar.transactions.put(transaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> putTransactions(List<Transaction> transactions) =>
|
||||||
|
isar.writeTxn(() async {
|
||||||
|
await isar.transactions.putAll(transactions);
|
||||||
|
});
|
||||||
|
|
||||||
|
// utxos
|
||||||
|
QueryBuilder<UTXO, UTXO, QAfterWhereClause> getUTXOs(String walletId) =>
|
||||||
|
isar.utxos.where().walletIdEqualTo(walletId);
|
||||||
|
|
||||||
|
Future<void> putUTXO(UTXO utxo) => isar.writeTxn(() async {
|
||||||
|
await isar.utxos.put(utxo);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> putUTXOs(List<UTXO> utxos) => isar.writeTxn(() async {
|
||||||
|
await isar.utxos.putAll(utxos);
|
||||||
|
});
|
||||||
|
|
||||||
|
// inputs
|
||||||
|
QueryBuilder<Input, Input, QAfterWhereClause> getInputs(String walletId) =>
|
||||||
|
isar.inputs.where().walletIdEqualTo(walletId);
|
||||||
|
|
||||||
|
Future<void> putInput(Input input) => isar.writeTxn(() async {
|
||||||
|
await isar.inputs.put(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> putInputs(List<Input> inputs) => isar.writeTxn(() async {
|
||||||
|
await isar.inputs.putAll(inputs);
|
||||||
|
});
|
||||||
|
|
||||||
|
// outputs
|
||||||
|
QueryBuilder<Output, Output, QAfterWhereClause> getOutputs(String walletId) =>
|
||||||
|
isar.outputs.where().walletIdEqualTo(walletId);
|
||||||
|
|
||||||
|
Future<void> putOutput(Output output) => isar.writeTxn(() async {
|
||||||
|
await isar.outputs.put(output);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> putOutputs(List<Output> outputs) => isar.writeTxn(() async {
|
||||||
|
await isar.outputs.putAll(outputs);
|
||||||
|
});
|
||||||
|
|
||||||
|
// transaction notes
|
||||||
|
QueryBuilder<TransactionNote, TransactionNote, QAfterWhereClause>
|
||||||
|
getTransactionNotes(String walletId) =>
|
||||||
|
isar.transactionNotes.where().walletIdEqualTo(walletId);
|
||||||
|
|
||||||
|
Future<void> putTransactionNote(TransactionNote transactionNote) =>
|
||||||
|
isar.writeTxn(() async {
|
||||||
|
await isar.transactionNotes.put(transactionNote);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> putTransactionNotes(List<TransactionNote> transactionNotes) =>
|
||||||
|
isar.writeTxn(() async {
|
||||||
|
await isar.transactionNotes.putAll(transactionNotes);
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
Future<void> deleteWalletBlockchainData(String walletId) async {
|
||||||
|
final transactionCount = await getTransactions(walletId).count();
|
||||||
|
final addressCount = await getAddresses(walletId).count();
|
||||||
|
final utxoCount = await getUTXOs(walletId).count();
|
||||||
|
final inputCount = await getInputs(walletId).count();
|
||||||
|
final outputCount = await getOutputs(walletId).count();
|
||||||
|
|
||||||
|
await isar.writeTxn(() async {
|
||||||
|
const paginateLimit = 50;
|
||||||
|
|
||||||
|
// transactions
|
||||||
|
for (int i = 0; i < transactionCount; i += paginateLimit) {
|
||||||
|
final txns = await getTransactions(walletId)
|
||||||
|
.offset(i)
|
||||||
|
.limit(paginateLimit)
|
||||||
|
.findAll();
|
||||||
|
await isar.transactions
|
||||||
|
.deleteAll(txns.map((e) => e.id).toList(growable: false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// addresses
|
||||||
|
for (int i = 0; i < addressCount; i += paginateLimit) {
|
||||||
|
final addresses = await getAddresses(walletId)
|
||||||
|
.offset(i)
|
||||||
|
.limit(paginateLimit)
|
||||||
|
.findAll();
|
||||||
|
await isar.addresses
|
||||||
|
.deleteAll(addresses.map((e) => e.id).toList(growable: false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// utxos
|
||||||
|
for (int i = 0; i < utxoCount; i += paginateLimit) {
|
||||||
|
final utxos =
|
||||||
|
await getUTXOs(walletId).offset(i).limit(paginateLimit).findAll();
|
||||||
|
await isar.utxos
|
||||||
|
.deleteAll(utxos.map((e) => e.id).toList(growable: false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// inputs
|
||||||
|
for (int i = 0; i < inputCount; i += paginateLimit) {
|
||||||
|
final inputs =
|
||||||
|
await getInputs(walletId).offset(i).limit(paginateLimit).findAll();
|
||||||
|
await isar.inputs
|
||||||
|
.deleteAll(inputs.map((e) => e.id).toList(growable: false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputs
|
||||||
|
for (int i = 0; i < outputCount; i += paginateLimit) {
|
||||||
|
final outputs =
|
||||||
|
await getOutputs(walletId).offset(i).limit(paginateLimit).findAll();
|
||||||
|
await isar.outputs
|
||||||
|
.deleteAll(outputs.map((e) => e.id).toList(growable: false));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addNewTransactionData(
|
||||||
|
List<Tuple4<Transaction, List<Output>, List<Input>, Address?>>
|
||||||
|
transactionsData,
|
||||||
|
String walletId) async {
|
||||||
|
await isar.writeTxn(() async {
|
||||||
|
for (final data in transactionsData) {
|
||||||
|
final tx = data.item1;
|
||||||
|
|
||||||
|
final potentiallyUnconfirmedTx = await getTransactions(walletId)
|
||||||
|
.filter()
|
||||||
|
.txidEqualTo(tx.txid)
|
||||||
|
.findFirst();
|
||||||
|
if (potentiallyUnconfirmedTx != null) {
|
||||||
|
// update use id to replace tx
|
||||||
|
tx.id = potentiallyUnconfirmedTx.id;
|
||||||
|
await isar.transactions.delete(potentiallyUnconfirmedTx.id);
|
||||||
|
}
|
||||||
|
// save transaction
|
||||||
|
await isar.transactions.put(tx);
|
||||||
|
|
||||||
|
// link and save outputs
|
||||||
|
if (data.item2.isNotEmpty) {
|
||||||
|
await isar.outputs.putAll(data.item2);
|
||||||
|
tx.outputs.addAll(data.item2);
|
||||||
|
await tx.outputs.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// link and save inputs
|
||||||
|
if (data.item3.isNotEmpty) {
|
||||||
|
await isar.inputs.putAll(data.item3);
|
||||||
|
tx.inputs.addAll(data.item3);
|
||||||
|
await tx.inputs.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.item4 != null) {
|
||||||
|
final address = await getAddresses(walletId)
|
||||||
|
.filter()
|
||||||
|
.valueEqualTo(data.item4!.value)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
// check if address exists in db and add if it does not
|
||||||
|
if (address == null) {
|
||||||
|
await isar.addresses.put(data.item4!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// link and save address
|
||||||
|
tx.address.value = address ?? data.item4!;
|
||||||
|
await tx.address.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -245,3 +245,11 @@ class DB {
|
||||||
Future<void> deleteBoxFromDisk({required String boxName}) async =>
|
Future<void> deleteBoxFromDisk({required String boxName}) async =>
|
||||||
await mutex.protect(() async => await Hive.deleteBoxFromDisk(boxName));
|
await mutex.protect(() async => await Hive.deleteBoxFromDisk(boxName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class DBKeys {
|
||||||
|
static const String cachedBalance = "cachedBalance";
|
||||||
|
static const String cachedBalanceSecondary = "cachedBalanceSecondary";
|
||||||
|
static const String isFavorite = "isFavorite";
|
||||||
|
static const String id = "id";
|
||||||
|
static const String storedChainHeight = "storedChainHeight";
|
||||||
|
}
|
||||||
|
|
|
@ -64,6 +64,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
import 'package:window_size/window_size.dart';
|
import 'package:window_size/window_size.dart';
|
||||||
|
|
||||||
|
import 'db/main_db.dart';
|
||||||
|
|
||||||
final openedFromSWBFileStringStateProvider =
|
final openedFromSWBFileStringStateProvider =
|
||||||
StateProvider<String?>((ref) => null);
|
StateProvider<String?>((ref) => null);
|
||||||
|
|
||||||
|
@ -155,7 +157,7 @@ void main() async {
|
||||||
|
|
||||||
await Hive.openBox<dynamic>(DB.boxNameDBInfo);
|
await Hive.openBox<dynamic>(DB.boxNameDBInfo);
|
||||||
|
|
||||||
// todo: db migrate stuff for desktop needs to be handled eventually
|
// Desktop migrate handled elsewhere (currently desktop_login_view.dart)
|
||||||
if (!Util.isDesktop) {
|
if (!Util.isDesktop) {
|
||||||
int dbVersion = DB.instance.get<dynamic>(
|
int dbVersion = DB.instance.get<dynamic>(
|
||||||
boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ??
|
boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ??
|
||||||
|
@ -170,7 +172,7 @@ void main() async {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log("Cannot migrate database\n$e $s",
|
Logging.instance.log("Cannot migrate mobile database\n$e $s",
|
||||||
level: LogLevel.Error, printFullLength: true);
|
level: LogLevel.Error, printFullLength: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,6 +265,8 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
||||||
await loadShared();
|
await loadShared();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await MainDB.instance.isarInit();
|
||||||
|
|
||||||
_notificationsService = ref.read(notificationsProvider);
|
_notificationsService = ref.read(notificationsProvider);
|
||||||
_nodeService = ref.read(nodeServiceChangeNotifierProvider);
|
_nodeService = ref.read(nodeServiceChangeNotifierProvider);
|
||||||
_tradesService = ref.read(tradesServiceProvider);
|
_tradesService = ref.read(tradesServiceProvider);
|
||||||
|
|
59
lib/models/balance.dart
Normal file
59
lib/models/balance.dart
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:decimal/decimal.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
|
|
||||||
|
class Balance {
|
||||||
|
final Coin coin;
|
||||||
|
final int total;
|
||||||
|
final int spendable;
|
||||||
|
final int blockedTotal;
|
||||||
|
final int pendingSpendable;
|
||||||
|
|
||||||
|
Balance({
|
||||||
|
required this.coin,
|
||||||
|
required this.total,
|
||||||
|
required this.spendable,
|
||||||
|
required this.blockedTotal,
|
||||||
|
required this.pendingSpendable,
|
||||||
|
});
|
||||||
|
|
||||||
|
Decimal getTotal({bool includeBlocked = false}) => Format.satoshisToAmount(
|
||||||
|
includeBlocked ? total : total - blockedTotal,
|
||||||
|
coin: coin,
|
||||||
|
);
|
||||||
|
|
||||||
|
Decimal getSpendable() => Format.satoshisToAmount(
|
||||||
|
spendable,
|
||||||
|
coin: coin,
|
||||||
|
);
|
||||||
|
|
||||||
|
Decimal getPending() => Format.satoshisToAmount(
|
||||||
|
pendingSpendable,
|
||||||
|
coin: coin,
|
||||||
|
);
|
||||||
|
|
||||||
|
Decimal getBlocked() => Format.satoshisToAmount(
|
||||||
|
blockedTotal,
|
||||||
|
coin: coin,
|
||||||
|
);
|
||||||
|
|
||||||
|
String toJsonIgnoreCoin() => jsonEncode({
|
||||||
|
"total": total,
|
||||||
|
"spendable": spendable,
|
||||||
|
"blockedTotal": blockedTotal,
|
||||||
|
"pendingSpendable": pendingSpendable,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Balance.fromJson(String json, Coin coin) {
|
||||||
|
final decoded = jsonDecode(json);
|
||||||
|
return Balance(
|
||||||
|
coin: coin,
|
||||||
|
total: decoded["total"] as int,
|
||||||
|
spendable: decoded["spendable"] as int,
|
||||||
|
blockedTotal: decoded["blockedTotal"] as int,
|
||||||
|
pendingSpendable: decoded["pendingSpendable"] as int,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
94
lib/models/isar/models/address/address.dart
Normal file
94
lib/models/isar/models/address/address.dart
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/address/crypto_currency_address.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
|
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||||
|
|
||||||
|
part 'address.g.dart';
|
||||||
|
|
||||||
|
class AddressException extends SWException {
|
||||||
|
AddressException(super.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Collection(accessor: "addresses")
|
||||||
|
class Address extends CryptoCurrencyAddress {
|
||||||
|
Address({
|
||||||
|
required this.walletId,
|
||||||
|
required this.value,
|
||||||
|
required this.publicKey,
|
||||||
|
required this.derivationIndex,
|
||||||
|
required this.type,
|
||||||
|
required this.subType,
|
||||||
|
this.otherData,
|
||||||
|
});
|
||||||
|
|
||||||
|
Id id = Isar.autoIncrement;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
late final String walletId;
|
||||||
|
|
||||||
|
@Index(unique: true, composite: [CompositeIndex("walletId")])
|
||||||
|
late final String value;
|
||||||
|
|
||||||
|
late final List<byte> publicKey;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
late final int derivationIndex; // -1 generally means unknown
|
||||||
|
|
||||||
|
@enumerated
|
||||||
|
late final AddressType type;
|
||||||
|
|
||||||
|
@enumerated
|
||||||
|
late final AddressSubType subType;
|
||||||
|
|
||||||
|
late final String? otherData;
|
||||||
|
|
||||||
|
final transactions = IsarLinks<Transaction>();
|
||||||
|
|
||||||
|
int derivationChain() {
|
||||||
|
if (subType == AddressSubType.receiving) {
|
||||||
|
return 0; // 0 for receiving (external)
|
||||||
|
} else if (subType == AddressSubType.change) {
|
||||||
|
return 1; // 1 for change (internal)
|
||||||
|
} else {
|
||||||
|
throw AddressException("Could not imply derivation chain value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isPaynymAddress() =>
|
||||||
|
subType == AddressSubType.paynymNotification ||
|
||||||
|
subType == AddressSubType.paynymSend ||
|
||||||
|
subType == AddressSubType.paynymReceive;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => "{ "
|
||||||
|
"id: $id, "
|
||||||
|
"walletId: $walletId, "
|
||||||
|
"value: $value, "
|
||||||
|
"publicKey: $publicKey, "
|
||||||
|
"derivationIndex: $derivationIndex, "
|
||||||
|
"type: ${type.name}, "
|
||||||
|
"subType: ${subType.name}, "
|
||||||
|
"transactionsLength: ${transactions.length} "
|
||||||
|
"otherData: $otherData, "
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AddressType {
|
||||||
|
p2pkh,
|
||||||
|
p2sh,
|
||||||
|
p2wpkh,
|
||||||
|
cryptonote,
|
||||||
|
mimbleWimble,
|
||||||
|
unknown,
|
||||||
|
nonWallet,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AddressSubType {
|
||||||
|
receiving,
|
||||||
|
change,
|
||||||
|
paynymNotification,
|
||||||
|
paynymSend,
|
||||||
|
paynymReceive,
|
||||||
|
unknown,
|
||||||
|
nonWallet,
|
||||||
|
}
|
1737
lib/models/isar/models/address/address.g.dart
Normal file
1737
lib/models/isar/models/address/address.g.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,3 @@
|
||||||
|
abstract class CryptoCurrencyAddress {
|
||||||
|
// future use?
|
||||||
|
}
|
46
lib/models/isar/models/blockchain_data/input.dart
Normal file
46
lib/models/isar/models/blockchain_data/input.dart
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/output.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
|
|
||||||
|
part 'input.g.dart';
|
||||||
|
|
||||||
|
@Collection()
|
||||||
|
class Input {
|
||||||
|
Input({
|
||||||
|
required this.walletId,
|
||||||
|
required this.txid,
|
||||||
|
required this.vout,
|
||||||
|
required this.scriptSig,
|
||||||
|
required this.scriptSigAsm,
|
||||||
|
required this.isCoinbase,
|
||||||
|
required this.sequence,
|
||||||
|
required this.innerRedeemScriptAsm,
|
||||||
|
});
|
||||||
|
|
||||||
|
Id id = Isar.autoIncrement;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
late final String walletId;
|
||||||
|
|
||||||
|
late final String txid;
|
||||||
|
|
||||||
|
late final int vout;
|
||||||
|
|
||||||
|
late final String? scriptSig;
|
||||||
|
|
||||||
|
late final String? scriptSigAsm;
|
||||||
|
|
||||||
|
// TODO: find witness type // is it even used?
|
||||||
|
// late List<dynamic>? witness;
|
||||||
|
|
||||||
|
late final bool? isCoinbase;
|
||||||
|
|
||||||
|
late final int? sequence;
|
||||||
|
|
||||||
|
late final String? innerRedeemScriptAsm;
|
||||||
|
|
||||||
|
final prevOut = IsarLink<Output>();
|
||||||
|
|
||||||
|
@Backlink(to: 'inputs')
|
||||||
|
final transaction = IsarLink<Transaction>();
|
||||||
|
}
|
1584
lib/models/isar/models/blockchain_data/input.g.dart
Normal file
1584
lib/models/isar/models/blockchain_data/input.g.dart
Normal file
File diff suppressed because it is too large
Load diff
34
lib/models/isar/models/blockchain_data/output.dart
Normal file
34
lib/models/isar/models/blockchain_data/output.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
|
|
||||||
|
part 'output.g.dart';
|
||||||
|
|
||||||
|
@Collection()
|
||||||
|
class Output {
|
||||||
|
Output({
|
||||||
|
required this.walletId,
|
||||||
|
required this.scriptPubKey,
|
||||||
|
required this.scriptPubKeyAsm,
|
||||||
|
required this.scriptPubKeyType,
|
||||||
|
required this.scriptPubKeyAddress,
|
||||||
|
required this.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
Id id = Isar.autoIncrement;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
late final String walletId;
|
||||||
|
|
||||||
|
late final String? scriptPubKey;
|
||||||
|
|
||||||
|
late final String? scriptPubKeyAsm;
|
||||||
|
|
||||||
|
late final String? scriptPubKeyType;
|
||||||
|
|
||||||
|
late final String scriptPubKeyAddress;
|
||||||
|
|
||||||
|
late final int value;
|
||||||
|
|
||||||
|
@Backlink(to: 'outputs')
|
||||||
|
final transaction = IsarLink<Transaction>();
|
||||||
|
}
|
1389
lib/models/isar/models/blockchain_data/output.g.dart
Normal file
1389
lib/models/isar/models/blockchain_data/output.g.dart
Normal file
File diff suppressed because it is too large
Load diff
114
lib/models/isar/models/blockchain_data/transaction.dart
Normal file
114
lib/models/isar/models/blockchain_data/transaction.dart
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/address/address.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/input.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/output.dart';
|
||||||
|
|
||||||
|
part 'transaction.g.dart';
|
||||||
|
|
||||||
|
@Collection()
|
||||||
|
class Transaction {
|
||||||
|
Transaction({
|
||||||
|
required this.walletId,
|
||||||
|
required this.txid,
|
||||||
|
required this.timestamp,
|
||||||
|
required this.type,
|
||||||
|
required this.subType,
|
||||||
|
required this.amount,
|
||||||
|
required this.fee,
|
||||||
|
required this.height,
|
||||||
|
required this.isCancelled,
|
||||||
|
required this.isLelantus,
|
||||||
|
required this.slateId,
|
||||||
|
required this.otherData,
|
||||||
|
});
|
||||||
|
|
||||||
|
Id id = Isar.autoIncrement;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
late final String walletId;
|
||||||
|
|
||||||
|
@Index(unique: true, composite: [CompositeIndex("walletId")])
|
||||||
|
late final String txid;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
late final int timestamp;
|
||||||
|
|
||||||
|
@enumerated
|
||||||
|
late final TransactionType type;
|
||||||
|
|
||||||
|
@enumerated
|
||||||
|
late final TransactionSubType subType;
|
||||||
|
|
||||||
|
late final int amount;
|
||||||
|
|
||||||
|
late final int fee;
|
||||||
|
|
||||||
|
late final int? height;
|
||||||
|
|
||||||
|
late final bool isCancelled;
|
||||||
|
|
||||||
|
late bool? isLelantus;
|
||||||
|
|
||||||
|
late final String? slateId;
|
||||||
|
|
||||||
|
late final String? otherData;
|
||||||
|
|
||||||
|
@Backlink(to: "transactions")
|
||||||
|
final address = IsarLink<Address>();
|
||||||
|
|
||||||
|
final inputs = IsarLinks<Input>();
|
||||||
|
|
||||||
|
final outputs = IsarLinks<Output>();
|
||||||
|
|
||||||
|
int getConfirmations(int currentChainHeight) {
|
||||||
|
if (height == null || height! <= 0) return 0;
|
||||||
|
return max(0, currentChainHeight - (height! - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isConfirmed(int currentChainHeight, int minimumConfirms) {
|
||||||
|
final confirmations = getConfirmations(currentChainHeight);
|
||||||
|
return confirmations >= minimumConfirms;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() => "{ "
|
||||||
|
"id: $id, "
|
||||||
|
"walletId: $walletId, "
|
||||||
|
"txid: $txid, "
|
||||||
|
"timestamp: $timestamp, "
|
||||||
|
"type: ${type.name}, "
|
||||||
|
"subType: ${subType.name}, "
|
||||||
|
"amount: $amount, "
|
||||||
|
"fee: $fee, "
|
||||||
|
"height: $height, "
|
||||||
|
"isCancelled: $isCancelled, "
|
||||||
|
"isLelantus: $isLelantus, "
|
||||||
|
"slateId: $slateId, "
|
||||||
|
"otherData: $otherData, "
|
||||||
|
"address: ${address.value}, "
|
||||||
|
"inputsLength: ${inputs.length}, "
|
||||||
|
"outputsLength: ${outputs.length}, "
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used in Isar db and stored there as int indexes so adding/removing values
|
||||||
|
// in this definition should be done extremely carefully in production
|
||||||
|
enum TransactionType {
|
||||||
|
// TODO: add more types before prod release?
|
||||||
|
outgoing,
|
||||||
|
incoming,
|
||||||
|
sentToSelf, // should we keep this?
|
||||||
|
unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used in Isar db and stored there as int indexes so adding/removing values
|
||||||
|
// in this definition should be done extremely carefully in production
|
||||||
|
enum TransactionSubType {
|
||||||
|
// TODO: add more types before prod release?
|
||||||
|
none,
|
||||||
|
bip47Notification, // bip47 payment code notification transaction flag
|
||||||
|
mint, // firo specific
|
||||||
|
join; // firo specific
|
||||||
|
}
|
2324
lib/models/isar/models/blockchain_data/transaction.g.dart
Normal file
2324
lib/models/isar/models/blockchain_data/transaction.g.dart
Normal file
File diff suppressed because it is too large
Load diff
79
lib/models/isar/models/blockchain_data/utxo.dart
Normal file
79
lib/models/isar/models/blockchain_data/utxo.dart
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
part 'utxo.g.dart';
|
||||||
|
|
||||||
|
@Collection(accessor: "utxos")
|
||||||
|
class UTXO {
|
||||||
|
UTXO({
|
||||||
|
required this.walletId,
|
||||||
|
required this.txid,
|
||||||
|
required this.vout,
|
||||||
|
required this.value,
|
||||||
|
required this.name,
|
||||||
|
required this.isBlocked,
|
||||||
|
required this.blockedReason,
|
||||||
|
required this.isCoinbase,
|
||||||
|
required this.blockHash,
|
||||||
|
required this.blockHeight,
|
||||||
|
required this.blockTime,
|
||||||
|
this.otherData,
|
||||||
|
});
|
||||||
|
|
||||||
|
Id id = Isar.autoIncrement;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
late final String walletId;
|
||||||
|
|
||||||
|
@Index(unique: true, replace: true, composite: [CompositeIndex("walletId")])
|
||||||
|
late final String txid;
|
||||||
|
|
||||||
|
late final int vout;
|
||||||
|
|
||||||
|
late final int value;
|
||||||
|
|
||||||
|
late final String name;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
late final bool isBlocked;
|
||||||
|
|
||||||
|
late final String? blockedReason;
|
||||||
|
|
||||||
|
late final bool isCoinbase;
|
||||||
|
|
||||||
|
late final String? blockHash;
|
||||||
|
|
||||||
|
late final int? blockHeight;
|
||||||
|
|
||||||
|
late final int? blockTime;
|
||||||
|
|
||||||
|
late final String? otherData;
|
||||||
|
|
||||||
|
int getConfirmations(int currentChainHeight) {
|
||||||
|
if (blockTime == null || blockHash == null) return 0;
|
||||||
|
if (blockHeight == null || blockHeight! <= 0) return 0;
|
||||||
|
return max(0, currentChainHeight - (blockHeight! - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isConfirmed(int currentChainHeight, int minimumConfirms) {
|
||||||
|
final confirmations = getConfirmations(currentChainHeight);
|
||||||
|
return confirmations >= minimumConfirms;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => "{ "
|
||||||
|
"id: $id, "
|
||||||
|
"walletId: $walletId, "
|
||||||
|
"txid: $txid, "
|
||||||
|
"vout: $vout, "
|
||||||
|
"value: $value, "
|
||||||
|
"name: $name, "
|
||||||
|
"isBlocked: $isBlocked, "
|
||||||
|
"blockedReason: $blockedReason, "
|
||||||
|
"isCoinbase: $isCoinbase, "
|
||||||
|
"blockHash: $blockHash, "
|
||||||
|
"blockHeight: $blockHeight, "
|
||||||
|
"blockTime: $blockTime, "
|
||||||
|
"}";
|
||||||
|
}
|
2016
lib/models/isar/models/blockchain_data/utxo.g.dart
Normal file
2016
lib/models/isar/models/blockchain_data/utxo.g.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,7 @@ part of 'encrypted_string_value.dart';
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, join_return_with_assignment, avoid_js_rounded_ints, prefer_final_locals
|
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||||
|
|
||||||
extension GetEncryptedStringValueCollection on Isar {
|
extension GetEncryptedStringValueCollection on Isar {
|
||||||
IsarCollection<EncryptedStringValue> get encryptedStringValues =>
|
IsarCollection<EncryptedStringValue> get encryptedStringValues =>
|
||||||
|
@ -30,12 +30,9 @@ const EncryptedStringValueSchema = CollectionSchema(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
estimateSize: _encryptedStringValueEstimateSize,
|
estimateSize: _encryptedStringValueEstimateSize,
|
||||||
serializeNative: _encryptedStringValueSerializeNative,
|
serialize: _encryptedStringValueSerialize,
|
||||||
deserializeNative: _encryptedStringValueDeserializeNative,
|
deserialize: _encryptedStringValueDeserialize,
|
||||||
deserializePropNative: _encryptedStringValueDeserializePropNative,
|
deserializeProp: _encryptedStringValueDeserializeProp,
|
||||||
serializeWeb: _encryptedStringValueSerializeWeb,
|
|
||||||
deserializeWeb: _encryptedStringValueDeserializeWeb,
|
|
||||||
deserializePropWeb: _encryptedStringValueDeserializePropWeb,
|
|
||||||
idName: r'id',
|
idName: r'id',
|
||||||
indexes: {
|
indexes: {
|
||||||
r'key': IndexSchema(
|
r'key': IndexSchema(
|
||||||
|
@ -57,7 +54,7 @@ const EncryptedStringValueSchema = CollectionSchema(
|
||||||
getId: _encryptedStringValueGetId,
|
getId: _encryptedStringValueGetId,
|
||||||
getLinks: _encryptedStringValueGetLinks,
|
getLinks: _encryptedStringValueGetLinks,
|
||||||
attach: _encryptedStringValueAttach,
|
attach: _encryptedStringValueAttach,
|
||||||
version: 5,
|
version: '3.0.5',
|
||||||
);
|
);
|
||||||
|
|
||||||
int _encryptedStringValueEstimateSize(
|
int _encryptedStringValueEstimateSize(
|
||||||
|
@ -71,20 +68,19 @@ int _encryptedStringValueEstimateSize(
|
||||||
return bytesCount;
|
return bytesCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _encryptedStringValueSerializeNative(
|
void _encryptedStringValueSerialize(
|
||||||
EncryptedStringValue object,
|
EncryptedStringValue object,
|
||||||
IsarBinaryWriter writer,
|
IsarWriter writer,
|
||||||
List<int> offsets,
|
List<int> offsets,
|
||||||
Map<Type, List<int>> allOffsets,
|
Map<Type, List<int>> allOffsets,
|
||||||
) {
|
) {
|
||||||
writer.writeString(offsets[0], object.key);
|
writer.writeString(offsets[0], object.key);
|
||||||
writer.writeString(offsets[1], object.value);
|
writer.writeString(offsets[1], object.value);
|
||||||
return writer.usedBytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EncryptedStringValue _encryptedStringValueDeserializeNative(
|
EncryptedStringValue _encryptedStringValueDeserialize(
|
||||||
int id,
|
Id id,
|
||||||
IsarBinaryReader reader,
|
IsarReader reader,
|
||||||
List<int> offsets,
|
List<int> offsets,
|
||||||
Map<Type, List<int>> allOffsets,
|
Map<Type, List<int>> allOffsets,
|
||||||
) {
|
) {
|
||||||
|
@ -95,9 +91,8 @@ EncryptedStringValue _encryptedStringValueDeserializeNative(
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
P _encryptedStringValueDeserializePropNative<P>(
|
P _encryptedStringValueDeserializeProp<P>(
|
||||||
Id id,
|
IsarReader reader,
|
||||||
IsarBinaryReader reader,
|
|
||||||
int propertyId,
|
int propertyId,
|
||||||
int offset,
|
int offset,
|
||||||
Map<Type, List<int>> allOffsets,
|
Map<Type, List<int>> allOffsets,
|
||||||
|
@ -112,33 +107,8 @@ P _encryptedStringValueDeserializePropNative<P>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object _encryptedStringValueSerializeWeb(
|
Id _encryptedStringValueGetId(EncryptedStringValue object) {
|
||||||
IsarCollection<EncryptedStringValue> collection,
|
|
||||||
EncryptedStringValue object) {
|
|
||||||
/*final jsObj = IsarNative.newJsObject();*/ throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
EncryptedStringValue _encryptedStringValueDeserializeWeb(
|
|
||||||
IsarCollection<EncryptedStringValue> collection, Object jsObj) {
|
|
||||||
/*final object = EncryptedStringValue();object.id = IsarNative.jsObjectGet(jsObj, r'id') ?? (double.negativeInfinity as int);object.key = IsarNative.jsObjectGet(jsObj, r'key') ?? '';object.value = IsarNative.jsObjectGet(jsObj, r'value') ?? '';*/
|
|
||||||
//return object;
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
P _encryptedStringValueDeserializePropWeb<P>(
|
|
||||||
Object jsObj, String propertyName) {
|
|
||||||
switch (propertyName) {
|
|
||||||
default:
|
|
||||||
throw IsarError('Illegal propertyName');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int? _encryptedStringValueGetId(EncryptedStringValue object) {
|
|
||||||
if (object.id == Isar.autoIncrement) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return object.id;
|
return object.id;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<IsarLinkBase<dynamic>> _encryptedStringValueGetLinks(
|
List<IsarLinkBase<dynamic>> _encryptedStringValueGetLinks(
|
||||||
|
@ -188,19 +158,19 @@ extension EncryptedStringValueByIndex on IsarCollection<EncryptedStringValue> {
|
||||||
return deleteAllByIndexSync(r'key', values);
|
return deleteAllByIndexSync(r'key', values);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> putByKey(EncryptedStringValue object) {
|
Future<Id> putByKey(EncryptedStringValue object) {
|
||||||
return putByIndex(r'key', object);
|
return putByIndex(r'key', object);
|
||||||
}
|
}
|
||||||
|
|
||||||
int putByKeySync(EncryptedStringValue object, {bool saveLinks = true}) {
|
Id putByKeySync(EncryptedStringValue object, {bool saveLinks = true}) {
|
||||||
return putByIndexSync(r'key', object, saveLinks: saveLinks);
|
return putByIndexSync(r'key', object, saveLinks: saveLinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<int>> putAllByKey(List<EncryptedStringValue> objects) {
|
Future<List<Id>> putAllByKey(List<EncryptedStringValue> objects) {
|
||||||
return putAllByIndex(r'key', objects);
|
return putAllByIndex(r'key', objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<int> putAllByKeySync(List<EncryptedStringValue> objects,
|
List<Id> putAllByKeySync(List<EncryptedStringValue> objects,
|
||||||
{bool saveLinks = true}) {
|
{bool saveLinks = true}) {
|
||||||
return putAllByIndexSync(r'key', objects, saveLinks: saveLinks);
|
return putAllByIndexSync(r'key', objects, saveLinks: saveLinks);
|
||||||
}
|
}
|
||||||
|
@ -219,7 +189,7 @@ extension EncryptedStringValueQueryWhereSort
|
||||||
extension EncryptedStringValueQueryWhere
|
extension EncryptedStringValueQueryWhere
|
||||||
on QueryBuilder<EncryptedStringValue, EncryptedStringValue, QWhereClause> {
|
on QueryBuilder<EncryptedStringValue, EncryptedStringValue, QWhereClause> {
|
||||||
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
|
||||||
idEqualTo(int id) {
|
idEqualTo(Id id) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addWhereClause(IdWhereClause.between(
|
return query.addWhereClause(IdWhereClause.between(
|
||||||
lower: id,
|
lower: id,
|
||||||
|
@ -229,7 +199,7 @@ extension EncryptedStringValueQueryWhere
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
|
||||||
idNotEqualTo(int id) {
|
idNotEqualTo(Id id) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
if (query.whereSort == Sort.asc) {
|
if (query.whereSort == Sort.asc) {
|
||||||
return query
|
return query
|
||||||
|
@ -252,7 +222,7 @@ extension EncryptedStringValueQueryWhere
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
|
||||||
idGreaterThan(int id, {bool include = false}) {
|
idGreaterThan(Id id, {bool include = false}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addWhereClause(
|
return query.addWhereClause(
|
||||||
IdWhereClause.greaterThan(lower: id, includeLower: include),
|
IdWhereClause.greaterThan(lower: id, includeLower: include),
|
||||||
|
@ -261,7 +231,7 @@ extension EncryptedStringValueQueryWhere
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
|
||||||
idLessThan(int id, {bool include = false}) {
|
idLessThan(Id id, {bool include = false}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addWhereClause(
|
return query.addWhereClause(
|
||||||
IdWhereClause.lessThan(upper: id, includeUpper: include),
|
IdWhereClause.lessThan(upper: id, includeUpper: include),
|
||||||
|
@ -271,8 +241,8 @@ extension EncryptedStringValueQueryWhere
|
||||||
|
|
||||||
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue, QAfterWhereClause>
|
||||||
idBetween(
|
idBetween(
|
||||||
int lowerId,
|
Id lowerId,
|
||||||
int upperId, {
|
Id upperId, {
|
||||||
bool includeLower = true,
|
bool includeLower = true,
|
||||||
bool includeUpper = true,
|
bool includeUpper = true,
|
||||||
}) {
|
}) {
|
||||||
|
@ -335,7 +305,7 @@ extension EncryptedStringValueQueryWhere
|
||||||
extension EncryptedStringValueQueryFilter on QueryBuilder<EncryptedStringValue,
|
extension EncryptedStringValueQueryFilter on QueryBuilder<EncryptedStringValue,
|
||||||
EncryptedStringValue, QFilterCondition> {
|
EncryptedStringValue, QFilterCondition> {
|
||||||
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
||||||
QAfterFilterCondition> idEqualTo(int value) {
|
QAfterFilterCondition> idEqualTo(Id value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.equalTo(
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
property: r'id',
|
property: r'id',
|
||||||
|
@ -346,7 +316,7 @@ extension EncryptedStringValueQueryFilter on QueryBuilder<EncryptedStringValue,
|
||||||
|
|
||||||
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
||||||
QAfterFilterCondition> idGreaterThan(
|
QAfterFilterCondition> idGreaterThan(
|
||||||
int value, {
|
Id value, {
|
||||||
bool include = false,
|
bool include = false,
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
@ -360,7 +330,7 @@ extension EncryptedStringValueQueryFilter on QueryBuilder<EncryptedStringValue,
|
||||||
|
|
||||||
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
||||||
QAfterFilterCondition> idLessThan(
|
QAfterFilterCondition> idLessThan(
|
||||||
int value, {
|
Id value, {
|
||||||
bool include = false,
|
bool include = false,
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
@ -374,8 +344,8 @@ extension EncryptedStringValueQueryFilter on QueryBuilder<EncryptedStringValue,
|
||||||
|
|
||||||
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
||||||
QAfterFilterCondition> idBetween(
|
QAfterFilterCondition> idBetween(
|
||||||
int lower,
|
Id lower,
|
||||||
int upper, {
|
Id upper, {
|
||||||
bool includeLower = true,
|
bool includeLower = true,
|
||||||
bool includeUpper = true,
|
bool includeUpper = true,
|
||||||
}) {
|
}) {
|
||||||
|
@ -508,6 +478,26 @@ extension EncryptedStringValueQueryFilter on QueryBuilder<EncryptedStringValue,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
||||||
|
QAfterFilterCondition> keyIsEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'key',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
||||||
|
QAfterFilterCondition> keyIsNotEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
property: r'key',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
||||||
QAfterFilterCondition> valueEqualTo(
|
QAfterFilterCondition> valueEqualTo(
|
||||||
String value, {
|
String value, {
|
||||||
|
@ -625,6 +615,26 @@ extension EncryptedStringValueQueryFilter on QueryBuilder<EncryptedStringValue,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
||||||
|
QAfterFilterCondition> valueIsEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'value',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<EncryptedStringValue, EncryptedStringValue,
|
||||||
|
QAfterFilterCondition> valueIsNotEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
property: r'value',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EncryptedStringValueQueryObject on QueryBuilder<EncryptedStringValue,
|
extension EncryptedStringValueQueryObject on QueryBuilder<EncryptedStringValue,
|
||||||
|
|
7
lib/models/isar/models/isar_models.dart
Normal file
7
lib/models/isar/models/isar_models.dart
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export 'address/address.dart';
|
||||||
|
export 'blockchain_data/input.dart';
|
||||||
|
export 'blockchain_data/output.dart';
|
||||||
|
export 'blockchain_data/transaction.dart';
|
||||||
|
export 'blockchain_data/utxo.dart';
|
||||||
|
export 'log.dart';
|
||||||
|
export 'transaction_note.dart';
|
|
@ -13,6 +13,7 @@ class Log {
|
||||||
@Index()
|
@Index()
|
||||||
late int timestampInMillisUTC;
|
late int timestampInMillisUTC;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.name)
|
||||||
late LogLevel logLevel;
|
late LogLevel logLevel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -7,7 +7,7 @@ part of 'log.dart';
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, join_return_with_assignment, avoid_js_rounded_ints, prefer_final_locals
|
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
|
||||||
|
|
||||||
extension GetLogCollection on Isar {
|
extension GetLogCollection on Isar {
|
||||||
IsarCollection<Log> get logs => this.collection();
|
IsarCollection<Log> get logs => this.collection();
|
||||||
|
@ -21,6 +21,7 @@ const LogSchema = CollectionSchema(
|
||||||
id: 0,
|
id: 0,
|
||||||
name: r'logLevel',
|
name: r'logLevel',
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
|
enumMap: _LoglogLevelEnumValueMap,
|
||||||
),
|
),
|
||||||
r'message': PropertySchema(
|
r'message': PropertySchema(
|
||||||
id: 1,
|
id: 1,
|
||||||
|
@ -34,12 +35,9 @@ const LogSchema = CollectionSchema(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
estimateSize: _logEstimateSize,
|
estimateSize: _logEstimateSize,
|
||||||
serializeNative: _logSerializeNative,
|
serialize: _logSerialize,
|
||||||
deserializeNative: _logDeserializeNative,
|
deserialize: _logDeserialize,
|
||||||
deserializePropNative: _logDeserializePropNative,
|
deserializeProp: _logDeserializeProp,
|
||||||
serializeWeb: _logSerializeWeb,
|
|
||||||
deserializeWeb: _logDeserializeWeb,
|
|
||||||
deserializePropWeb: _logDeserializePropWeb,
|
|
||||||
idName: r'id',
|
idName: r'id',
|
||||||
indexes: {
|
indexes: {
|
||||||
r'timestampInMillisUTC': IndexSchema(
|
r'timestampInMillisUTC': IndexSchema(
|
||||||
|
@ -61,7 +59,7 @@ const LogSchema = CollectionSchema(
|
||||||
getId: _logGetId,
|
getId: _logGetId,
|
||||||
getLinks: _logGetLinks,
|
getLinks: _logGetLinks,
|
||||||
attach: _logAttach,
|
attach: _logAttach,
|
||||||
version: 5,
|
version: '3.0.5',
|
||||||
);
|
);
|
||||||
|
|
||||||
int _logEstimateSize(
|
int _logEstimateSize(
|
||||||
|
@ -70,49 +68,48 @@ int _logEstimateSize(
|
||||||
Map<Type, List<int>> allOffsets,
|
Map<Type, List<int>> allOffsets,
|
||||||
) {
|
) {
|
||||||
var bytesCount = offsets.last;
|
var bytesCount = offsets.last;
|
||||||
bytesCount += 3 + object.logLevel.value.length * 3;
|
bytesCount += 3 + object.logLevel.name.length * 3;
|
||||||
bytesCount += 3 + object.message.length * 3;
|
bytesCount += 3 + object.message.length * 3;
|
||||||
return bytesCount;
|
return bytesCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _logSerializeNative(
|
void _logSerialize(
|
||||||
Log object,
|
Log object,
|
||||||
IsarBinaryWriter writer,
|
IsarWriter writer,
|
||||||
List<int> offsets,
|
List<int> offsets,
|
||||||
Map<Type, List<int>> allOffsets,
|
Map<Type, List<int>> allOffsets,
|
||||||
) {
|
) {
|
||||||
writer.writeString(offsets[0], object.logLevel.value);
|
writer.writeString(offsets[0], object.logLevel.name);
|
||||||
writer.writeString(offsets[1], object.message);
|
writer.writeString(offsets[1], object.message);
|
||||||
writer.writeLong(offsets[2], object.timestampInMillisUTC);
|
writer.writeLong(offsets[2], object.timestampInMillisUTC);
|
||||||
return writer.usedBytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log _logDeserializeNative(
|
Log _logDeserialize(
|
||||||
int id,
|
Id id,
|
||||||
IsarBinaryReader reader,
|
IsarReader reader,
|
||||||
List<int> offsets,
|
List<int> offsets,
|
||||||
Map<Type, List<int>> allOffsets,
|
Map<Type, List<int>> allOffsets,
|
||||||
) {
|
) {
|
||||||
final object = Log();
|
final object = Log();
|
||||||
object.id = id;
|
object.id = id;
|
||||||
object.logLevel =
|
object.logLevel =
|
||||||
_LogLogLevelMap[reader.readStringOrNull(offsets[0])] ?? LogLevel.Info;
|
_LoglogLevelValueEnumMap[reader.readStringOrNull(offsets[0])] ??
|
||||||
|
LogLevel.Info;
|
||||||
object.message = reader.readString(offsets[1]);
|
object.message = reader.readString(offsets[1]);
|
||||||
object.timestampInMillisUTC = reader.readLong(offsets[2]);
|
object.timestampInMillisUTC = reader.readLong(offsets[2]);
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
P _logDeserializePropNative<P>(
|
P _logDeserializeProp<P>(
|
||||||
Id id,
|
IsarReader reader,
|
||||||
IsarBinaryReader reader,
|
|
||||||
int propertyId,
|
int propertyId,
|
||||||
int offset,
|
int offset,
|
||||||
Map<Type, List<int>> allOffsets,
|
Map<Type, List<int>> allOffsets,
|
||||||
) {
|
) {
|
||||||
switch (propertyId) {
|
switch (propertyId) {
|
||||||
case 0:
|
case 0:
|
||||||
return (_LogLogLevelMap[reader.readStringOrNull(offset)] ?? LogLevel.Info)
|
return (_LoglogLevelValueEnumMap[reader.readStringOrNull(offset)] ??
|
||||||
as P;
|
LogLevel.Info) as P;
|
||||||
case 1:
|
case 1:
|
||||||
return (reader.readString(offset)) as P;
|
return (reader.readString(offset)) as P;
|
||||||
case 2:
|
case 2:
|
||||||
|
@ -122,36 +119,21 @@ P _logDeserializePropNative<P>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object _logSerializeWeb(IsarCollection<Log> collection, Log object) {
|
const _LoglogLevelEnumValueMap = {
|
||||||
/*final jsObj = IsarNative.newJsObject();*/ throw UnimplementedError();
|
r'Info': r'Info',
|
||||||
}
|
r'Warning': r'Warning',
|
||||||
|
r'Error': r'Error',
|
||||||
Log _logDeserializeWeb(IsarCollection<Log> collection, Object jsObj) {
|
r'Fatal': r'Fatal',
|
||||||
/*final object = Log();object.id = IsarNative.jsObjectGet(jsObj, r'id') ?? (double.negativeInfinity as int);object.logLevel = IsarNative.jsObjectGet(jsObj, r'logLevel') ?? LogLevel.Info;object.message = IsarNative.jsObjectGet(jsObj, r'message') ?? '';object.timestampInMillisUTC = IsarNative.jsObjectGet(jsObj, r'timestampInMillisUTC') ?? (double.negativeInfinity as int);*/
|
};
|
||||||
//return object;
|
const _LoglogLevelValueEnumMap = {
|
||||||
throw UnimplementedError();
|
r'Info': LogLevel.Info,
|
||||||
}
|
r'Warning': LogLevel.Warning,
|
||||||
|
r'Error': LogLevel.Error,
|
||||||
P _logDeserializePropWeb<P>(Object jsObj, String propertyName) {
|
r'Fatal': LogLevel.Fatal,
|
||||||
switch (propertyName) {
|
|
||||||
default:
|
|
||||||
throw IsarError('Illegal propertyName');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final _LogLogLevelMap = {
|
|
||||||
LogLevel.Info.value: LogLevel.Info,
|
|
||||||
LogLevel.Warning.value: LogLevel.Warning,
|
|
||||||
LogLevel.Error.value: LogLevel.Error,
|
|
||||||
LogLevel.Fatal.value: LogLevel.Fatal,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int? _logGetId(Log object) {
|
Id _logGetId(Log object) {
|
||||||
if (object.id == Isar.autoIncrement) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return object.id;
|
return object.id;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<IsarLinkBase<dynamic>> _logGetLinks(Log object) {
|
List<IsarLinkBase<dynamic>> _logGetLinks(Log object) {
|
||||||
|
@ -179,7 +161,7 @@ extension LogQueryWhereSort on QueryBuilder<Log, Log, QWhere> {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LogQueryWhere on QueryBuilder<Log, Log, QWhereClause> {
|
extension LogQueryWhere on QueryBuilder<Log, Log, QWhereClause> {
|
||||||
QueryBuilder<Log, Log, QAfterWhereClause> idEqualTo(int id) {
|
QueryBuilder<Log, Log, QAfterWhereClause> idEqualTo(Id id) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addWhereClause(IdWhereClause.between(
|
return query.addWhereClause(IdWhereClause.between(
|
||||||
lower: id,
|
lower: id,
|
||||||
|
@ -188,7 +170,7 @@ extension LogQueryWhere on QueryBuilder<Log, Log, QWhereClause> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Log, Log, QAfterWhereClause> idNotEqualTo(int id) {
|
QueryBuilder<Log, Log, QAfterWhereClause> idNotEqualTo(Id id) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
if (query.whereSort == Sort.asc) {
|
if (query.whereSort == Sort.asc) {
|
||||||
return query
|
return query
|
||||||
|
@ -210,7 +192,7 @@ extension LogQueryWhere on QueryBuilder<Log, Log, QWhereClause> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Log, Log, QAfterWhereClause> idGreaterThan(int id,
|
QueryBuilder<Log, Log, QAfterWhereClause> idGreaterThan(Id id,
|
||||||
{bool include = false}) {
|
{bool include = false}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addWhereClause(
|
return query.addWhereClause(
|
||||||
|
@ -219,7 +201,7 @@ extension LogQueryWhere on QueryBuilder<Log, Log, QWhereClause> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Log, Log, QAfterWhereClause> idLessThan(int id,
|
QueryBuilder<Log, Log, QAfterWhereClause> idLessThan(Id id,
|
||||||
{bool include = false}) {
|
{bool include = false}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addWhereClause(
|
return query.addWhereClause(
|
||||||
|
@ -229,8 +211,8 @@ extension LogQueryWhere on QueryBuilder<Log, Log, QWhereClause> {
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Log, Log, QAfterWhereClause> idBetween(
|
QueryBuilder<Log, Log, QAfterWhereClause> idBetween(
|
||||||
int lowerId,
|
Id lowerId,
|
||||||
int upperId, {
|
Id upperId, {
|
||||||
bool includeLower = true,
|
bool includeLower = true,
|
||||||
bool includeUpper = true,
|
bool includeUpper = true,
|
||||||
}) {
|
}) {
|
||||||
|
@ -336,7 +318,7 @@ extension LogQueryWhere on QueryBuilder<Log, Log, QWhereClause> {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LogQueryFilter on QueryBuilder<Log, Log, QFilterCondition> {
|
extension LogQueryFilter on QueryBuilder<Log, Log, QFilterCondition> {
|
||||||
QueryBuilder<Log, Log, QAfterFilterCondition> idEqualTo(int value) {
|
QueryBuilder<Log, Log, QAfterFilterCondition> idEqualTo(Id value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
return query.addFilterCondition(FilterCondition.equalTo(
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
property: r'id',
|
property: r'id',
|
||||||
|
@ -346,7 +328,7 @@ extension LogQueryFilter on QueryBuilder<Log, Log, QFilterCondition> {
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Log, Log, QAfterFilterCondition> idGreaterThan(
|
QueryBuilder<Log, Log, QAfterFilterCondition> idGreaterThan(
|
||||||
int value, {
|
Id value, {
|
||||||
bool include = false,
|
bool include = false,
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
@ -359,7 +341,7 @@ extension LogQueryFilter on QueryBuilder<Log, Log, QFilterCondition> {
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Log, Log, QAfterFilterCondition> idLessThan(
|
QueryBuilder<Log, Log, QAfterFilterCondition> idLessThan(
|
||||||
int value, {
|
Id value, {
|
||||||
bool include = false,
|
bool include = false,
|
||||||
}) {
|
}) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
@ -372,8 +354,8 @@ extension LogQueryFilter on QueryBuilder<Log, Log, QFilterCondition> {
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryBuilder<Log, Log, QAfterFilterCondition> idBetween(
|
QueryBuilder<Log, Log, QAfterFilterCondition> idBetween(
|
||||||
int lower,
|
Id lower,
|
||||||
int upper, {
|
Id upper, {
|
||||||
bool includeLower = true,
|
bool includeLower = true,
|
||||||
bool includeUpper = true,
|
bool includeUpper = true,
|
||||||
}) {
|
}) {
|
||||||
|
@ -498,6 +480,24 @@ extension LogQueryFilter on QueryBuilder<Log, Log, QFilterCondition> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Log, Log, QAfterFilterCondition> logLevelIsEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'logLevel',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Log, Log, QAfterFilterCondition> logLevelIsNotEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
property: r'logLevel',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Log, Log, QAfterFilterCondition> messageEqualTo(
|
QueryBuilder<Log, Log, QAfterFilterCondition> messageEqualTo(
|
||||||
String value, {
|
String value, {
|
||||||
bool caseSensitive = true,
|
bool caseSensitive = true,
|
||||||
|
@ -608,6 +608,24 @@ extension LogQueryFilter on QueryBuilder<Log, Log, QFilterCondition> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Log, Log, QAfterFilterCondition> messageIsEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.equalTo(
|
||||||
|
property: r'message',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryBuilder<Log, Log, QAfterFilterCondition> messageIsNotEmpty() {
|
||||||
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||||
|
property: r'message',
|
||||||
|
value: '',
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QueryBuilder<Log, Log, QAfterFilterCondition> timestampInMillisUTCEqualTo(
|
QueryBuilder<Log, Log, QAfterFilterCondition> timestampInMillisUTCEqualTo(
|
||||||
int value) {
|
int value) {
|
||||||
return QueryBuilder.apply(this, (query) {
|
return QueryBuilder.apply(this, (query) {
|
||||||
|
|
22
lib/models/isar/models/transaction_note.dart
Normal file
22
lib/models/isar/models/transaction_note.dart
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
|
part 'transaction_note.g.dart';
|
||||||
|
|
||||||
|
@Collection()
|
||||||
|
class TransactionNote {
|
||||||
|
TransactionNote({
|
||||||
|
required this.walletId,
|
||||||
|
required this.txid,
|
||||||
|
required this.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
Id id = Isar.autoIncrement;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
late String walletId;
|
||||||
|
|
||||||
|
@Index(unique: true, composite: [CompositeIndex("walletId")])
|
||||||
|
late String txid;
|
||||||
|
|
||||||
|
late String value;
|
||||||
|
}
|
1073
lib/models/isar/models/transaction_note.g.dart
Normal file
1073
lib/models/isar/models/transaction_note.g.dart
Normal file
File diff suppressed because it is too large
Load diff
35
lib/models/paynym/created_paynym.dart
Normal file
35
lib/models/paynym/created_paynym.dart
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
class CreatedPaynym {
|
||||||
|
final bool claimed;
|
||||||
|
final String? nymAvatar;
|
||||||
|
final String? nymId;
|
||||||
|
final String? nymName;
|
||||||
|
final String? token;
|
||||||
|
|
||||||
|
CreatedPaynym(
|
||||||
|
this.claimed,
|
||||||
|
this.nymAvatar,
|
||||||
|
this.nymId,
|
||||||
|
this.nymName,
|
||||||
|
this.token,
|
||||||
|
);
|
||||||
|
|
||||||
|
CreatedPaynym.fromMap(Map<String, dynamic> map)
|
||||||
|
: claimed = map["claimed"] as bool,
|
||||||
|
nymAvatar = map["nymAvatar"] as String?,
|
||||||
|
nymId = map["nymID"] as String?,
|
||||||
|
nymName = map["nymName"] as String?,
|
||||||
|
token = map["token"] as String?;
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"claimed": claimed,
|
||||||
|
"nymAvatar": nymAvatar,
|
||||||
|
"nymId": nymId,
|
||||||
|
"nymName": nymName,
|
||||||
|
"token": token,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return toMap().toString();
|
||||||
|
}
|
||||||
|
}
|
67
lib/models/paynym/paynym_account.dart
Normal file
67
lib/models/paynym/paynym_account.dart
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||||
|
import 'package:stackwallet/models/paynym/paynym_code.dart';
|
||||||
|
|
||||||
|
class PaynymAccount {
|
||||||
|
final String nymID;
|
||||||
|
final String nymName;
|
||||||
|
|
||||||
|
final List<PaynymCode> codes;
|
||||||
|
|
||||||
|
/// list of nymId
|
||||||
|
final List<PaynymAccountLite> followers;
|
||||||
|
|
||||||
|
/// list of nymId
|
||||||
|
final List<PaynymAccountLite> following;
|
||||||
|
|
||||||
|
PaynymAccount(
|
||||||
|
this.nymID,
|
||||||
|
this.nymName,
|
||||||
|
this.codes,
|
||||||
|
this.followers,
|
||||||
|
this.following,
|
||||||
|
);
|
||||||
|
|
||||||
|
PaynymAccount.fromMap(Map<String, dynamic> map)
|
||||||
|
: nymID = map["nymID"] as String,
|
||||||
|
nymName = map["nymName"] as String,
|
||||||
|
codes = (map["codes"] as List<dynamic>)
|
||||||
|
.map((e) => PaynymCode.fromMap(Map<String, dynamic>.from(e as Map)))
|
||||||
|
.toList(),
|
||||||
|
followers = (map["followers"] as List<dynamic>)
|
||||||
|
.map((e) =>
|
||||||
|
PaynymAccountLite.fromMap(Map<String, dynamic>.from(e as Map)))
|
||||||
|
.toList(),
|
||||||
|
following = (map["following"] as List<dynamic>)
|
||||||
|
.map((e) =>
|
||||||
|
PaynymAccountLite.fromMap(Map<String, dynamic>.from(e as Map)))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
PaynymAccount copyWith({
|
||||||
|
String? nymID,
|
||||||
|
String? nymName,
|
||||||
|
List<PaynymCode>? codes,
|
||||||
|
List<PaynymAccountLite>? followers,
|
||||||
|
List<PaynymAccountLite>? following,
|
||||||
|
}) {
|
||||||
|
return PaynymAccount(
|
||||||
|
nymID ?? this.nymID,
|
||||||
|
nymName ?? this.nymName,
|
||||||
|
codes ?? this.codes,
|
||||||
|
followers ?? this.followers,
|
||||||
|
following ?? this.following,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"nymID": nymID,
|
||||||
|
"nymName": nymName,
|
||||||
|
"codes": codes.map((e) => e.toMap()),
|
||||||
|
"followers": followers.map((e) => e.toMap()),
|
||||||
|
"following": followers.map((e) => e.toMap()),
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return toMap().toString();
|
||||||
|
}
|
||||||
|
}
|
31
lib/models/paynym/paynym_account_lite.dart
Normal file
31
lib/models/paynym/paynym_account_lite.dart
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
class PaynymAccountLite {
|
||||||
|
final String nymId;
|
||||||
|
final String nymName;
|
||||||
|
final String code;
|
||||||
|
final bool segwit;
|
||||||
|
|
||||||
|
PaynymAccountLite(
|
||||||
|
this.nymId,
|
||||||
|
this.nymName,
|
||||||
|
this.code,
|
||||||
|
this.segwit,
|
||||||
|
);
|
||||||
|
|
||||||
|
PaynymAccountLite.fromMap(Map<String, dynamic> map)
|
||||||
|
: nymId = map["nymId"] as String,
|
||||||
|
nymName = map["nymName"] as String,
|
||||||
|
code = map["code"] as String,
|
||||||
|
segwit = map["segwit"] as bool;
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"nymId": nymId,
|
||||||
|
"nymName": nymName,
|
||||||
|
"code": code,
|
||||||
|
"segwit": segwit,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return toMap().toString();
|
||||||
|
}
|
||||||
|
}
|
20
lib/models/paynym/paynym_claim.dart
Normal file
20
lib/models/paynym/paynym_claim.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
class PaynymClaim {
|
||||||
|
final String claimed;
|
||||||
|
final String token;
|
||||||
|
|
||||||
|
PaynymClaim(this.claimed, this.token);
|
||||||
|
|
||||||
|
PaynymClaim.fromMap(Map<String, dynamic> map)
|
||||||
|
: claimed = map["claimed"] as String,
|
||||||
|
token = map["token"] as String;
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"claimed": claimed,
|
||||||
|
"token": token,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return toMap().toString();
|
||||||
|
}
|
||||||
|
}
|
27
lib/models/paynym/paynym_code.dart
Normal file
27
lib/models/paynym/paynym_code.dart
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
class PaynymCode {
|
||||||
|
final bool claimed;
|
||||||
|
final bool segwit;
|
||||||
|
final String code;
|
||||||
|
|
||||||
|
PaynymCode(
|
||||||
|
this.claimed,
|
||||||
|
this.segwit,
|
||||||
|
this.code,
|
||||||
|
);
|
||||||
|
|
||||||
|
PaynymCode.fromMap(Map<String, dynamic> map)
|
||||||
|
: claimed = map["claimed"] as bool,
|
||||||
|
segwit = map["segwit"] as bool,
|
||||||
|
code = map["code"] as String;
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"claimed": claimed,
|
||||||
|
"segwit": segwit,
|
||||||
|
"code": code,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return toMap().toString();
|
||||||
|
}
|
||||||
|
}
|
23
lib/models/paynym/paynym_follow.dart
Normal file
23
lib/models/paynym/paynym_follow.dart
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
class PaynymFollow {
|
||||||
|
final String follower;
|
||||||
|
final String following;
|
||||||
|
final String token;
|
||||||
|
|
||||||
|
PaynymFollow(this.follower, this.following, this.token);
|
||||||
|
|
||||||
|
PaynymFollow.fromMap(Map<String, dynamic> map)
|
||||||
|
: follower = map["follower"] as String,
|
||||||
|
following = map["following"] as String,
|
||||||
|
token = map["token"] as String;
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"follower": follower,
|
||||||
|
"following": following,
|
||||||
|
"token": token,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return toMap().toString();
|
||||||
|
}
|
||||||
|
}
|
12
lib/models/paynym/paynym_response.dart
Normal file
12
lib/models/paynym/paynym_response.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
class PaynymResponse<T> {
|
||||||
|
final T? value;
|
||||||
|
final int statusCode;
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
PaynymResponse(this.value, this.statusCode, this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "PaynymResponse: value=$value, statusCode=$statusCode, message=$message";
|
||||||
|
}
|
||||||
|
}
|
23
lib/models/paynym/paynym_unfollow.dart
Normal file
23
lib/models/paynym/paynym_unfollow.dart
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
class PaynymUnfollow {
|
||||||
|
final String follower;
|
||||||
|
final String unfollowing;
|
||||||
|
final String token;
|
||||||
|
|
||||||
|
PaynymUnfollow(this.follower, this.unfollowing, this.token);
|
||||||
|
|
||||||
|
PaynymUnfollow.fromMap(Map<String, dynamic> map)
|
||||||
|
: follower = map["follower"] as String,
|
||||||
|
unfollowing = map["unfollowing"] as String,
|
||||||
|
token = map["token"] as String;
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"follower": follower,
|
||||||
|
"unfollowing": unfollowing,
|
||||||
|
"token": token,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return toMap().toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
|
@ -160,8 +161,9 @@ class _VerifyRecoveryPhraseViewState
|
||||||
|
|
||||||
result.insert(random.nextInt(wordsToShow), chosenWord);
|
result.insert(random.nextInt(wordsToShow), chosenWord);
|
||||||
|
|
||||||
//todo: this prints sensitive info
|
if (kDebugMode) {
|
||||||
debugPrint("Mnemonic game correct word: $chosenWord");
|
print("Mnemonic game correct word: $chosenWord");
|
||||||
|
}
|
||||||
|
|
||||||
return Tuple2(result, chosenWord);
|
return Tuple2(result, chosenWord);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:stackwallet/models/contact.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
import 'package:stackwallet/pages/address_book_views/subviews/add_new_contact_address_view.dart';
|
import 'package:stackwallet/pages/address_book_views/subviews/add_new_contact_address_view.dart';
|
||||||
import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_address_view.dart';
|
import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_address_view.dart';
|
||||||
|
@ -15,7 +15,6 @@ import 'package:stackwallet/services/coins/manager.dart';
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
import 'package:stackwallet/widgets/background.dart';
|
||||||
|
@ -28,6 +27,8 @@ import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/transaction_card.dart';
|
import 'package:stackwallet/widgets/transaction_card.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
import '../../../db/main_db.dart';
|
||||||
|
|
||||||
class ContactDetailsView extends ConsumerStatefulWidget {
|
class ContactDetailsView extends ConsumerStatefulWidget {
|
||||||
const ContactDetailsView({
|
const ContactDetailsView({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -50,15 +51,6 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> {
|
||||||
|
|
||||||
List<Tuple2<String, Transaction>> _cachedTransactions = [];
|
List<Tuple2<String, Transaction>> _cachedTransactions = [];
|
||||||
|
|
||||||
bool _contactHasAddress(String address, Contact contact) {
|
|
||||||
for (final entry in contact.addresses) {
|
|
||||||
if (entry.address == address) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<Tuple2<String, Transaction>>> _filteredTransactionsByContact(
|
Future<List<Tuple2<String, Transaction>>> _filteredTransactionsByContact(
|
||||||
List<Manager> managers,
|
List<Manager> managers,
|
||||||
) async {
|
) async {
|
||||||
|
@ -69,18 +61,18 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> {
|
||||||
|
|
||||||
List<Tuple2<String, Transaction>> result = [];
|
List<Tuple2<String, Transaction>> result = [];
|
||||||
for (final manager in managers) {
|
for (final manager in managers) {
|
||||||
final transactions = (await manager.transactionData)
|
final transactions = await MainDB.instance
|
||||||
.getAllTransactions()
|
.getTransactions(manager.walletId)
|
||||||
.values
|
.filter()
|
||||||
.toList()
|
.anyOf(contact.addresses.map((e) => e.address),
|
||||||
.where((e) => _contactHasAddress(e.address, contact));
|
(q, String e) => q.address((q) => q.valueEqualTo(e)))
|
||||||
|
.sortByTimestampDesc()
|
||||||
|
.findAll();
|
||||||
|
|
||||||
for (final tx in transactions) {
|
for (final tx in transactions) {
|
||||||
result.add(Tuple2(manager.walletId, tx));
|
result.add(Tuple2(manager.walletId, tx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// sort by date
|
|
||||||
result.sort((a, b) => b.item2.timestamp - a.item2.timestamp);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
import 'package:stackwallet/pages/exchange_view/exchange_form.dart';
|
import 'package:stackwallet/pages/exchange_view/exchange_form.dart';
|
||||||
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
||||||
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
||||||
|
@ -12,6 +14,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
import 'package:stackwallet/widgets/trade_card.dart';
|
import 'package:stackwallet/widgets/trade_card.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
import '../../db/main_db.dart';
|
||||||
|
|
||||||
class ExchangeView extends ConsumerStatefulWidget {
|
class ExchangeView extends ConsumerStatefulWidget {
|
||||||
const ExchangeView({Key? key}) : super(key: key);
|
const ExchangeView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@ -129,10 +133,11 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
|
||||||
//todo: check if print needed
|
//todo: check if print needed
|
||||||
// debugPrint("name: ${manager.walletName}");
|
// debugPrint("name: ${manager.walletName}");
|
||||||
|
|
||||||
// TODO store tx data completely locally in isar so we don't lock up ui here when querying txData
|
final tx = await MainDB.instance
|
||||||
final txData = await manager.transactionData;
|
.getTransactions(walletIds.first)
|
||||||
|
.filter()
|
||||||
final tx = txData.getAllTransactions()[txid];
|
.txidEqualTo(txid)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
unawaited(Navigator.of(context).pushNamed(
|
unawaited(Navigator.of(context).pushNamed(
|
||||||
|
|
|
@ -438,8 +438,10 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
|
||||||
style: STextStyles.itemSubtitle(context),
|
style: STextStyles.itemSubtitle(context),
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: (manager.wallet as FiroWallet)
|
// TODO redo this widget now that its not actually a future
|
||||||
.availablePrivateBalance(),
|
future: Future(() =>
|
||||||
|
(manager.wallet as FiroWallet)
|
||||||
|
.availablePrivateBalance()),
|
||||||
builder: (builderContext,
|
builder: (builderContext,
|
||||||
AsyncSnapshot<Decimal> snapshot) {
|
AsyncSnapshot<Decimal> snapshot) {
|
||||||
if (snapshot.connectionState ==
|
if (snapshot.connectionState ==
|
||||||
|
@ -524,8 +526,10 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
|
||||||
style: STextStyles.itemSubtitle(context),
|
style: STextStyles.itemSubtitle(context),
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: (manager.wallet as FiroWallet)
|
// TODO redo this widget now that its not actually a future
|
||||||
.availablePublicBalance(),
|
future: Future(() =>
|
||||||
|
(manager.wallet as FiroWallet)
|
||||||
|
.availablePublicBalance()),
|
||||||
builder: (builderContext,
|
builder: (builderContext,
|
||||||
AsyncSnapshot<Decimal> snapshot) {
|
AsyncSnapshot<Decimal> snapshot) {
|
||||||
if (snapshot.connectionState ==
|
if (snapshot.connectionState ==
|
||||||
|
@ -634,7 +638,8 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
|
||||||
),
|
),
|
||||||
if (!isFiro)
|
if (!isFiro)
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: manager.totalBalance,
|
// TODO redo this widget now that its not actually a future
|
||||||
|
future: Future(() => manager.balance.getTotal()),
|
||||||
builder:
|
builder:
|
||||||
(builderContext, AsyncSnapshot<Decimal> snapshot) {
|
(builderContext, AsyncSnapshot<Decimal> snapshot) {
|
||||||
if (snapshot.connectionState ==
|
if (snapshot.connectionState ==
|
||||||
|
|
|
@ -7,7 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart';
|
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart';
|
||||||
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart';
|
import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart';
|
||||||
import 'package:stackwallet/pages/exchange_view/send_from_view.dart';
|
import 'package:stackwallet/pages/exchange_view/send_from_view.dart';
|
||||||
|
@ -23,7 +23,6 @@ import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
|
|
||||||
import 'package:stackwallet/utilities/format.dart';
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
|
|
458
lib/pages/paynym/add_new_paynym_follow_view.dart
Normal file
458
lib/pages/paynym/add_new_paynym_follow_view.dart
Normal file
|
@ -0,0 +1,458 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/models/paynym/paynym_account.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/featured_paynyms_widget.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/paynym_card.dart';
|
||||||
|
import 'package:stackwallet/providers/global/paynym_api_provider.dart';
|
||||||
|
import 'package:stackwallet/utilities/barcode_scanner_interface.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/conditional_parent.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/paynym_search_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/secondary_button.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/loading_indicator.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||||
|
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||||
|
|
||||||
|
class AddNewPaynymFollowView extends ConsumerStatefulWidget {
|
||||||
|
const AddNewPaynymFollowView({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
|
||||||
|
static const String routeName = "/addNewPaynymFollow";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<AddNewPaynymFollowView> createState() =>
|
||||||
|
_AddNewPaynymFollowViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AddNewPaynymFollowViewState
|
||||||
|
extends ConsumerState<AddNewPaynymFollowView> {
|
||||||
|
late final TextEditingController _searchController;
|
||||||
|
late final FocusNode searchFieldFocusNode;
|
||||||
|
|
||||||
|
String _searchString = "";
|
||||||
|
|
||||||
|
bool _didSearch = false;
|
||||||
|
PaynymAccount? _searchResult;
|
||||||
|
|
||||||
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
|
Future<void> _search() async {
|
||||||
|
_didSearch = true;
|
||||||
|
bool didPopLoading = false;
|
||||||
|
unawaited(
|
||||||
|
showDialog<void>(
|
||||||
|
barrierDismissible: false,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const LoadingIndicator(
|
||||||
|
width: 200,
|
||||||
|
),
|
||||||
|
).then((_) => didPopLoading = true),
|
||||||
|
);
|
||||||
|
|
||||||
|
final paynymAccount = await ref.read(paynymAPIProvider).nym(_searchString);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
if (!didPopLoading) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_searchResult = paynymAccount.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _clear() async {
|
||||||
|
_searchString = "";
|
||||||
|
setState(() {
|
||||||
|
_searchController.text = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _paste() async {
|
||||||
|
final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
|
||||||
|
if (data?.text != null && data!.text!.isNotEmpty) {
|
||||||
|
String content = data.text!.trim();
|
||||||
|
if (content.contains("\n")) {
|
||||||
|
content = content.substring(
|
||||||
|
0,
|
||||||
|
content.indexOf(
|
||||||
|
"\n",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_searchString = content;
|
||||||
|
setState(() {
|
||||||
|
_searchController.text = content;
|
||||||
|
_searchController.selection = TextSelection.collapsed(
|
||||||
|
offset: content.length,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _scanQr() async {
|
||||||
|
try {
|
||||||
|
if (!isDesktop && FocusScope.of(context).hasFocus) {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||||
|
}
|
||||||
|
|
||||||
|
final qrResult = await const BarcodeScannerWrapper().scan();
|
||||||
|
|
||||||
|
final pCodeString = qrResult.rawContent;
|
||||||
|
|
||||||
|
_searchString = pCodeString;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_searchController.text = pCodeString;
|
||||||
|
_searchController.selection = TextSelection.collapsed(
|
||||||
|
offset: pCodeString.length,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
// scan failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_searchController = TextEditingController();
|
||||||
|
searchFieldFocusNode = FocusNode();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_searchController.dispose();
|
||||||
|
searchFieldFocusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
debugPrint("BUILD: $runtimeType");
|
||||||
|
|
||||||
|
return ConditionalParent(
|
||||||
|
condition: !isDesktop,
|
||||||
|
builder: (child) => MasterScaffold(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: AppBarBackButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
titleSpacing: 0,
|
||||||
|
title: Text(
|
||||||
|
"New follow",
|
||||||
|
style: STextStyles.navBarTitle(context),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) => SingleChildScrollView(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minHeight: constraints.maxHeight,
|
||||||
|
),
|
||||||
|
child: IntrinsicHeight(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ConditionalParent(
|
||||||
|
condition: isDesktop,
|
||||||
|
builder: (child) => DesktopDialog(
|
||||||
|
maxWidth: 580,
|
||||||
|
maxHeight: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 32),
|
||||||
|
child: Text(
|
||||||
|
"New follow",
|
||||||
|
style: STextStyles.desktopH3(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const DesktopDialogCloseButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 32,
|
||||||
|
right: 32,
|
||||||
|
bottom: 32,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Featured PayNyms",
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
: STextStyles.sectionLabelMedium12(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
FeaturedPaynymsWidget(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Add new",
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
: STextStyles.sectionLabelMedium12(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
RoundedContainer(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldDefaultBG,
|
||||||
|
height: 56,
|
||||||
|
child: Center(
|
||||||
|
child: TextField(
|
||||||
|
autocorrect: !isDesktop,
|
||||||
|
enableSuggestions: !isDesktop,
|
||||||
|
controller: _searchController,
|
||||||
|
focusNode: searchFieldFocusNode,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_searchString = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
style: STextStyles.desktopTextExtraExtraSmall(
|
||||||
|
context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldActiveText,
|
||||||
|
// height: 1.8,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Paste payment code",
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
contentPadding: const EdgeInsets.all(16),
|
||||||
|
hintStyle:
|
||||||
|
STextStyles.desktopTextFieldLabel(context)
|
||||||
|
.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
errorBorder: InputBorder.none,
|
||||||
|
disabledBorder: InputBorder.none,
|
||||||
|
focusedErrorBorder: InputBorder.none,
|
||||||
|
suffixIcon: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8),
|
||||||
|
child: UnconstrainedBox(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_searchController.text.isNotEmpty
|
||||||
|
? TextFieldIconButton(
|
||||||
|
onTap: _clear,
|
||||||
|
child: RoundedContainer(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.all(8),
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonBackSecondary,
|
||||||
|
child: const XIcon(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: TextFieldIconButton(
|
||||||
|
key: const Key(
|
||||||
|
"paynymPasteAddressFieldButtonKey"),
|
||||||
|
onTap: _paste,
|
||||||
|
child: RoundedContainer(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.all(8),
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonBackSecondary,
|
||||||
|
child: const ClipboardIcon(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextFieldIconButton(
|
||||||
|
key: const Key(
|
||||||
|
"paynymScanQrButtonKey"),
|
||||||
|
onTap: _scanQr,
|
||||||
|
child: RoundedContainer(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonBackSecondary,
|
||||||
|
child: const QrCodeIcon(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
PaynymSearchButton(onPressed: _search),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!isDesktop)
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
autocorrect: !isDesktop,
|
||||||
|
enableSuggestions: !isDesktop,
|
||||||
|
controller: _searchController,
|
||||||
|
focusNode: searchFieldFocusNode,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_searchString = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
style: STextStyles.field(context),
|
||||||
|
decoration: standardInputDecoration(
|
||||||
|
"Paste payment code",
|
||||||
|
searchFieldFocusNode,
|
||||||
|
context,
|
||||||
|
desktopMed: isDesktop,
|
||||||
|
).copyWith(
|
||||||
|
suffixIcon: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8),
|
||||||
|
child: UnconstrainedBox(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_searchController.text.isNotEmpty
|
||||||
|
? TextFieldIconButton(
|
||||||
|
onTap: _clear,
|
||||||
|
child: const XIcon(),
|
||||||
|
)
|
||||||
|
: TextFieldIconButton(
|
||||||
|
key: const Key(
|
||||||
|
"paynymPasteAddressFieldButtonKey"),
|
||||||
|
onTap: _paste,
|
||||||
|
child: const ClipboardIcon(),
|
||||||
|
),
|
||||||
|
TextFieldIconButton(
|
||||||
|
key: const Key("paynymScanQrButtonKey"),
|
||||||
|
onTap: _scanQr,
|
||||||
|
child: const QrCodeIcon(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!isDesktop)
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
if (!isDesktop)
|
||||||
|
SecondaryButton(
|
||||||
|
label: "Search",
|
||||||
|
onPressed: _search,
|
||||||
|
),
|
||||||
|
if (_didSearch)
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
if (_didSearch && _searchResult == null)
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
borderColor: isDesktop
|
||||||
|
? Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.backgroundAppBar
|
||||||
|
: null,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Nothing found. Please check the payment code.",
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
: STextStyles.label(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_didSearch && _searchResult != null)
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
borderColor: isDesktop
|
||||||
|
? Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.backgroundAppBar
|
||||||
|
: null,
|
||||||
|
child: PaynymCard(
|
||||||
|
label: _searchResult!.nymName,
|
||||||
|
paymentCodeString: _searchResult!.codes.first.code,
|
||||||
|
walletId: widget.walletId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
138
lib/pages/paynym/dialogs/claiming_paynym_dialog.dart
Normal file
138
lib/pages/paynym/dialogs/claiming_paynym_dialog.dart
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.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/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/stack_dialog.dart';
|
||||||
|
|
||||||
|
class ClaimingPaynymDialog extends StatefulWidget {
|
||||||
|
const ClaimingPaynymDialog({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ClaimingPaynymDialog> createState() => _RestoringDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RestoringDialogState extends State<ClaimingPaynymDialog>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
late AnimationController? _spinController;
|
||||||
|
late Animation<double> _spinAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_spinController = AnimationController(
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
vsync: this,
|
||||||
|
)..repeat();
|
||||||
|
|
||||||
|
_spinAnimation = CurvedAnimation(
|
||||||
|
parent: _spinController!,
|
||||||
|
curve: Curves.linear,
|
||||||
|
);
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_spinController?.dispose();
|
||||||
|
_spinController = null;
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (Util.isDesktop) {
|
||||||
|
return DesktopDialog(
|
||||||
|
maxWidth: 580,
|
||||||
|
maxHeight: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
DesktopDialogCloseButton(
|
||||||
|
onPressedOverride: () => Navigator.of(context).pop(true),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
RotationTransition(
|
||||||
|
turns: _spinAnimation,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
Assets.svg.arrowRotate,
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.accentColorDark,
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(40),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Claiming PayNym",
|
||||||
|
style: STextStyles.desktopH2(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"We are generating your PayNym",
|
||||||
|
style: STextStyles.desktopTextMedium(context).copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
SecondaryButton(
|
||||||
|
label: "Cancel",
|
||||||
|
width: 272,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
child: StackDialog(
|
||||||
|
title: "Claiming PayNym",
|
||||||
|
message: "We are generating your PayNym",
|
||||||
|
icon: RotationTransition(
|
||||||
|
turns: _spinAnimation,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
Assets.svg.arrowRotate,
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.accentColorDark,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rightButton: SecondaryButton(
|
||||||
|
label: "Cancel",
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
lib/pages/paynym/dialogs/confirm_paynym_connect_dialog.dart
Normal file
138
lib/pages/paynym/dialogs/confirm_paynym_connect_dialog.dart
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.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/desktop/desktop_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
|
||||||
|
class ConfirmPaynymConnectDialog extends StatelessWidget {
|
||||||
|
const ConfirmPaynymConnectDialog({
|
||||||
|
Key? key,
|
||||||
|
required this.nymName,
|
||||||
|
required this.onConfirmPressed,
|
||||||
|
required this.amount,
|
||||||
|
required this.coin,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String nymName;
|
||||||
|
final VoidCallback onConfirmPressed;
|
||||||
|
final int amount;
|
||||||
|
final Coin coin;
|
||||||
|
|
||||||
|
String get title => "Connect to $nymName";
|
||||||
|
|
||||||
|
String get message => "A one-time connection fee of "
|
||||||
|
"${Format.satoshisToAmount(amount, coin: coin)} ${coin.ticker} "
|
||||||
|
"will be charged to connect to this PayNym.\n\nThis fee "
|
||||||
|
"covers the cost of creating a one-time transaction to create a "
|
||||||
|
"record on the blockchain. This keeps PayNyms decentralized.";
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (Util.isDesktop) {
|
||||||
|
return DesktopDialog(
|
||||||
|
maxHeight: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 40),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
Assets.svg.userPlus,
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const DesktopDialogCloseButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 40,
|
||||||
|
bottom: 32,
|
||||||
|
right: 40,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: STextStyles.desktopH3(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 40,
|
||||||
|
right: 40,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
message,
|
||||||
|
style: STextStyles.desktopTextMedium(context).copyWith(
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 40,
|
||||||
|
bottom: 40,
|
||||||
|
right: 40,
|
||||||
|
top: 32,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SecondaryButton(
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
label: "Cancel",
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 16,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: PrimaryButton(
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
label: "Connect",
|
||||||
|
onPressed: onConfirmPressed,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return StackDialog(
|
||||||
|
title: title,
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.userPlus,
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
message: message,
|
||||||
|
leftButton: SecondaryButton(
|
||||||
|
buttonHeight: ButtonHeight.xl,
|
||||||
|
label: "Cancel",
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
),
|
||||||
|
rightButton: PrimaryButton(
|
||||||
|
buttonHeight: ButtonHeight.xl,
|
||||||
|
label: "Connect",
|
||||||
|
onPressed: onConfirmPressed,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
332
lib/pages/paynym/dialogs/paynym_details_popup.dart
Normal file
332
lib/pages/paynym/dialogs/paynym_details_popup.dart
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||||
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/dialogs/confirm_paynym_connect_dialog.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart';
|
||||||
|
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
|
||||||
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
|
import 'package:stackwallet/route_generator.dart';
|
||||||
|
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||||
|
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/paynym_follow_toggle_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||||
|
|
||||||
|
class PaynymDetailsPopup extends ConsumerStatefulWidget {
|
||||||
|
const PaynymDetailsPopup({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
required this.accountLite,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
final PaynymAccountLite accountLite;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<PaynymDetailsPopup> createState() => _PaynymDetailsPopupState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
||||||
|
bool _showInsufficientFundsInfo = false;
|
||||||
|
|
||||||
|
Future<void> _onConnectPressed() async {
|
||||||
|
bool canPop = false;
|
||||||
|
unawaited(
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => WillPopScope(
|
||||||
|
onWillPop: () async => canPop,
|
||||||
|
child: const LoadingIndicator(
|
||||||
|
width: 200,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final wallet = ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(widget.walletId)
|
||||||
|
.wallet as DogecoinWallet;
|
||||||
|
|
||||||
|
// sanity check to prevent second notifcation tx
|
||||||
|
if (wallet.hasConnectedConfirmed(widget.accountLite.code)) {
|
||||||
|
canPop = true;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
// TODO show info popup
|
||||||
|
return;
|
||||||
|
} else if (wallet.hasConnected(widget.accountLite.code)) {
|
||||||
|
canPop = true;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
// TODO show info popup
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final rates = await wallet.fees;
|
||||||
|
|
||||||
|
Map<String, dynamic> preparedTx;
|
||||||
|
|
||||||
|
try {
|
||||||
|
preparedTx = await wallet.buildNotificationTx(
|
||||||
|
selectedTxFeeRate: rates.medium,
|
||||||
|
targetPaymentCodeString: widget.accountLite.code,
|
||||||
|
);
|
||||||
|
} on InsufficientBalanceException catch (_) {
|
||||||
|
if (mounted) {
|
||||||
|
canPop = true;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_showInsufficientFundsInfo = true;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
// We have enough balance and prepared tx should be good to go.
|
||||||
|
|
||||||
|
canPop = true;
|
||||||
|
// close loading
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
// Close details
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
// show info pop up
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ConfirmPaynymConnectDialog(
|
||||||
|
nymName: widget.accountLite.nymName,
|
||||||
|
onConfirmPressed: () {
|
||||||
|
//
|
||||||
|
print("CONFIRM NOTIF TX: $preparedTx");
|
||||||
|
|
||||||
|
Navigator.of(context).push(
|
||||||
|
RouteGenerator.getRoute(
|
||||||
|
builder: (_) => ConfirmTransactionView(
|
||||||
|
walletId: wallet.walletId,
|
||||||
|
routeOnSuccessName: PaynymHomeView.routeName,
|
||||||
|
isPaynymNotificationTransaction: true,
|
||||||
|
transactionInfo: {
|
||||||
|
"hex": preparedTx["hex"],
|
||||||
|
"address": preparedTx["recipientPaynym"],
|
||||||
|
"recipientAmt": preparedTx["amount"],
|
||||||
|
"fee": preparedTx["fee"],
|
||||||
|
"vSize": preparedTx["vSize"],
|
||||||
|
"note": "PayNym connect"
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
amount: (preparedTx["amount"] as int) + (preparedTx["fee"] as int),
|
||||||
|
coin: wallet.coin,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DesktopDialog(
|
||||||
|
maxWidth: MediaQuery.of(context).size.width - 32,
|
||||||
|
maxHeight: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 24,
|
||||||
|
top: 24,
|
||||||
|
right: 24,
|
||||||
|
bottom: 16,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
PayNymBot(
|
||||||
|
paymentCodeString: widget.accountLite.code,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.accountLite.nymName,
|
||||||
|
style: STextStyles.w600_12(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
PrimaryButton(
|
||||||
|
label: "Connect",
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.circlePlusFilled,
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonTextPrimary,
|
||||||
|
),
|
||||||
|
iconSpacing: 4,
|
||||||
|
width: 86,
|
||||||
|
onPressed: _onConnectPressed,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (_showInsufficientFundsInfo)
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
RoundedContainer(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.warningBackground,
|
||||||
|
child: Text(
|
||||||
|
"Adding a PayNym to your contacts requires a one-time "
|
||||||
|
"transaction fee for creating the record on the "
|
||||||
|
"blockchain. Please deposit more "
|
||||||
|
"${ref.read(walletsChangeNotifierProvider).getManager(widget.walletId).wallet.coin.ticker} "
|
||||||
|
"into your wallet and try again.",
|
||||||
|
style: STextStyles.infoSmall(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.warningForeground,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.backgroundAppBar,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 24,
|
||||||
|
top: 16,
|
||||||
|
right: 24,
|
||||||
|
bottom: 16,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(minHeight: 86),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"PayNym address",
|
||||||
|
style: STextStyles.infoSmall(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.accountLite.code,
|
||||||
|
style: STextStyles.infoSmall(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
),
|
||||||
|
QrImage(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
size: 86,
|
||||||
|
data: widget.accountLite.code,
|
||||||
|
foregroundColor:
|
||||||
|
Theme.of(context).extension<StackColors>()!.textDark,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 24,
|
||||||
|
right: 24,
|
||||||
|
bottom: 24,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: PaynymFollowToggleButton(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
paymentCodeStringToFollow: widget.accountLite.code,
|
||||||
|
style: PaynymFollowToggleButtonStyle.detailsPopup,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SecondaryButton(
|
||||||
|
label: "Copy",
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.copy,
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonTextSecondary,
|
||||||
|
),
|
||||||
|
iconSpacing: 4,
|
||||||
|
onPressed: () async {
|
||||||
|
await Clipboard.setData(
|
||||||
|
ClipboardData(
|
||||||
|
text: widget.accountLite.code,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
unawaited(
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.info,
|
||||||
|
message: "Copied to clipboard",
|
||||||
|
iconAsset: Assets.svg.copy,
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
161
lib/pages/paynym/dialogs/paynym_qr_popup.dart
Normal file
161
lib/pages/paynym/dialogs/paynym_qr_popup.dart
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:stackwallet/models/paynym/paynym_account.dart';
|
||||||
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.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/custom_buttons/blue_text_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||||
|
|
||||||
|
class PaynymQrPopup extends StatelessWidget {
|
||||||
|
const PaynymQrPopup({
|
||||||
|
Key? key,
|
||||||
|
required this.paynymAccount,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final PaynymAccount paynymAccount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
|
return DesktopDialog(
|
||||||
|
maxWidth: isDesktop ? 580 : MediaQuery.of(context).size.width - 32,
|
||||||
|
maxHeight: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (isDesktop)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 32),
|
||||||
|
child: Text(
|
||||||
|
"Address details",
|
||||||
|
style: STextStyles.desktopH3(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const DesktopDialogCloseButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: isDesktop ? 32 : 24,
|
||||||
|
top: isDesktop ? 16 : 24,
|
||||||
|
right: 24,
|
||||||
|
bottom: 16,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
PayNymBot(
|
||||||
|
paymentCodeString: paynymAccount.codes.first.code,
|
||||||
|
size: isDesktop ? 56 : 32,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
paynymAccount.nymName,
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.w500_24(context)
|
||||||
|
: STextStyles.w600_12(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!isDesktop)
|
||||||
|
Container(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.backgroundAppBar,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 24,
|
||||||
|
top: 16,
|
||||||
|
right: 24,
|
||||||
|
bottom: 24,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(minHeight: 107),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
isDesktop ? "PayNym address" : "Your PayNym address",
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.desktopTextSmall(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textSubtitle1,
|
||||||
|
)
|
||||||
|
: STextStyles.infoSmall(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
paynymAccount.codes.first.code,
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.desktopTextSmall(context)
|
||||||
|
: STextStyles.infoSmall(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
BlueTextButton(
|
||||||
|
text: "Copy",
|
||||||
|
textSize: isDesktop ? 18 : 10,
|
||||||
|
onTap: () async {
|
||||||
|
await Clipboard.setData(
|
||||||
|
ClipboardData(
|
||||||
|
text: paynymAccount.codes.first.code,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
unawaited(
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.info,
|
||||||
|
message: "Copied to clipboard",
|
||||||
|
iconAsset: Assets.svg.copy,
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
),
|
||||||
|
QrImage(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
size: 107,
|
||||||
|
data: paynymAccount.codes.first.code,
|
||||||
|
foregroundColor:
|
||||||
|
Theme.of(context).extension<StackColors>()!.textDark,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
258
lib/pages/paynym/paynym_claim_view.dart
Normal file
258
lib/pages/paynym/paynym_claim_view.dart
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
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/paynym/dialogs/claiming_paynym_dialog.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
||||||
|
import 'package:stackwallet/providers/global/paynym_api_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/coins/coin_paynym_extension.dart';
|
||||||
|
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.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/conditional_parent.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
|
|
||||||
|
class PaynymClaimView extends ConsumerStatefulWidget {
|
||||||
|
const PaynymClaimView({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
|
||||||
|
static const String routeName = "/claimPaynym";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<PaynymClaimView> createState() => _PaynymClaimViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaynymClaimViewState extends ConsumerState<PaynymClaimView> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
debugPrint("BUILD: $runtimeType");
|
||||||
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
|
return MasterScaffold(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
appBar: isDesktop
|
||||||
|
? DesktopAppBar(
|
||||||
|
isCompactHeight: true,
|
||||||
|
background: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||||
|
leading: Row(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 24,
|
||||||
|
right: 20,
|
||||||
|
),
|
||||||
|
child: AppBarIconButton(
|
||||||
|
size: 32,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldDefaultBG,
|
||||||
|
shadows: const [],
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.arrowLeft,
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.topNavIconPrimary,
|
||||||
|
),
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SvgPicture.asset(
|
||||||
|
Assets.svg.user,
|
||||||
|
width: 42,
|
||||||
|
height: 42,
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"PayNym",
|
||||||
|
style: STextStyles.desktopH3(context),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: AppBar(
|
||||||
|
leading: const AppBarBackButton(),
|
||||||
|
titleSpacing: 0,
|
||||||
|
title: Text(
|
||||||
|
"PayNym",
|
||||||
|
style: STextStyles.navBarTitle(context),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: ConditionalParent(
|
||||||
|
condition: !isDesktop,
|
||||||
|
builder: (child) => SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ConditionalParent(
|
||||||
|
condition: isDesktop,
|
||||||
|
builder: (child) => SizedBox(
|
||||||
|
width: 328,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Spacer(
|
||||||
|
flex: 1,
|
||||||
|
),
|
||||||
|
Image(
|
||||||
|
image: AssetImage(
|
||||||
|
Assets.png.stack,
|
||||||
|
),
|
||||||
|
width: MediaQuery.of(context).size.width / 2,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"You do not have a PayNym yet.\nClaim yours now!",
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.desktopSubtitleH2(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textSubtitle1,
|
||||||
|
)
|
||||||
|
: STextStyles.baseXS(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textSubtitle1,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
const SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
if (!isDesktop)
|
||||||
|
const Spacer(
|
||||||
|
flex: 2,
|
||||||
|
),
|
||||||
|
PrimaryButton(
|
||||||
|
label: "Claim",
|
||||||
|
onPressed: () async {
|
||||||
|
bool shouldCancel = false;
|
||||||
|
unawaited(
|
||||||
|
showDialog<bool?>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => const ClaimingPaynymDialog(),
|
||||||
|
).then((value) => shouldCancel = value == true),
|
||||||
|
);
|
||||||
|
|
||||||
|
// get wallet to access paynym calls
|
||||||
|
final wallet = ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(widget.walletId)
|
||||||
|
.wallet as DogecoinWallet;
|
||||||
|
|
||||||
|
if (shouldCancel) return;
|
||||||
|
|
||||||
|
// get payment code
|
||||||
|
final pCode = await wallet.getPaymentCode();
|
||||||
|
|
||||||
|
if (shouldCancel) return;
|
||||||
|
|
||||||
|
// attempt to create new entry in paynym.is db
|
||||||
|
final created = await ref
|
||||||
|
.read(paynymAPIProvider)
|
||||||
|
.create(pCode.toString());
|
||||||
|
|
||||||
|
debugPrint("created:$created");
|
||||||
|
if (shouldCancel) return;
|
||||||
|
|
||||||
|
if (created.value!.claimed) {
|
||||||
|
// payment code already claimed
|
||||||
|
debugPrint("pcode already claimed!!");
|
||||||
|
if (mounted) {
|
||||||
|
if (isDesktop) {
|
||||||
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).popUntil(
|
||||||
|
ModalRoute.withName(
|
||||||
|
WalletView.routeName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldCancel) return;
|
||||||
|
|
||||||
|
final token =
|
||||||
|
await ref.read(paynymAPIProvider).token(pCode.toString());
|
||||||
|
|
||||||
|
if (shouldCancel) return;
|
||||||
|
|
||||||
|
// sign token with notification private key
|
||||||
|
final signature =
|
||||||
|
await wallet.signStringWithNotificationKey(token.value!);
|
||||||
|
|
||||||
|
if (shouldCancel) return;
|
||||||
|
|
||||||
|
// claim paynym account
|
||||||
|
final claim = await ref
|
||||||
|
.read(paynymAPIProvider)
|
||||||
|
.claim(token.value!, signature);
|
||||||
|
|
||||||
|
if (shouldCancel) return;
|
||||||
|
|
||||||
|
if (claim.value?.claimed == pCode.toString()) {
|
||||||
|
final account =
|
||||||
|
await ref.read(paynymAPIProvider).nym(pCode.toString());
|
||||||
|
|
||||||
|
ref.read(myPaynymAccountStateProvider.state).state =
|
||||||
|
account.value!;
|
||||||
|
if (mounted) {
|
||||||
|
if (isDesktop) {
|
||||||
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).popUntil(
|
||||||
|
ModalRoute.withName(
|
||||||
|
WalletView.routeName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await Navigator.of(context).pushNamed(
|
||||||
|
PaynymHomeView.routeName,
|
||||||
|
arguments: widget.walletId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (mounted && !shouldCancel) {
|
||||||
|
Navigator.of(context, rootNavigator: isDesktop).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
const Spacer(
|
||||||
|
flex: 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
636
lib/pages/paynym/paynym_home_view.dart
Normal file
636
lib/pages/paynym/paynym_home_view.dart
Normal file
|
@ -0,0 +1,636 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/dialogs/paynym_qr_popup.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/desktop_paynym_details.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/paynym_followers_list.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/paynym_following_list.dart';
|
||||||
|
import 'package:stackwallet/providers/ui/selected_paynym_details_item_Provider.dart';
|
||||||
|
import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/utilities/constants.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/conditional_parent.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/icon_widgets/copy_icon.dart';
|
||||||
|
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
||||||
|
import 'package:stackwallet/widgets/icon_widgets/share_icon.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
import 'package:stackwallet/widgets/toggle.dart';
|
||||||
|
|
||||||
|
class PaynymHomeView extends ConsumerStatefulWidget {
|
||||||
|
const PaynymHomeView({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
|
||||||
|
static const String routeName = "/paynymHome";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<PaynymHomeView> createState() => _PaynymHomeViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
||||||
|
bool showFollowers = false;
|
||||||
|
int secretCount = 0;
|
||||||
|
Timer? timer;
|
||||||
|
|
||||||
|
bool _followButtonHoverState = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
timer?.cancel();
|
||||||
|
timer = null;
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
debugPrint("BUILD: $runtimeType");
|
||||||
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
|
return MasterScaffold(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
appBar: isDesktop
|
||||||
|
? DesktopAppBar(
|
||||||
|
isCompactHeight: true,
|
||||||
|
background: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||||
|
leading: Row(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 24,
|
||||||
|
right: 20,
|
||||||
|
),
|
||||||
|
child: AppBarIconButton(
|
||||||
|
size: 32,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldDefaultBG,
|
||||||
|
shadows: const [],
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.arrowLeft,
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.topNavIconPrimary,
|
||||||
|
),
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SvgPicture.asset(
|
||||||
|
Assets.svg.user,
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"PayNym",
|
||||||
|
style: STextStyles.desktopH3(context),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 12),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 56,
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
onEnter: (_) => setState(() {
|
||||||
|
_followButtonHoverState = true;
|
||||||
|
}),
|
||||||
|
onExit: (_) => setState(() {
|
||||||
|
_followButtonHoverState = false;
|
||||||
|
}),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AddNewPaynymFollowView(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: RoundedContainer(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||||
|
color: _followButtonHoverState
|
||||||
|
? Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.highlight
|
||||||
|
: Colors.transparent,
|
||||||
|
radiusMultiplier: 100,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(
|
||||||
|
Assets.svg.plus,
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Follow",
|
||||||
|
style:
|
||||||
|
STextStyles.desktopButtonSecondaryEnabled(
|
||||||
|
context)
|
||||||
|
.copyWith(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: AppBar(
|
||||||
|
leading: AppBarBackButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
titleSpacing: 0,
|
||||||
|
title: Text(
|
||||||
|
"PayNym",
|
||||||
|
style: STextStyles.navBarTitle(context),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1,
|
||||||
|
child: AppBarIconButton(
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.circlePlusFilled,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
AddNewPaynymFollowView.routeName,
|
||||||
|
arguments: widget.walletId,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1,
|
||||||
|
child: AppBarIconButton(
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.circleQuestion,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
// todo info ?
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: ConditionalParent(
|
||||||
|
condition: !isDesktop,
|
||||||
|
builder: (child) => SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
isDesktop ? CrossAxisAlignment.start : CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (!isDesktop)
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
secretCount++;
|
||||||
|
if (secretCount > 5) {
|
||||||
|
debugPrint(
|
||||||
|
"My Account: ${ref.read(myPaynymAccountStateProvider.state).state}");
|
||||||
|
debugPrint(
|
||||||
|
"My Account: ${ref.read(myPaynymAccountStateProvider.state).state!.following}");
|
||||||
|
secretCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer ??= Timer(
|
||||||
|
const Duration(milliseconds: 1500),
|
||||||
|
() {
|
||||||
|
secretCount = 0;
|
||||||
|
timer = null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: PayNymBot(
|
||||||
|
paymentCodeString: ref
|
||||||
|
.watch(myPaynymAccountStateProvider.state)
|
||||||
|
.state!
|
||||||
|
.codes
|
||||||
|
.first
|
||||||
|
.code,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
ref
|
||||||
|
.watch(myPaynymAccountStateProvider.state)
|
||||||
|
.state!
|
||||||
|
.nymName,
|
||||||
|
style: STextStyles.desktopMenuItemSelected(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
Format.shorten(
|
||||||
|
ref
|
||||||
|
.watch(myPaynymAccountStateProvider.state)
|
||||||
|
.state!
|
||||||
|
.codes
|
||||||
|
.first
|
||||||
|
.code,
|
||||||
|
12,
|
||||||
|
5),
|
||||||
|
style: STextStyles.label(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 11,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SecondaryButton(
|
||||||
|
label: "Copy",
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
iconSpacing: 4,
|
||||||
|
icon: CopyIcon(
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await Clipboard.setData(
|
||||||
|
ClipboardData(
|
||||||
|
text: ref
|
||||||
|
.read(myPaynymAccountStateProvider.state)
|
||||||
|
.state!
|
||||||
|
.codes
|
||||||
|
.first
|
||||||
|
.code,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
unawaited(
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.info,
|
||||||
|
message: "Copied to clipboard",
|
||||||
|
iconAsset: Assets.svg.copy,
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 13,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SecondaryButton(
|
||||||
|
label: "Share",
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
iconSpacing: 4,
|
||||||
|
icon: ShareIcon(
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
// copy to clipboard
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 13,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SecondaryButton(
|
||||||
|
label: "Address",
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
iconSpacing: 4,
|
||||||
|
icon: QrCodeIcon(
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => PaynymQrPopup(
|
||||||
|
paynymAccount: ref
|
||||||
|
.read(myPaynymAccountStateProvider.state)
|
||||||
|
.state!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: RoundedWhiteContainer(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
secretCount++;
|
||||||
|
if (secretCount > 5) {
|
||||||
|
debugPrint(
|
||||||
|
"My Account: ${ref.read(myPaynymAccountStateProvider.state).state}");
|
||||||
|
debugPrint(
|
||||||
|
"My Account: ${ref.read(myPaynymAccountStateProvider.state).state!.following}");
|
||||||
|
secretCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer ??= Timer(
|
||||||
|
const Duration(milliseconds: 1500),
|
||||||
|
() {
|
||||||
|
secretCount = 0;
|
||||||
|
timer = null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: PayNymBot(
|
||||||
|
paymentCodeString: ref
|
||||||
|
.watch(myPaynymAccountStateProvider.state)
|
||||||
|
.state!
|
||||||
|
.codes
|
||||||
|
.first
|
||||||
|
.code,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 16,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
ref
|
||||||
|
.watch(myPaynymAccountStateProvider.state)
|
||||||
|
.state!
|
||||||
|
.nymName,
|
||||||
|
style: STextStyles.desktopH3(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
Format.shorten(
|
||||||
|
ref
|
||||||
|
.watch(myPaynymAccountStateProvider.state)
|
||||||
|
.state!
|
||||||
|
.codes
|
||||||
|
.first
|
||||||
|
.code,
|
||||||
|
12,
|
||||||
|
5),
|
||||||
|
style:
|
||||||
|
STextStyles.desktopTextExtraExtraSmall(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
SecondaryButton(
|
||||||
|
label: "Copy",
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
width: 160,
|
||||||
|
icon: CopyIcon(
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await Clipboard.setData(
|
||||||
|
ClipboardData(
|
||||||
|
text: ref
|
||||||
|
.read(myPaynymAccountStateProvider.state)
|
||||||
|
.state!
|
||||||
|
.codes
|
||||||
|
.first
|
||||||
|
.code,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
unawaited(
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.info,
|
||||||
|
message: "Copied to clipboard",
|
||||||
|
iconAsset: Assets.svg.copy,
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 16,
|
||||||
|
),
|
||||||
|
SecondaryButton(
|
||||||
|
label: "Address",
|
||||||
|
width: 160,
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
icon: QrCodeIcon(
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => PaynymQrPopup(
|
||||||
|
paynymAccount: ref
|
||||||
|
.read(myPaynymAccountStateProvider.state)
|
||||||
|
.state!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!isDesktop)
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
ConditionalParent(
|
||||||
|
condition: isDesktop,
|
||||||
|
builder: (child) => Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 24),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
height: isDesktop ? 56 : 40,
|
||||||
|
width: isDesktop ? 490 : null,
|
||||||
|
child: Toggle(
|
||||||
|
onColor: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||||
|
onText:
|
||||||
|
"Following (${ref.watch(myPaynymAccountStateProvider.state).state?.following.length ?? 0})",
|
||||||
|
offColor: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldDefaultBG,
|
||||||
|
offText:
|
||||||
|
"Followers (${ref.watch(myPaynymAccountStateProvider.state).state?.followers.length ?? 0})",
|
||||||
|
isOn: showFollowers,
|
||||||
|
onValueChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
showFollowers = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: isDesktop ? 20 : 16,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ConditionalParent(
|
||||||
|
condition: isDesktop,
|
||||||
|
builder: (child) => Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 24),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 490,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 24,
|
||||||
|
),
|
||||||
|
if (ref
|
||||||
|
.watch(selectedPaynymDetailsItemProvider.state)
|
||||||
|
.state !=
|
||||||
|
null)
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 600,
|
||||||
|
),
|
||||||
|
child: DesktopPaynymDetails(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
accountLite: ref
|
||||||
|
.watch(selectedPaynymDetailsItemProvider
|
||||||
|
.state)
|
||||||
|
.state!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (ref
|
||||||
|
.watch(selectedPaynymDetailsItemProvider.state)
|
||||||
|
.state !=
|
||||||
|
null)
|
||||||
|
const SizedBox(
|
||||||
|
width: 24,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ConditionalParent(
|
||||||
|
condition: !isDesktop,
|
||||||
|
builder: (child) => Container(
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
child: !showFollowers
|
||||||
|
? PaynymFollowingList(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
)
|
||||||
|
: PaynymFollowersList(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
344
lib/pages/paynym/subwidgets/desktop_paynym_details.dart
Normal file
344
lib/pages/paynym/subwidgets/desktop_paynym_details.dart
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||||
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/dialogs/confirm_paynym_connect_dialog.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart';
|
||||||
|
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
|
||||||
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
|
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||||
|
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/paynym_follow_toggle_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
|
class DesktopPaynymDetails extends ConsumerStatefulWidget {
|
||||||
|
const DesktopPaynymDetails({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
required this.accountLite,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
final PaynymAccountLite accountLite;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<DesktopPaynymDetails> createState() =>
|
||||||
|
_PaynymDetailsPopupState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
|
||||||
|
bool _showInsufficientFundsInfo = false;
|
||||||
|
|
||||||
|
Future<void> _onConnectPressed() async {
|
||||||
|
bool canPop = false;
|
||||||
|
unawaited(
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => WillPopScope(
|
||||||
|
onWillPop: () async => canPop,
|
||||||
|
child: const LoadingIndicator(
|
||||||
|
width: 200,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final wallet = ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(widget.walletId)
|
||||||
|
.wallet as DogecoinWallet;
|
||||||
|
|
||||||
|
// sanity check to prevent second notification tx
|
||||||
|
if (wallet.hasConnectedConfirmed(widget.accountLite.code)) {
|
||||||
|
canPop = true;
|
||||||
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
|
// TODO show info popup
|
||||||
|
return;
|
||||||
|
} else if (wallet.hasConnected(widget.accountLite.code)) {
|
||||||
|
canPop = true;
|
||||||
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
|
// TODO show info popup
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final rates = await wallet.fees;
|
||||||
|
|
||||||
|
Map<String, dynamic> preparedTx;
|
||||||
|
|
||||||
|
try {
|
||||||
|
preparedTx = await wallet.buildNotificationTx(
|
||||||
|
selectedTxFeeRate: rates.medium,
|
||||||
|
targetPaymentCodeString: widget.accountLite.code,
|
||||||
|
);
|
||||||
|
} on InsufficientBalanceException catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
canPop = true;
|
||||||
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_showInsufficientFundsInfo = true;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
// We have enough balance and prepared tx should be good to go.
|
||||||
|
|
||||||
|
canPop = true;
|
||||||
|
// close loading
|
||||||
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
|
|
||||||
|
// show info pop up
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ConfirmPaynymConnectDialog(
|
||||||
|
nymName: widget.accountLite.nymName,
|
||||||
|
onConfirmPressed: () {
|
||||||
|
//
|
||||||
|
print("CONFIRM NOTIF TX: $preparedTx");
|
||||||
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
|
unawaited(
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => DesktopDialog(
|
||||||
|
maxHeight: double.infinity,
|
||||||
|
maxWidth: 580,
|
||||||
|
child: ConfirmTransactionView(
|
||||||
|
walletId: wallet.walletId,
|
||||||
|
isPaynymNotificationTransaction: true,
|
||||||
|
transactionInfo: {
|
||||||
|
"hex": preparedTx["hex"],
|
||||||
|
"address": preparedTx["recipientPaynym"],
|
||||||
|
"recipientAmt": preparedTx["amount"],
|
||||||
|
"fee": preparedTx["fee"],
|
||||||
|
"vSize": preparedTx["vSize"],
|
||||||
|
"note": "PayNym connect"
|
||||||
|
},
|
||||||
|
onSuccessInsteadOfRouteOnSuccess: () {
|
||||||
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
|
unawaited(
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.success,
|
||||||
|
message:
|
||||||
|
"Connection initiated to ${widget.accountLite.nymName}",
|
||||||
|
iconAsset: Assets.svg.copy,
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
amount: (preparedTx["amount"] as int) + (preparedTx["fee"] as int),
|
||||||
|
coin: wallet.coin,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSend() async {
|
||||||
|
print("sned");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final wallet = ref
|
||||||
|
.watch(walletsChangeNotifierProvider)
|
||||||
|
.getManager(widget.walletId)
|
||||||
|
.wallet as DogecoinWallet;
|
||||||
|
return RoundedWhiteContainer(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
PayNymBot(
|
||||||
|
paymentCodeString: widget.accountLite.code,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.accountLite.nymName,
|
||||||
|
style: STextStyles.desktopTextSmall(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (!wallet.hasConnected(widget.accountLite.code))
|
||||||
|
Expanded(
|
||||||
|
child: PrimaryButton(
|
||||||
|
label: "Connect",
|
||||||
|
buttonHeight: ButtonHeight.s,
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.circlePlusFilled,
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonTextPrimary,
|
||||||
|
),
|
||||||
|
iconSpacing: 6,
|
||||||
|
onPressed: _onConnectPressed,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (wallet.hasConnected(widget.accountLite.code))
|
||||||
|
Expanded(
|
||||||
|
child: PrimaryButton(
|
||||||
|
label: "Send",
|
||||||
|
buttonHeight: ButtonHeight.s,
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.circleArrowUpRight,
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonTextPrimary,
|
||||||
|
),
|
||||||
|
iconSpacing: 6,
|
||||||
|
onPressed: _onSend,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: PaynymFollowToggleButton(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
paymentCodeStringToFollow: widget.accountLite.code,
|
||||||
|
style: PaynymFollowToggleButtonStyle.detailsDesktop,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (_showInsufficientFundsInfo)
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
RoundedContainer(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.warningBackground,
|
||||||
|
child: Text(
|
||||||
|
"Adding a PayNym to your contacts requires a one-time "
|
||||||
|
"transaction fee for creating the record on the "
|
||||||
|
"blockchain. Please deposit more "
|
||||||
|
"${ref.read(walletsChangeNotifierProvider).getManager(widget.walletId).wallet.coin.ticker} "
|
||||||
|
"into your wallet and try again.",
|
||||||
|
style: STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.warningForeground,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.backgroundAppBar,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"PayNym address",
|
||||||
|
style: STextStyles.desktopTextExtraExtraSmall(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(minHeight: 100),
|
||||||
|
child: Text(
|
||||||
|
widget.accountLite.code,
|
||||||
|
style: STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
),
|
||||||
|
QrImage(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
size: 100,
|
||||||
|
data: widget.accountLite.code,
|
||||||
|
foregroundColor:
|
||||||
|
Theme.of(context).extension<StackColors>()!.textDark,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
BlueTextButton(
|
||||||
|
text: "Copy",
|
||||||
|
onTap: () async {
|
||||||
|
await Clipboard.setData(
|
||||||
|
ClipboardData(
|
||||||
|
text: widget.accountLite.code,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
unawaited(
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.info,
|
||||||
|
message: "Copied to clipboard",
|
||||||
|
iconAsset: Assets.svg.copy,
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
65
lib/pages/paynym/subwidgets/featured_paynyms_widget.dart
Normal file
65
lib/pages/paynym/subwidgets/featured_paynyms_widget.dart
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/paynym_card.dart';
|
||||||
|
import 'package:stackwallet/utilities/featured_paynyms.dart';
|
||||||
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
|
class FeaturedPaynymsWidget extends StatelessWidget {
|
||||||
|
const FeaturedPaynymsWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final entries = FeaturedPaynyms.featured.entries.toList(growable: false);
|
||||||
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
|
return ConditionalParent(
|
||||||
|
condition: !isDesktop,
|
||||||
|
builder: (child) => RoundedWhiteContainer(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < entries.length; i++)
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
if (i > 0)
|
||||||
|
isDesktop
|
||||||
|
? const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.backgroundAppBar,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
ConditionalParent(
|
||||||
|
condition: isDesktop,
|
||||||
|
builder: (child) => RoundedWhiteContainer(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
borderColor: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.backgroundAppBar,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
child: PaynymCard(
|
||||||
|
walletId: walletId,
|
||||||
|
label: entries[i].key,
|
||||||
|
paymentCodeString: entries[i].value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
33
lib/pages/paynym/subwidgets/paynym_bot.dart
Normal file
33
lib/pages/paynym/subwidgets/paynym_bot.dart
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||||
|
|
||||||
|
class PayNymBot extends StatelessWidget {
|
||||||
|
const PayNymBot({
|
||||||
|
Key? key,
|
||||||
|
required this.paymentCodeString,
|
||||||
|
this.size = 60.0,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String paymentCodeString;
|
||||||
|
final double size;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(size / 2),
|
||||||
|
child: SizedBox(
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
child: Image.network(
|
||||||
|
"https://paynym.is/$paymentCodeString/avatar",
|
||||||
|
loadingBuilder: (context, child, loadingProgress) =>
|
||||||
|
loadingProgress == null
|
||||||
|
? child
|
||||||
|
: const Center(
|
||||||
|
child: LoadingIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
85
lib/pages/paynym/subwidgets/paynym_card.dart
Normal file
85
lib/pages/paynym/subwidgets/paynym_card.dart
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.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/custom_buttons/paynym_follow_toggle_button.dart';
|
||||||
|
|
||||||
|
class PaynymCard extends StatefulWidget {
|
||||||
|
const PaynymCard({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
required this.label,
|
||||||
|
required this.paymentCodeString,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
final String label;
|
||||||
|
final String paymentCodeString;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PaynymCard> createState() => _PaynymCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaynymCardState extends State<PaynymCard> {
|
||||||
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: isDesktop
|
||||||
|
? const EdgeInsets.symmetric(
|
||||||
|
vertical: 16,
|
||||||
|
horizontal: 20,
|
||||||
|
)
|
||||||
|
: const EdgeInsets.all(12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
PayNymBot(
|
||||||
|
size: 32,
|
||||||
|
paymentCodeString: widget.paymentCodeString,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.label,
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldActiveText,
|
||||||
|
)
|
||||||
|
: STextStyles.w500_12(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 2,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
Format.shorten(widget.paymentCodeString, 12, 5),
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
: STextStyles.w500_12(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textSubtitle1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PaynymFollowToggleButton(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
paymentCodeStringToFollow: widget.paymentCodeString,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
124
lib/pages/paynym/subwidgets/paynym_card_button.dart
Normal file
124
lib/pages/paynym/subwidgets/paynym_card_button.dart
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/dialogs/paynym_details_popup.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart';
|
||||||
|
import 'package:stackwallet/providers/ui/selected_paynym_details_item_Provider.dart';
|
||||||
|
import 'package:stackwallet/utilities/constants.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/rounded_container.dart';
|
||||||
|
|
||||||
|
class PaynymCardButton extends ConsumerStatefulWidget {
|
||||||
|
const PaynymCardButton({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
required this.accountLite,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
final PaynymAccountLite accountLite;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<PaynymCardButton> createState() => _PaynymCardButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaynymCardButtonState extends ConsumerState<PaynymCardButton> {
|
||||||
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: RoundedContainer(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
color: isDesktop &&
|
||||||
|
ref
|
||||||
|
.watch(selectedPaynymDetailsItemProvider.state)
|
||||||
|
.state
|
||||||
|
?.nymId ==
|
||||||
|
widget.accountLite.nymId
|
||||||
|
? Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.accentColorDark
|
||||||
|
.withOpacity(0.08)
|
||||||
|
: Colors.transparent,
|
||||||
|
child: RawMaterialButton(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (isDesktop) {
|
||||||
|
ref.read(selectedPaynymDetailsItemProvider.state).state =
|
||||||
|
widget.accountLite;
|
||||||
|
} else {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => PaynymDetailsPopup(
|
||||||
|
accountLite: widget.accountLite,
|
||||||
|
walletId: widget.walletId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: isDesktop
|
||||||
|
? const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 12,
|
||||||
|
)
|
||||||
|
: const EdgeInsets.all(8.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
PayNymBot(
|
||||||
|
size: 32,
|
||||||
|
paymentCodeString: widget.accountLite.code,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.accountLite.nymName,
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldActiveText,
|
||||||
|
)
|
||||||
|
: STextStyles.w500_12(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 2,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
Format.shorten(widget.accountLite.code, 12, 5),
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
: STextStyles.w500_12(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textSubtitle1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
114
lib/pages/paynym/subwidgets/paynym_followers_list.dart
Normal file
114
lib/pages/paynym/subwidgets/paynym_followers_list.dart
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/paynym_card_button.dart';
|
||||||
|
import 'package:stackwallet/providers/wallet/my_paynym_account_state_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/rounded_white_container.dart';
|
||||||
|
|
||||||
|
class PaynymFollowersList extends ConsumerStatefulWidget {
|
||||||
|
const PaynymFollowersList({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<PaynymFollowersList> createState() =>
|
||||||
|
_PaynymFollowersListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaynymFollowersListState extends ConsumerState<PaynymFollowersList> {
|
||||||
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
|
BorderRadius get _borderRadiusFirst {
|
||||||
|
return BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
topRight: Radius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderRadius get _borderRadiusLast {
|
||||||
|
return BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
bottomRight: Radius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final followers =
|
||||||
|
ref.watch(myPaynymAccountStateProvider.state).state?.followers;
|
||||||
|
final count = followers?.length ?? 0;
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
itemCount: max(count, 1),
|
||||||
|
separatorBuilder: (BuildContext context, int index) => Container(
|
||||||
|
height: 1.5,
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
if (count == 0) {
|
||||||
|
return RoundedWhiteContainer(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Your PayNym followers will appear here",
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textSubtitle1,
|
||||||
|
)
|
||||||
|
: STextStyles.label(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (count == 1) {
|
||||||
|
return RoundedWhiteContainer(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
child: PaynymCardButton(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
accountLite: followers![0],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
BorderRadius? borderRadius;
|
||||||
|
if (index == 0) {
|
||||||
|
borderRadius = _borderRadiusFirst;
|
||||||
|
} else if (index == count - 1) {
|
||||||
|
borderRadius = _borderRadiusLast;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
key: Key("paynymCardKey_${followers![index].nymId}"),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||||
|
),
|
||||||
|
child: PaynymCardButton(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
accountLite: followers[index],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
114
lib/pages/paynym/subwidgets/paynym_following_list.dart
Normal file
114
lib/pages/paynym/subwidgets/paynym_following_list.dart
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/subwidgets/paynym_card_button.dart';
|
||||||
|
import 'package:stackwallet/providers/wallet/my_paynym_account_state_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/rounded_white_container.dart';
|
||||||
|
|
||||||
|
class PaynymFollowingList extends ConsumerStatefulWidget {
|
||||||
|
const PaynymFollowingList({
|
||||||
|
Key? key,
|
||||||
|
required this.walletId,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String walletId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<PaynymFollowingList> createState() =>
|
||||||
|
_PaynymFollowingListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaynymFollowingListState extends ConsumerState<PaynymFollowingList> {
|
||||||
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
|
BorderRadius get _borderRadiusFirst {
|
||||||
|
return BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
topRight: Radius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderRadius get _borderRadiusLast {
|
||||||
|
return BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
bottomRight: Radius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final following =
|
||||||
|
ref.watch(myPaynymAccountStateProvider.state).state?.following;
|
||||||
|
final count = following?.length ?? 0;
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
itemCount: max(count, 1),
|
||||||
|
separatorBuilder: (BuildContext context, int index) => Container(
|
||||||
|
height: 1.5,
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
if (count == 0) {
|
||||||
|
return RoundedWhiteContainer(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Your PayNym contacts will appear here",
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textSubtitle1,
|
||||||
|
)
|
||||||
|
: STextStyles.label(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (count == 1) {
|
||||||
|
return RoundedWhiteContainer(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
child: PaynymCardButton(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
accountLite: following![0],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
BorderRadius? borderRadius;
|
||||||
|
if (index == 0) {
|
||||||
|
borderRadius = _borderRadiusFirst;
|
||||||
|
} else if (index == count - 1) {
|
||||||
|
borderRadius = _borderRadiusLast;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
key: Key("paynymCardKey_${following![index].nymId}"),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||||
|
),
|
||||||
|
child: PaynymCardButton(
|
||||||
|
walletId: widget.walletId,
|
||||||
|
accountLite: following[index],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,8 @@ import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub
|
||||||
import 'package:stackwallet/providers/providers.dart';
|
import 'package:stackwallet/providers/providers.dart';
|
||||||
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||||
import 'package:stackwallet/route_generator.dart';
|
import 'package:stackwallet/route_generator.dart';
|
||||||
|
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||||
|
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
@ -41,6 +43,9 @@ class ConfirmTransactionView extends ConsumerStatefulWidget {
|
||||||
required this.walletId,
|
required this.walletId,
|
||||||
this.routeOnSuccessName = WalletView.routeName,
|
this.routeOnSuccessName = WalletView.routeName,
|
||||||
this.isTradeTransaction = false,
|
this.isTradeTransaction = false,
|
||||||
|
this.isPaynymTransaction = false,
|
||||||
|
this.isPaynymNotificationTransaction = false,
|
||||||
|
this.onSuccessInsteadOfRouteOnSuccess,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
static const String routeName = "/confirmTransactionView";
|
static const String routeName = "/confirmTransactionView";
|
||||||
|
@ -49,6 +54,9 @@ class ConfirmTransactionView extends ConsumerStatefulWidget {
|
||||||
final String walletId;
|
final String walletId;
|
||||||
final String routeOnSuccessName;
|
final String routeOnSuccessName;
|
||||||
final bool isTradeTransaction;
|
final bool isTradeTransaction;
|
||||||
|
final bool isPaynymTransaction;
|
||||||
|
final bool isPaynymNotificationTransaction;
|
||||||
|
final VoidCallback? onSuccessInsteadOfRouteOnSuccess;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<ConfirmTransactionView> createState() =>
|
ConsumerState<ConfirmTransactionView> createState() =>
|
||||||
|
@ -83,6 +91,13 @@ class _ConfirmTransactionViewState
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String txid;
|
String txid;
|
||||||
|
if (widget.isPaynymNotificationTransaction) {
|
||||||
|
txid = await (manager.wallet as DogecoinWallet)
|
||||||
|
.confirmNotificationTx(preparedTx: transactionInfo);
|
||||||
|
} else if (widget.isPaynymTransaction) {
|
||||||
|
//
|
||||||
|
throw UnimplementedError("paynym send not implemented yet");
|
||||||
|
} else {
|
||||||
final coin = manager.coin;
|
final coin = manager.coin;
|
||||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||||
|
@ -92,6 +107,7 @@ class _ConfirmTransactionViewState
|
||||||
} else {
|
} else {
|
||||||
txid = await manager.confirmSend(txData: transactionInfo);
|
txid = await manager.confirmSend(txData: transactionInfo);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// save note
|
// save note
|
||||||
await ref
|
await ref
|
||||||
|
@ -102,7 +118,12 @@ class _ConfirmTransactionViewState
|
||||||
|
|
||||||
// pop back to wallet
|
// pop back to wallet
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.of(context).popUntil(ModalRoute.withName(routeOnSuccessName));
|
if (widget.onSuccessInsteadOfRouteOnSuccess == null) {
|
||||||
|
Navigator.of(context)
|
||||||
|
.popUntil(ModalRoute.withName(routeOnSuccessName));
|
||||||
|
} else {
|
||||||
|
widget.onSuccessInsteadOfRouteOnSuccess!.call();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} on BadEpicHttpAddressException catch (_) {
|
} on BadEpicHttpAddressException catch (_) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
|
@ -485,22 +485,22 @@ class _SendViewState extends ConsumerState<SendView> {
|
||||||
coin == Coin.firoTestNet)
|
coin == Coin.firoTestNet)
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
|
// TODO redo this widget now that its not actually a future
|
||||||
future: (coin != Coin.firo &&
|
future: (coin != Coin.firo &&
|
||||||
coin != Coin.firoTestNet)
|
coin != Coin.firoTestNet)
|
||||||
? ref.watch(provider.select(
|
? Future(() => ref.watch(
|
||||||
(value) => value.availableBalance))
|
provider.select((value) =>
|
||||||
: ref
|
value.balance.getSpendable())))
|
||||||
.watch(
|
: ref.watch(publicPrivateBalanceStateProvider.state).state ==
|
||||||
publicPrivateBalanceStateProvider
|
|
||||||
.state)
|
|
||||||
.state ==
|
|
||||||
"Private"
|
"Private"
|
||||||
? (ref.watch(provider).wallet
|
? Future(() => (ref
|
||||||
as FiroWallet)
|
.watch(provider)
|
||||||
.availablePrivateBalance()
|
.wallet as FiroWallet)
|
||||||
: (ref.watch(provider).wallet
|
.availablePrivateBalance())
|
||||||
as FiroWallet)
|
: Future(() => (ref
|
||||||
.availablePublicBalance(),
|
.watch(provider)
|
||||||
|
.wallet as FiroWallet)
|
||||||
|
.availablePublicBalance()),
|
||||||
builder:
|
builder:
|
||||||
(_, AsyncSnapshot<Decimal> snapshot) {
|
(_, AsyncSnapshot<Decimal> snapshot) {
|
||||||
if (snapshot.connectionState ==
|
if (snapshot.connectionState ==
|
||||||
|
@ -1085,9 +1085,10 @@ class _SendViewState extends ConsumerState<SendView> {
|
||||||
.decimalPlacesForCoin(coin));
|
.decimalPlacesForCoin(coin));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cryptoAmountController.text = (await ref
|
cryptoAmountController.text = (ref
|
||||||
.read(provider)
|
.read(provider)
|
||||||
.availableBalance)
|
.balance
|
||||||
|
.getSpendable())
|
||||||
.toStringAsFixed(
|
.toStringAsFixed(
|
||||||
Constants.decimalPlacesForCoin(
|
Constants.decimalPlacesForCoin(
|
||||||
coin));
|
coin));
|
||||||
|
@ -1523,43 +1524,43 @@ class _SendViewState extends ConsumerState<SendView> {
|
||||||
.read(walletsChangeNotifierProvider)
|
.read(walletsChangeNotifierProvider)
|
||||||
.getManager(walletId);
|
.getManager(walletId);
|
||||||
|
|
||||||
// TODO: remove the need for this!!
|
// // TODO: remove the need for this!!
|
||||||
final bool isOwnAddress =
|
// final bool isOwnAddress =
|
||||||
await manager.isOwnAddress(_address!);
|
// await manager.isOwnAddress(_address!);
|
||||||
if (isOwnAddress) {
|
// if (isOwnAddress && coin != Coin.dogecoinTestNet) {
|
||||||
await showDialog<dynamic>(
|
// await showDialog<dynamic>(
|
||||||
context: context,
|
// context: context,
|
||||||
useSafeArea: false,
|
// useSafeArea: false,
|
||||||
barrierDismissible: true,
|
// barrierDismissible: true,
|
||||||
builder: (context) {
|
// builder: (context) {
|
||||||
return StackDialog(
|
// return StackDialog(
|
||||||
title: "Transaction failed",
|
// title: "Transaction failed",
|
||||||
message:
|
// message:
|
||||||
"Sending to self is currently disabled",
|
// "Sending to self is currently disabled",
|
||||||
rightButton: TextButton(
|
// rightButton: TextButton(
|
||||||
style: Theme.of(context)
|
// style: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
// .extension<StackColors>()!
|
||||||
.getSecondaryEnabledButtonColor(
|
// .getSecondaryEnabledButtonColor(
|
||||||
context),
|
// context),
|
||||||
child: Text(
|
// child: Text(
|
||||||
"Ok",
|
// "Ok",
|
||||||
style: STextStyles.button(
|
// style: STextStyles.button(
|
||||||
context)
|
// context)
|
||||||
.copyWith(
|
// .copyWith(
|
||||||
color: Theme.of(context)
|
// color: Theme.of(context)
|
||||||
.extension<
|
// .extension<
|
||||||
StackColors>()!
|
// StackColors>()!
|
||||||
.accentColorDark),
|
// .accentColorDark),
|
||||||
),
|
// ),
|
||||||
onPressed: () {
|
// onPressed: () {
|
||||||
Navigator.of(context).pop();
|
// Navigator.of(context).pop();
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
final amount =
|
final amount =
|
||||||
Format.decimalAmountToSatoshis(
|
Format.decimalAmountToSatoshis(
|
||||||
|
@ -1575,22 +1576,20 @@ class _SendViewState extends ConsumerState<SendView> {
|
||||||
"Private") {
|
"Private") {
|
||||||
availableBalance =
|
availableBalance =
|
||||||
Format.decimalAmountToSatoshis(
|
Format.decimalAmountToSatoshis(
|
||||||
await (manager.wallet
|
(manager.wallet as FiroWallet)
|
||||||
as FiroWallet)
|
|
||||||
.availablePrivateBalance(),
|
.availablePrivateBalance(),
|
||||||
coin);
|
coin);
|
||||||
} else {
|
} else {
|
||||||
availableBalance =
|
availableBalance =
|
||||||
Format.decimalAmountToSatoshis(
|
Format.decimalAmountToSatoshis(
|
||||||
await (manager.wallet
|
(manager.wallet as FiroWallet)
|
||||||
as FiroWallet)
|
|
||||||
.availablePublicBalance(),
|
.availablePublicBalance(),
|
||||||
coin);
|
coin);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
availableBalance =
|
availableBalance =
|
||||||
Format.decimalAmountToSatoshis(
|
Format.decimalAmountToSatoshis(
|
||||||
await manager.availableBalance,
|
manager.balance.getSpendable(),
|
||||||
coin);
|
coin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -154,7 +154,9 @@ class _FiroBalanceSelectionSheetState
|
||||||
width: 2,
|
width: 2,
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: firoWallet.availablePrivateBalance(),
|
// TODO redo this widget now that its not actually a future
|
||||||
|
future: Future(
|
||||||
|
() => firoWallet.availablePrivateBalance()),
|
||||||
builder:
|
builder:
|
||||||
(context, AsyncSnapshot<Decimal> snapshot) {
|
(context, AsyncSnapshot<Decimal> snapshot) {
|
||||||
if (snapshot.connectionState ==
|
if (snapshot.connectionState ==
|
||||||
|
@ -244,7 +246,9 @@ class _FiroBalanceSelectionSheetState
|
||||||
width: 2,
|
width: 2,
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: firoWallet.availablePublicBalance(),
|
// TODO redo this widget now that its not actually a future
|
||||||
|
future: Future(
|
||||||
|
() => firoWallet.availablePublicBalance()),
|
||||||
builder:
|
builder:
|
||||||
(context, AsyncSnapshot<Decimal> snapshot) {
|
(context, AsyncSnapshot<Decimal> snapshot) {
|
||||||
if (snapshot.connectionState ==
|
if (snapshot.connectionState ==
|
||||||
|
|
|
@ -145,7 +145,8 @@ class WalletSyncingOptionsView extends ConsumerWidget {
|
||||||
height: 2,
|
height: 2,
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: manager.totalBalance,
|
future: Future(
|
||||||
|
() => manager.balance.getTotal()),
|
||||||
builder: (builderContext,
|
builder: (builderContext,
|
||||||
AsyncSnapshot<Decimal> snapshot) {
|
AsyncSnapshot<Decimal> snapshot) {
|
||||||
if (snapshot.connectionState ==
|
if (snapshot.connectionState ==
|
||||||
|
|
|
@ -507,7 +507,7 @@ class _WalletNetworkSettingsViewState
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Synchronized",
|
"Synchronized",
|
||||||
style: STextStyles.w600_10(context),
|
style: STextStyles.w600_12(context),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"100%",
|
"100%",
|
||||||
|
@ -581,7 +581,7 @@ class _WalletNetworkSettingsViewState
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
AnimatedText(
|
AnimatedText(
|
||||||
style: STextStyles.w600_10(context),
|
style: STextStyles.w600_12(context),
|
||||||
stringsToLoopThrough: const [
|
stringsToLoopThrough: const [
|
||||||
"Synchronizing",
|
"Synchronizing",
|
||||||
"Synchronizing.",
|
"Synchronizing.",
|
||||||
|
@ -679,7 +679,7 @@ class _WalletNetworkSettingsViewState
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Unable to synchronize",
|
"Unable to synchronize",
|
||||||
style: STextStyles.w600_10(context).copyWith(
|
style: STextStyles.w600_12(context).copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.accentColorRed,
|
.accentColorRed,
|
||||||
|
|
|
@ -2,9 +2,10 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
|
||||||
|
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
|
||||||
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
||||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
import 'package:stackwallet/route_generator.dart';
|
import 'package:stackwallet/route_generator.dart';
|
||||||
|
@ -37,19 +38,10 @@ class TransactionsList extends ConsumerStatefulWidget {
|
||||||
class _TransactionsListState extends ConsumerState<TransactionsList> {
|
class _TransactionsListState extends ConsumerState<TransactionsList> {
|
||||||
//
|
//
|
||||||
bool _hasLoaded = false;
|
bool _hasLoaded = false;
|
||||||
Map<String, Transaction> _transactions = {};
|
List<Transaction> _transactions2 = [];
|
||||||
|
|
||||||
late final ChangeNotifierProvider<Manager> managerProvider;
|
late final ChangeNotifierProvider<Manager> managerProvider;
|
||||||
|
|
||||||
void updateTransactions(TransactionData newData) {
|
|
||||||
_transactions = {};
|
|
||||||
final newTransactions =
|
|
||||||
newData.txChunks.expand((element) => element.transactions);
|
|
||||||
for (final tx in newTransactions) {
|
|
||||||
_transactions[tx.txid] = tx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BorderRadius get _borderRadiusFirst {
|
BorderRadius get _borderRadiusFirst {
|
||||||
return BorderRadius.only(
|
return BorderRadius.only(
|
||||||
topLeft: Radius.circular(
|
topLeft: Radius.circular(
|
||||||
|
@ -73,12 +65,15 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget itemBuilder(
|
Widget itemBuilder(
|
||||||
BuildContext context, Transaction tx, BorderRadius? radius) {
|
BuildContext context,
|
||||||
|
Transaction tx,
|
||||||
|
BorderRadius? radius,
|
||||||
|
) {
|
||||||
final matchingTrades = ref
|
final matchingTrades = ref
|
||||||
.read(tradesServiceProvider)
|
.read(tradesServiceProvider)
|
||||||
.trades
|
.trades
|
||||||
.where((e) => e.payInTxid == tx.txid || e.payOutTxid == tx.txid);
|
.where((e) => e.payInTxid == tx.txid || e.payOutTxid == tx.txid);
|
||||||
if (tx.txType == "Sent" && matchingTrades.isNotEmpty) {
|
if (tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) {
|
||||||
final trade = matchingTrades.first;
|
final trade = matchingTrades.first;
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
@ -90,13 +85,16 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
||||||
children: [
|
children: [
|
||||||
TransactionCard(
|
TransactionCard(
|
||||||
// this may mess with combined firo transactions
|
// this may mess with combined firo transactions
|
||||||
key: Key(tx.toString()), //
|
key: Key(tx.txid + tx.type.name + tx.address.value.toString()), //
|
||||||
transaction: tx,
|
transaction: tx,
|
||||||
walletId: widget.walletId,
|
walletId: widget.walletId,
|
||||||
),
|
),
|
||||||
TradeCard(
|
TradeCard(
|
||||||
// this may mess with combined firo transactions
|
// this may mess with combined firo transactions
|
||||||
key: Key(tx.toString() + trade.uuid), //
|
key: Key(tx.txid +
|
||||||
|
tx.type.name +
|
||||||
|
tx.address.value.toString() +
|
||||||
|
trade.uuid), //
|
||||||
trade: trade,
|
trade: trade,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (Util.isDesktop) {
|
if (Util.isDesktop) {
|
||||||
|
@ -182,7 +180,7 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
||||||
),
|
),
|
||||||
child: TransactionCard(
|
child: TransactionCard(
|
||||||
// this may mess with combined firo transactions
|
// this may mess with combined firo transactions
|
||||||
key: Key(tx.toString()), //
|
key: Key(tx.txid + tx.type.name + tx.address.value.toString()), //
|
||||||
transaction: tx,
|
transaction: tx,
|
||||||
walletId: widget.walletId,
|
walletId: widget.walletId,
|
||||||
),
|
),
|
||||||
|
@ -190,6 +188,13 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateHeightProvider(Manager manager) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
|
ref.read(currentHeightProvider(manager.coin).state).state =
|
||||||
|
manager.currentHeight;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
managerProvider = widget.managerProvider;
|
managerProvider = widget.managerProvider;
|
||||||
|
@ -202,13 +207,16 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
||||||
// .watch(walletsChangeNotifierProvider)
|
// .watch(walletsChangeNotifierProvider)
|
||||||
// .getManagerProvider(widget.walletId);
|
// .getManagerProvider(widget.walletId);
|
||||||
|
|
||||||
|
final manager = ref.watch(walletsChangeNotifierProvider
|
||||||
|
.select((value) => value.getManager(widget.walletId)));
|
||||||
|
|
||||||
|
updateHeightProvider(manager);
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future:
|
future: manager.transactions,
|
||||||
ref.watch(managerProvider.select((value) => value.transactionData)),
|
builder: (fbContext, AsyncSnapshot<List<Transaction>> snapshot) {
|
||||||
builder: (fbContext, AsyncSnapshot<TransactionData> snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done &&
|
if (snapshot.connectionState == ConnectionState.done &&
|
||||||
snapshot.hasData) {
|
snapshot.hasData) {
|
||||||
updateTransactions(snapshot.data!);
|
_transactions2 = snapshot.data!;
|
||||||
_hasLoaded = true;
|
_hasLoaded = true;
|
||||||
}
|
}
|
||||||
if (!_hasLoaded) {
|
if (!_hasLoaded) {
|
||||||
|
@ -227,11 +235,10 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (_transactions.isEmpty) {
|
if (_transactions2.isEmpty) {
|
||||||
return const NoTransActionsFound();
|
return const NoTransActionsFound();
|
||||||
} else {
|
} else {
|
||||||
final list = _transactions.values.toList(growable: false);
|
_transactions2.sort((a, b) => b.timestamp - a.timestamp);
|
||||||
list.sort((a, b) => b.timestamp - a.timestamp);
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
//todo: check if print needed
|
//todo: check if print needed
|
||||||
|
@ -247,12 +254,16 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
||||||
? ListView.separated(
|
? ListView.separated(
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
BorderRadius? radius;
|
BorderRadius? radius;
|
||||||
if (index == list.length - 1) {
|
if (_transactions2.length == 1) {
|
||||||
|
radius = BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
);
|
||||||
|
} else if (index == _transactions2.length - 1) {
|
||||||
radius = _borderRadiusLast;
|
radius = _borderRadiusLast;
|
||||||
} else if (index == 0) {
|
} else if (index == 0) {
|
||||||
radius = _borderRadiusFirst;
|
radius = _borderRadiusFirst;
|
||||||
}
|
}
|
||||||
final tx = list[index];
|
final tx = _transactions2[index];
|
||||||
return itemBuilder(context, tx, radius);
|
return itemBuilder(context, tx, radius);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) {
|
separatorBuilder: (context, index) {
|
||||||
|
@ -264,18 +275,22 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
||||||
.background,
|
.background,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
itemCount: list.length,
|
itemCount: _transactions2.length,
|
||||||
)
|
)
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
itemCount: list.length,
|
itemCount: _transactions2.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
BorderRadius? radius;
|
BorderRadius? radius;
|
||||||
if (index == list.length - 1) {
|
if (_transactions2.length == 1) {
|
||||||
|
radius = BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
);
|
||||||
|
} else if (index == _transactions2.length - 1) {
|
||||||
radius = _borderRadiusLast;
|
radius = _borderRadiusLast;
|
||||||
} else if (index == 0) {
|
} else if (index == 0) {
|
||||||
radius = _borderRadiusFirst;
|
radius = _borderRadiusFirst;
|
||||||
}
|
}
|
||||||
final tx = list[index];
|
final tx = _transactions2[index];
|
||||||
return itemBuilder(context, tx, radius);
|
return itemBuilder(context, tx, radius);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,17 +1,26 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
|
||||||
class TxIcon extends StatelessWidget {
|
class TxIcon extends StatelessWidget {
|
||||||
const TxIcon({Key? key, required this.transaction}) : super(key: key);
|
const TxIcon({
|
||||||
|
Key? key,
|
||||||
|
required this.transaction,
|
||||||
|
required this.currentHeight,
|
||||||
|
required this.coin,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
final Transaction transaction;
|
final Transaction transaction;
|
||||||
|
final int currentHeight;
|
||||||
|
final Coin coin;
|
||||||
|
|
||||||
static const Size size = Size(32, 32);
|
static const Size size = Size(32, 32);
|
||||||
|
|
||||||
String _getAssetName(
|
String _getAssetName(
|
||||||
bool isCancelled, bool isReceived, bool isPending, BuildContext context) {
|
bool isCancelled, bool isReceived, bool isPending, BuildContext context) {
|
||||||
if (!isReceived && transaction.subType == "mint") {
|
if (!isReceived && transaction.subType == TransactionSubType.mint) {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return Assets.svg.anonymizeFailed;
|
return Assets.svg.anonymizeFailed;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +51,7 @@ class TxIcon extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final txIsReceived = transaction.txType == "Received";
|
final txIsReceived = transaction.type == TransactionType.incoming;
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: size.width,
|
width: size.width,
|
||||||
|
@ -52,7 +61,10 @@ class TxIcon extends StatelessWidget {
|
||||||
_getAssetName(
|
_getAssetName(
|
||||||
transaction.isCancelled,
|
transaction.isCancelled,
|
||||||
txIsReceived,
|
txIsReceived,
|
||||||
!transaction.confirmedStatus,
|
!transaction.isConfirmed(
|
||||||
|
currentHeight,
|
||||||
|
coin.requiredConfirmations,
|
||||||
|
),
|
||||||
context,
|
context,
|
||||||
),
|
),
|
||||||
width: size.width,
|
width: size.width,
|
||||||
|
|
|
@ -32,13 +32,14 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
|
||||||
.watch(walletsChangeNotifierProvider
|
.watch(walletsChangeNotifierProvider
|
||||||
.select((value) => value.getManager(walletId)))
|
.select((value) => value.getManager(walletId)))
|
||||||
.wallet as FiroWallet;
|
.wallet as FiroWallet;
|
||||||
totalBalanceFuture = firoWallet.availablePublicBalance();
|
totalBalanceFuture = Future(() => firoWallet.balance.getSpendable());
|
||||||
availableBalanceFuture = firoWallet.availablePrivateBalance();
|
availableBalanceFuture =
|
||||||
|
Future(() => firoWallet.balancePrivate.getSpendable());
|
||||||
} else {
|
} else {
|
||||||
final wallet = ref.watch(walletsChangeNotifierProvider
|
final manager = ref.watch(walletsChangeNotifierProvider
|
||||||
.select((value) => value.getManager(walletId)));
|
.select((value) => value.getManager(walletId)));
|
||||||
totalBalanceFuture = wallet.totalBalance;
|
totalBalanceFuture = Future(() => manager.balance.getTotal());
|
||||||
availableBalanceFuture = wallet.availableBalance;
|
availableBalanceFuture = Future(() => manager.balance.getSpendable());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.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/wallets_provider.dart';
|
||||||
|
import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart';
|
||||||
|
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||||
|
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||||
|
|
||||||
class WalletNavigationBar extends StatelessWidget {
|
class WalletNavigationBar extends StatefulWidget {
|
||||||
const WalletNavigationBar({
|
const WalletNavigationBar({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onReceivePressed,
|
required this.onReceivePressed,
|
||||||
|
@ -13,6 +26,8 @@ class WalletNavigationBar extends StatelessWidget {
|
||||||
required this.onBuyPressed,
|
required this.onBuyPressed,
|
||||||
required this.height,
|
required this.height,
|
||||||
required this.enableExchange,
|
required this.enableExchange,
|
||||||
|
required this.coin,
|
||||||
|
required this.walletId,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final VoidCallback onReceivePressed;
|
final VoidCallback onReceivePressed;
|
||||||
|
@ -21,18 +36,165 @@ class WalletNavigationBar extends StatelessWidget {
|
||||||
final VoidCallback onBuyPressed;
|
final VoidCallback onBuyPressed;
|
||||||
final double height;
|
final double height;
|
||||||
final bool enableExchange;
|
final bool enableExchange;
|
||||||
|
final Coin coin;
|
||||||
|
final String walletId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WalletNavigationBar> createState() => _WalletNavigationBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WalletNavigationBarState extends State<WalletNavigationBar> {
|
||||||
|
double scale = 0;
|
||||||
|
final duration = const Duration(milliseconds: 200);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Column(
|
||||||
height: height,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
// const Spacer(),
|
||||||
|
|
||||||
|
AnimatedScale(
|
||||||
|
scale: scale,
|
||||||
|
duration: duration,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
AnimatedOpacity(
|
||||||
|
opacity: scale,
|
||||||
|
duration: duration,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {},
|
||||||
|
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(
|
||||||
|
"Whirlpool",
|
||||||
|
style: STextStyles.w600_12(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// todo make generic and not doge specific
|
||||||
|
final wallet = (ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(widget.walletId)
|
||||||
|
.wallet as DogecoinWallet);
|
||||||
|
|
||||||
|
final code = await wallet.getPaymentCode();
|
||||||
|
|
||||||
|
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.w600_12(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: widget.height,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).extension<StackColors>()!.bottomNavBack,
|
color: Theme.of(context).extension<StackColors>()!.bottomNavBack,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
Theme.of(context).extension<StackColors>()!.standardBoxShadow
|
Theme.of(context).extension<StackColors>()!.standardBoxShadow
|
||||||
],
|
],
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
height / 2.0,
|
widget.height / 2.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
@ -50,12 +212,12 @@ class WalletNavigationBar extends StatelessWidget {
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
minWidth: 66,
|
minWidth: 66,
|
||||||
),
|
),
|
||||||
onPressed: onReceivePressed,
|
onPressed: widget.onReceivePressed,
|
||||||
splashColor:
|
splashColor:
|
||||||
Theme.of(context).extension<StackColors>()!.highlight,
|
Theme.of(context).extension<StackColors>()!.highlight,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
height / 2.0,
|
widget.height / 2.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -105,12 +267,12 @@ class WalletNavigationBar extends StatelessWidget {
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
minWidth: 66,
|
minWidth: 66,
|
||||||
),
|
),
|
||||||
onPressed: onSendPressed,
|
onPressed: widget.onSendPressed,
|
||||||
splashColor:
|
splashColor:
|
||||||
Theme.of(context).extension<StackColors>()!.highlight,
|
Theme.of(context).extension<StackColors>()!.highlight,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
height / 2.0,
|
widget.height / 2.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -156,17 +318,17 @@ class WalletNavigationBar extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (enableExchange)
|
if (widget.enableExchange)
|
||||||
RawMaterialButton(
|
RawMaterialButton(
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
minWidth: 66,
|
minWidth: 66,
|
||||||
),
|
),
|
||||||
onPressed: onExchangePressed,
|
onPressed: widget.onExchangePressed,
|
||||||
splashColor:
|
splashColor:
|
||||||
Theme.of(context).extension<StackColors>()!.highlight,
|
Theme.of(context).extension<StackColors>()!.highlight,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
height / 2.0,
|
widget.height / 2.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -195,6 +357,58 @@ class WalletNavigationBar extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (widget.coin.hasPaynymSupport)
|
||||||
|
RawMaterialButton(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minWidth: 66,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (scale == 0) {
|
||||||
|
setState(() {
|
||||||
|
scale = 1;
|
||||||
|
});
|
||||||
|
} else if (scale == 1) {
|
||||||
|
setState(() {
|
||||||
|
scale = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splashColor:
|
||||||
|
Theme.of(context).extension<StackColors>()!.highlight,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
widget.height / 2.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 2,
|
||||||
|
),
|
||||||
|
SvgPicture.asset(
|
||||||
|
Assets.svg.bars,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"More",
|
||||||
|
style: STextStyles.buttonSmall(context),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 12,
|
width: 12,
|
||||||
),
|
),
|
||||||
|
@ -234,52 +448,8 @@ class WalletNavigationBar extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
|
||||||
// class BarButton extends StatelessWidget {
|
|
||||||
// const BarButton(
|
|
||||||
// {Key? key, required this.icon, required this.text, this.onPressed})
|
|
||||||
// : super(key: key);
|
|
||||||
//
|
|
||||||
// final Widget icon;
|
|
||||||
// final String text;
|
|
||||||
// final VoidCallback? onPressed;
|
|
||||||
//
|
|
||||||
// @override
|
|
||||||
// Widget build(BuildContext context) {
|
|
||||||
// return Container(
|
|
||||||
// child: MaterialButton(
|
|
||||||
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
|
|
||||||
// padding: const EdgeInsets.all(0),
|
|
||||||
// minWidth: 45,
|
|
||||||
// shape: RoundedRectangleBorder(
|
|
||||||
// borderRadius: BorderRadius.circular(
|
|
||||||
// Constants.size.circularBorderRadius,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
||||||
// onPressed: onPressed,
|
|
||||||
// child: Padding(
|
|
||||||
// padding: const EdgeInsets.all(4.0),
|
|
||||||
// child: Column(
|
|
||||||
// mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
// children: [
|
|
||||||
// icon,
|
|
||||||
// SizedBox(
|
|
||||||
// height: 4,
|
|
||||||
// ),
|
|
||||||
// Text(
|
|
||||||
// text,
|
|
||||||
// style: STextStyles.itemSubtitle12(context).copyWith(
|
|
||||||
// fontSize: 10,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
|
@ -79,14 +79,16 @@ class _WalletSummaryInfoState extends State<WalletSummaryInfo> {
|
||||||
final firoWallet =
|
final firoWallet =
|
||||||
ref.watch(managerProvider.select((value) => value.wallet))
|
ref.watch(managerProvider.select((value) => value.wallet))
|
||||||
as FiroWallet;
|
as FiroWallet;
|
||||||
totalBalanceFuture = firoWallet.availablePublicBalance();
|
totalBalanceFuture =
|
||||||
availableBalanceFuture = firoWallet.availablePrivateBalance();
|
Future(() => firoWallet.balance.getSpendable());
|
||||||
|
availableBalanceFuture =
|
||||||
|
Future(() => firoWallet.balancePrivate.getSpendable());
|
||||||
} else {
|
} else {
|
||||||
totalBalanceFuture = ref.watch(
|
final manager = ref.watch(walletsChangeNotifierProvider
|
||||||
managerProvider.select((value) => value.totalBalance));
|
.select((value) => value.getManager(walletId)));
|
||||||
|
totalBalanceFuture = Future(() => manager.balance.getTotal());
|
||||||
availableBalanceFuture = ref.watch(
|
availableBalanceFuture =
|
||||||
managerProvider.select((value) => value.availableBalance));
|
Future(() => manager.balance.getSpendable());
|
||||||
}
|
}
|
||||||
|
|
||||||
final locale = ref.watch(localeServiceChangeNotifierProvider
|
final locale = ref.watch(localeServiceChangeNotifierProvider
|
||||||
|
|
|
@ -4,12 +4,13 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:stackwallet/models/contact.dart';
|
import 'package:stackwallet/models/contact.dart';
|
||||||
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
import 'package:stackwallet/models/transaction_filter.dart';
|
import 'package:stackwallet/models/transaction_filter.dart';
|
||||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
|
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
|
||||||
|
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
|
||||||
import 'package:stackwallet/providers/global/address_book_service_provider.dart';
|
import 'package:stackwallet/providers/global/address_book_service_provider.dart';
|
||||||
import 'package:stackwallet/providers/providers.dart';
|
import 'package:stackwallet/providers/providers.dart';
|
||||||
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
||||||
|
@ -89,11 +90,15 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.received && !filter.sent && tx.txType == "Sent") {
|
if (filter.received &&
|
||||||
|
!filter.sent &&
|
||||||
|
tx.type == TransactionType.outgoing) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.sent && !filter.received && tx.txType == "Received") {
|
if (filter.sent &&
|
||||||
|
!filter.received &&
|
||||||
|
tx.type == TransactionType.incoming) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +136,8 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
||||||
.isNotEmpty;
|
.isNotEmpty;
|
||||||
|
|
||||||
// check if address contains
|
// check if address contains
|
||||||
contains |= tx.address.toLowerCase().contains(keyword);
|
contains |=
|
||||||
|
tx.address.value?.value.toLowerCase().contains(keyword) ?? false;
|
||||||
|
|
||||||
// check if note contains
|
// check if note contains
|
||||||
contains |= notes[tx.txid] != null &&
|
contains |= notes[tx.txid] != null &&
|
||||||
|
@ -141,11 +147,10 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
||||||
contains |= tx.txid.toLowerCase().contains(keyword);
|
contains |= tx.txid.toLowerCase().contains(keyword);
|
||||||
|
|
||||||
// check if subType contains
|
// check if subType contains
|
||||||
contains |=
|
contains |= tx.subType.name.toLowerCase().contains(keyword);
|
||||||
tx.subType.isNotEmpty && tx.subType.toLowerCase().contains(keyword);
|
|
||||||
|
|
||||||
// check if txType contains
|
// check if txType contains
|
||||||
contains |= tx.txType.toLowerCase().contains(keyword);
|
contains |= tx.type.name.toLowerCase().contains(keyword);
|
||||||
|
|
||||||
// check if date contains
|
// check if date contains
|
||||||
contains |=
|
contains |=
|
||||||
|
@ -454,17 +459,13 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
||||||
// debugPrint("Consumer build called");
|
// debugPrint("Consumer build called");
|
||||||
|
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: ref.watch(managerProvider
|
future: ref.watch(
|
||||||
.select((value) => value.transactionData)),
|
managerProvider.select((value) => value.transactions)),
|
||||||
builder: (_, AsyncSnapshot<TransactionData> snapshot) {
|
builder: (_, AsyncSnapshot<List<Transaction>> snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done &&
|
if (snapshot.connectionState == ConnectionState.done &&
|
||||||
snapshot.hasData) {
|
snapshot.hasData) {
|
||||||
final filtered = filter(
|
final filtered = filter(
|
||||||
transactions: snapshot.data!
|
transactions: snapshot.data!, filter: criteria);
|
||||||
.getAllTransactions()
|
|
||||||
.values
|
|
||||||
.toList(),
|
|
||||||
filter: criteria);
|
|
||||||
|
|
||||||
final searched = search(_searchString, filtered);
|
final searched = search(_searchString, filtered);
|
||||||
|
|
||||||
|
@ -787,33 +788,33 @@ class _DesktopTransactionCardRowState
|
||||||
late final Transaction _transaction;
|
late final Transaction _transaction;
|
||||||
late final String walletId;
|
late final String walletId;
|
||||||
|
|
||||||
String whatIsIt(String type, Coin coin) {
|
String whatIsIt(TransactionType type, Coin coin, int height) {
|
||||||
if (coin == Coin.epicCash && _transaction.slateId == null) {
|
if (coin == Coin.epicCash && _transaction.slateId == null) {
|
||||||
return "Restored Funds";
|
return "Restored Funds";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_transaction.subType == "mint") {
|
if (_transaction.subType == TransactionSubType.mint) {
|
||||||
if (_transaction.confirmedStatus) {
|
if (_transaction.isConfirmed(height, coin.requiredConfirmations)) {
|
||||||
return "Anonymized";
|
return "Anonymized";
|
||||||
} else {
|
} else {
|
||||||
return "Anonymizing";
|
return "Anonymizing";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == "Received") {
|
if (type == TransactionType.incoming) {
|
||||||
if (_transaction.confirmedStatus) {
|
if (_transaction.isConfirmed(height, coin.requiredConfirmations)) {
|
||||||
return "Received";
|
return "Received";
|
||||||
} else {
|
} else {
|
||||||
return "Receiving";
|
return "Receiving";
|
||||||
}
|
}
|
||||||
} else if (type == "Sent") {
|
} else if (type == TransactionType.outgoing) {
|
||||||
if (_transaction.confirmedStatus) {
|
if (_transaction.isConfirmed(height, coin.requiredConfirmations)) {
|
||||||
return "Sent";
|
return "Sent";
|
||||||
} else {
|
} else {
|
||||||
return "Sending";
|
return "Sending";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return type;
|
return type.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -843,15 +844,17 @@ class _DesktopTransactionCardRowState
|
||||||
|
|
||||||
late final String prefix;
|
late final String prefix;
|
||||||
if (Util.isDesktop) {
|
if (Util.isDesktop) {
|
||||||
if (_transaction.txType == "Sent") {
|
if (_transaction.type == TransactionType.outgoing) {
|
||||||
prefix = "-";
|
prefix = "-";
|
||||||
} else if (_transaction.txType == "Received") {
|
} else if (_transaction.type == TransactionType.incoming) {
|
||||||
prefix = "+";
|
prefix = "+";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
prefix = "";
|
prefix = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
@ -911,7 +914,11 @@ class _DesktopTransactionCardRowState
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
TxIcon(transaction: _transaction),
|
TxIcon(
|
||||||
|
transaction: _transaction,
|
||||||
|
currentHeight: currentHeight,
|
||||||
|
coin: coin,
|
||||||
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 12,
|
width: 12,
|
||||||
),
|
),
|
||||||
|
@ -920,7 +927,11 @@ class _DesktopTransactionCardRowState
|
||||||
child: Text(
|
child: Text(
|
||||||
_transaction.isCancelled
|
_transaction.isCancelled
|
||||||
? "Cancelled"
|
? "Cancelled"
|
||||||
: whatIsIt(_transaction.txType, coin),
|
: whatIsIt(
|
||||||
|
_transaction.type,
|
||||||
|
coin,
|
||||||
|
currentHeight,
|
||||||
|
),
|
||||||
style:
|
style:
|
||||||
STextStyles.desktopTextExtraExtraSmall(context).copyWith(
|
STextStyles.desktopTextExtraExtraSmall(context).copyWith(
|
||||||
color: Theme.of(context).extension<StackColors>()!.textDark,
|
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||||
|
|
|
@ -5,12 +5,13 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:stackwallet/models/models.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart';
|
import 'package:stackwallet/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
|
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
||||||
|
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
|
||||||
import 'package:stackwallet/providers/global/address_book_service_provider.dart';
|
import 'package:stackwallet/providers/global/address_book_service_provider.dart';
|
||||||
import 'package:stackwallet/providers/providers.dart';
|
import 'package:stackwallet/providers/providers.dart';
|
||||||
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
||||||
|
@ -19,7 +20,6 @@ import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/block_explorers.dart';
|
import 'package:stackwallet/utilities/block_explorers.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
|
|
||||||
import 'package:stackwallet/utilities/format.dart';
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
@ -80,13 +80,13 @@ class _TransactionDetailsViewState
|
||||||
|
|
||||||
coin = widget.coin;
|
coin = widget.coin;
|
||||||
amount = Format.satoshisToAmount(_transaction.amount, coin: coin);
|
amount = Format.satoshisToAmount(_transaction.amount, coin: coin);
|
||||||
fee = Format.satoshisToAmount(_transaction.fees, coin: coin);
|
fee = Format.satoshisToAmount(_transaction.fee, coin: coin);
|
||||||
|
|
||||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||||
_transaction.subType == "mint") {
|
_transaction.subType == TransactionSubType.mint) {
|
||||||
amountPrefix = "";
|
amountPrefix = "";
|
||||||
} else {
|
} else {
|
||||||
amountPrefix = _transaction.txType.toLowerCase() == "sent" ? "-" : "+";
|
amountPrefix = _transaction.type == TransactionType.outgoing ? "-" : "+";
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
// if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||||
|
@ -102,10 +102,10 @@ class _TransactionDetailsViewState
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
String whatIsIt(String type) {
|
String whatIsIt(TransactionType type, int height) {
|
||||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||||
if (_transaction.subType == "mint") {
|
if (_transaction.subType == TransactionSubType.mint) {
|
||||||
if (_transaction.confirmedStatus) {
|
if (_transaction.isConfirmed(height, coin.requiredConfirmations)) {
|
||||||
return "Minted";
|
return "Minted";
|
||||||
} else {
|
} else {
|
||||||
return "Minting";
|
return "Minting";
|
||||||
|
@ -113,23 +113,23 @@ class _TransactionDetailsViewState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == "Received") {
|
if (type == TransactionType.incoming) {
|
||||||
// if (_transaction.isMinting) {
|
// if (_transaction.isMinting) {
|
||||||
// return "Minting";
|
// return "Minting";
|
||||||
// } else
|
// } else
|
||||||
if (_transaction.confirmedStatus) {
|
if (_transaction.isConfirmed(height, coin.requiredConfirmations)) {
|
||||||
return "Received";
|
return "Received";
|
||||||
} else {
|
} else {
|
||||||
return "Receiving";
|
return "Receiving";
|
||||||
}
|
}
|
||||||
} else if (type == "Sent") {
|
} else if (type == TransactionType.outgoing) {
|
||||||
if (_transaction.confirmedStatus) {
|
if (_transaction.isConfirmed(height, coin.requiredConfirmations)) {
|
||||||
return "Sent";
|
return "Sent";
|
||||||
} else {
|
} else {
|
||||||
return "Sending";
|
return "Sending";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return type;
|
return type.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,6 +298,8 @@ class _TransactionDetailsViewState
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
|
||||||
|
|
||||||
return ConditionalParent(
|
return ConditionalParent(
|
||||||
condition: !isDesktop,
|
condition: !isDesktop,
|
||||||
builder: (child) => Background(
|
builder: (child) => Background(
|
||||||
|
@ -403,6 +405,8 @@ class _TransactionDetailsViewState
|
||||||
children: [
|
children: [
|
||||||
TxIcon(
|
TxIcon(
|
||||||
transaction: _transaction,
|
transaction: _transaction,
|
||||||
|
currentHeight: currentHeight,
|
||||||
|
coin: coin,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 16,
|
width: 16,
|
||||||
|
@ -411,7 +415,9 @@ class _TransactionDetailsViewState
|
||||||
_transaction.isCancelled
|
_transaction.isCancelled
|
||||||
? "Cancelled"
|
? "Cancelled"
|
||||||
: whatIsIt(
|
: whatIsIt(
|
||||||
_transaction.txType),
|
_transaction.type,
|
||||||
|
currentHeight,
|
||||||
|
),
|
||||||
style:
|
style:
|
||||||
STextStyles.desktopTextMedium(
|
STextStyles.desktopTextMedium(
|
||||||
context),
|
context),
|
||||||
|
@ -489,6 +495,8 @@ class _TransactionDetailsViewState
|
||||||
if (!isDesktop)
|
if (!isDesktop)
|
||||||
TxIcon(
|
TxIcon(
|
||||||
transaction: _transaction,
|
transaction: _transaction,
|
||||||
|
currentHeight: currentHeight,
|
||||||
|
coin: coin,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -523,13 +531,17 @@ class _TransactionDetailsViewState
|
||||||
SelectableText(
|
SelectableText(
|
||||||
_transaction.isCancelled
|
_transaction.isCancelled
|
||||||
? "Cancelled"
|
? "Cancelled"
|
||||||
: whatIsIt(_transaction.txType),
|
: whatIsIt(
|
||||||
|
_transaction.type,
|
||||||
|
currentHeight,
|
||||||
|
),
|
||||||
style: isDesktop
|
style: isDesktop
|
||||||
? STextStyles
|
? STextStyles
|
||||||
.desktopTextExtraExtraSmall(
|
.desktopTextExtraExtraSmall(
|
||||||
context)
|
context)
|
||||||
.copyWith(
|
.copyWith(
|
||||||
color: _transaction.txType == "Sent"
|
color: _transaction.type ==
|
||||||
|
TransactionType.outgoing
|
||||||
? Theme.of(context)
|
? Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.accentColorOrange
|
.accentColorOrange
|
||||||
|
@ -546,11 +558,12 @@ class _TransactionDetailsViewState
|
||||||
),
|
),
|
||||||
if (!((coin == Coin.monero ||
|
if (!((coin == Coin.monero ||
|
||||||
coin == Coin.wownero) &&
|
coin == Coin.wownero) &&
|
||||||
_transaction.txType.toLowerCase() ==
|
_transaction.type ==
|
||||||
"sent") &&
|
TransactionType.outgoing) &&
|
||||||
!((coin == Coin.firo ||
|
!((coin == Coin.firo ||
|
||||||
coin == Coin.firoTestNet) &&
|
coin == Coin.firoTestNet) &&
|
||||||
_transaction.subType == "mint"))
|
_transaction.subType ==
|
||||||
|
TransactionSubType.mint))
|
||||||
isDesktop
|
isDesktop
|
||||||
? const _Divider()
|
? const _Divider()
|
||||||
: const SizedBox(
|
: const SizedBox(
|
||||||
|
@ -558,11 +571,12 @@ class _TransactionDetailsViewState
|
||||||
),
|
),
|
||||||
if (!((coin == Coin.monero ||
|
if (!((coin == Coin.monero ||
|
||||||
coin == Coin.wownero) &&
|
coin == Coin.wownero) &&
|
||||||
_transaction.txType.toLowerCase() ==
|
_transaction.type ==
|
||||||
"sent") &&
|
TransactionType.outgoing) &&
|
||||||
!((coin == Coin.firo ||
|
!((coin == Coin.firo ||
|
||||||
coin == Coin.firoTestNet) &&
|
coin == Coin.firoTestNet) &&
|
||||||
_transaction.subType == "mint"))
|
_transaction.subType ==
|
||||||
|
TransactionSubType.mint))
|
||||||
RoundedWhiteContainer(
|
RoundedWhiteContainer(
|
||||||
padding: isDesktop
|
padding: isDesktop
|
||||||
? const EdgeInsets.all(16)
|
? const EdgeInsets.all(16)
|
||||||
|
@ -578,8 +592,8 @@ class _TransactionDetailsViewState
|
||||||
CrossAxisAlignment.start,
|
CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
_transaction.txType.toLowerCase() ==
|
_transaction.type ==
|
||||||
"sent"
|
TransactionType.outgoing
|
||||||
? "Sent to"
|
? "Sent to"
|
||||||
: "Receiving address",
|
: "Receiving address",
|
||||||
style: isDesktop
|
style: isDesktop
|
||||||
|
@ -592,17 +606,19 @@ class _TransactionDetailsViewState
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
_transaction.txType.toLowerCase() ==
|
_transaction.type ==
|
||||||
"received"
|
TransactionType.incoming
|
||||||
? FutureBuilder(
|
? FutureBuilder(
|
||||||
future: fetchContactNameFor(
|
future: fetchContactNameFor(
|
||||||
_transaction.address),
|
_transaction.address
|
||||||
|
.value!.value),
|
||||||
builder: (builderContext,
|
builder: (builderContext,
|
||||||
AsyncSnapshot<String>
|
AsyncSnapshot<String>
|
||||||
snapshot) {
|
snapshot) {
|
||||||
String
|
String
|
||||||
addressOrContactName =
|
addressOrContactName =
|
||||||
_transaction.address;
|
_transaction.address
|
||||||
|
.value!.value;
|
||||||
if (snapshot.connectionState ==
|
if (snapshot.connectionState ==
|
||||||
ConnectionState
|
ConnectionState
|
||||||
.done &&
|
.done &&
|
||||||
|
@ -630,7 +646,8 @@ class _TransactionDetailsViewState
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: SelectableText(
|
: SelectableText(
|
||||||
_transaction.address,
|
_transaction
|
||||||
|
.address.value!.value,
|
||||||
style: isDesktop
|
style: isDesktop
|
||||||
? STextStyles
|
? STextStyles
|
||||||
.desktopTextExtraExtraSmall(
|
.desktopTextExtraExtraSmall(
|
||||||
|
@ -651,7 +668,7 @@ class _TransactionDetailsViewState
|
||||||
),
|
),
|
||||||
if (isDesktop)
|
if (isDesktop)
|
||||||
IconCopyButton(
|
IconCopyButton(
|
||||||
data: _transaction.address,
|
data: _transaction.address.value!.value,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -855,7 +872,10 @@ class _TransactionDetailsViewState
|
||||||
: const EdgeInsets.all(12),
|
: const EdgeInsets.all(12),
|
||||||
child: Builder(builder: (context) {
|
child: Builder(builder: (context) {
|
||||||
final feeString = showFeePending
|
final feeString = showFeePending
|
||||||
? _transaction.confirmedStatus
|
? _transaction.isConfirmed(
|
||||||
|
currentHeight,
|
||||||
|
coin.requiredConfirmations,
|
||||||
|
)
|
||||||
? Format.localizedStringAsFixed(
|
? Format.localizedStringAsFixed(
|
||||||
value: fee,
|
value: fee,
|
||||||
locale: ref.watch(
|
locale: ref.watch(
|
||||||
|
@ -947,9 +967,14 @@ class _TransactionDetailsViewState
|
||||||
: const EdgeInsets.all(12),
|
: const EdgeInsets.all(12),
|
||||||
child: Builder(builder: (context) {
|
child: Builder(builder: (context) {
|
||||||
final height = widget.coin != Coin.epicCash &&
|
final height = widget.coin != Coin.epicCash &&
|
||||||
_transaction.confirmedStatus
|
_transaction.isConfirmed(
|
||||||
|
currentHeight,
|
||||||
|
coin.requiredConfirmations,
|
||||||
|
)
|
||||||
? "${_transaction.height == 0 ? "Unknown" : _transaction.height}"
|
? "${_transaction.height == 0 ? "Unknown" : _transaction.height}"
|
||||||
: _transaction.confirmations > 0
|
: _transaction.getConfirmations(
|
||||||
|
currentHeight) >
|
||||||
|
0
|
||||||
? "${_transaction.height}"
|
? "${_transaction.height}"
|
||||||
: "Pending";
|
: "Pending";
|
||||||
|
|
||||||
|
@ -1297,9 +1322,13 @@ class _TransactionDetailsViewState
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||||
floatingActionButton: (coin == Coin.epicCash &&
|
floatingActionButton: (coin == Coin.epicCash &&
|
||||||
_transaction.confirmedStatus == false &&
|
_transaction.isConfirmed(
|
||||||
|
currentHeight,
|
||||||
|
coin.requiredConfirmations,
|
||||||
|
) ==
|
||||||
|
false &&
|
||||||
_transaction.isCancelled == false &&
|
_transaction.isCancelled == false &&
|
||||||
_transaction.txType == "Sent")
|
_transaction.type == TransactionType.outgoing)
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
|
|
|
@ -35,7 +35,6 @@ import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
|
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
|
|
||||||
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
|
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
@ -707,7 +706,6 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
Column(
|
Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
const Spacer(),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
@ -717,11 +715,11 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
left: 16,
|
left: 16,
|
||||||
right: 16,
|
right: 16,
|
||||||
),
|
),
|
||||||
child: SizedBox(
|
|
||||||
height: WalletView.navBarHeight,
|
|
||||||
child: WalletNavigationBar(
|
child: WalletNavigationBar(
|
||||||
enableExchange:
|
walletId: widget.walletId,
|
||||||
Constants.enableExchange &&
|
coin: ref.watch(managerProvider
|
||||||
|
.select((value) => value.coin)),
|
||||||
|
enableExchange: Constants.enableExchange &&
|
||||||
ref.watch(managerProvider.select(
|
ref.watch(managerProvider.select(
|
||||||
(value) => value.coin)) !=
|
(value) => value.coin)) !=
|
||||||
Coin.epicCash,
|
Coin.epicCash,
|
||||||
|
@ -748,8 +746,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
final coin =
|
final coin =
|
||||||
ref.read(managerProvider).coin;
|
ref.read(managerProvider).coin;
|
||||||
switch (ref
|
switch (ref
|
||||||
.read(
|
.read(walletBalanceToggleStateProvider
|
||||||
walletBalanceToggleStateProvider
|
|
||||||
.state)
|
.state)
|
||||||
.state) {
|
.state) {
|
||||||
case WalletBalanceToggleState.full:
|
case WalletBalanceToggleState.full:
|
||||||
|
@ -759,8 +756,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
.state)
|
.state)
|
||||||
.state = "Public";
|
.state = "Public";
|
||||||
break;
|
break;
|
||||||
case WalletBalanceToggleState
|
case WalletBalanceToggleState.available:
|
||||||
.available:
|
|
||||||
ref
|
ref
|
||||||
.read(
|
.read(
|
||||||
publicPrivateBalanceStateProvider
|
publicPrivateBalanceStateProvider
|
||||||
|
@ -779,7 +775,6 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
onBuyPressed: () {},
|
onBuyPressed: () {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -217,8 +217,8 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: ref.watch(
|
future: Future(() => ref.watch(managerProvider
|
||||||
managerProvider.select((value) => value.totalBalance)),
|
.select((value) => value.balance.getTotal()))),
|
||||||
builder: (builderContext, AsyncSnapshot<Decimal> snapshot) {
|
builder: (builderContext, AsyncSnapshot<Decimal> snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done &&
|
if (snapshot.connectionState == ConnectionState.done &&
|
||||||
snapshot.hasData) {
|
snapshot.hasData) {
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
import 'package:stackwallet/models/contact.dart';
|
import 'package:stackwallet/models/contact.dart';
|
||||||
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
import 'package:stackwallet/pages/address_book_views/subviews/add_new_contact_address_view.dart';
|
import 'package:stackwallet/pages/address_book_views/subviews/add_new_contact_address_view.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/address_book_view/subwidgets/desktop_address_card.dart';
|
import 'package:stackwallet/pages_desktop_specific/address_book_view/subwidgets/desktop_address_card.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart';
|
import 'package:stackwallet/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart';
|
||||||
|
@ -22,6 +24,8 @@ import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
import 'package:stackwallet/widgets/transaction_card.dart';
|
import 'package:stackwallet/widgets/transaction_card.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
import '../../../db/main_db.dart';
|
||||||
|
|
||||||
class DesktopContactDetails extends ConsumerStatefulWidget {
|
class DesktopContactDetails extends ConsumerStatefulWidget {
|
||||||
const DesktopContactDetails({
|
const DesktopContactDetails({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -57,11 +61,13 @@ class _DesktopContactDetailsState extends ConsumerState<DesktopContactDetails> {
|
||||||
|
|
||||||
List<Tuple2<String, Transaction>> result = [];
|
List<Tuple2<String, Transaction>> result = [];
|
||||||
for (final manager in managers) {
|
for (final manager in managers) {
|
||||||
final transactions = (await manager.transactionData)
|
final transactions = await MainDB.instance
|
||||||
.getAllTransactions()
|
.getTransactions(manager.walletId)
|
||||||
.values
|
.filter()
|
||||||
.toList()
|
.anyOf(contact.addresses.map((e) => e.address),
|
||||||
.where((e) => _contactHasAddress(e.address, contact));
|
(q, String e) => q.address((q) => q.valueEqualTo(e)))
|
||||||
|
.sortByTimestampDesc()
|
||||||
|
.findAll();
|
||||||
|
|
||||||
for (final tx in transactions) {
|
for (final tx in transactions) {
|
||||||
result.add(Tuple2(manager.walletId, tx));
|
result.add(Tuple2(manager.walletId, tx));
|
||||||
|
|
|
@ -4,12 +4,15 @@ import 'package:decimal/decimal.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart';
|
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart';
|
||||||
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
||||||
import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart';
|
import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart';
|
||||||
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
||||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
|
import 'package:stackwallet/route_generator.dart';
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/format.dart';
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
|
@ -26,7 +29,7 @@ import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
import '../../route_generator.dart';
|
import '../../db/main_db.dart';
|
||||||
|
|
||||||
class DesktopAllTradesView extends ConsumerStatefulWidget {
|
class DesktopAllTradesView extends ConsumerStatefulWidget {
|
||||||
const DesktopAllTradesView({Key? key}) : super(key: key);
|
const DesktopAllTradesView({Key? key}) : super(key: key);
|
||||||
|
@ -349,10 +352,12 @@ class _DesktopTradeRowCardState extends ConsumerState<DesktopTradeRowCard> {
|
||||||
//todo: check if print needed
|
//todo: check if print needed
|
||||||
// debugPrint("name: ${manager.walletName}");
|
// debugPrint("name: ${manager.walletName}");
|
||||||
|
|
||||||
// TODO store tx data completely locally in isar so we don't lock up ui here when querying txData
|
final tx = await MainDB.instance
|
||||||
final txData = await manager.transactionData;
|
.getTransactions(walletIds.first)
|
||||||
|
.filter()
|
||||||
|
.txidEqualTo(txid)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
final tx = txData.getAllTransactions()[txid];
|
|
||||||
await showDialog<void>(
|
await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => DesktopDialog(
|
builder: (context) => DesktopDialog(
|
||||||
|
|
|
@ -305,8 +305,9 @@ class _BalanceDisplayState extends ConsumerState<BalanceDisplay> {
|
||||||
final locale = ref.watch(
|
final locale = ref.watch(
|
||||||
localeServiceChangeNotifierProvider.select((value) => value.locale));
|
localeServiceChangeNotifierProvider.select((value) => value.locale));
|
||||||
|
|
||||||
|
// TODO redo this widget now that its not actually a future
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: manager.availableBalance,
|
future: Future(() => manager.balance.getSpendable()),
|
||||||
builder: (context, AsyncSnapshot<Decimal> snapshot) {
|
builder: (context, AsyncSnapshot<Decimal> snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done &&
|
if (snapshot.connectionState == ConnectionState.done &&
|
||||||
snapshot.hasData &&
|
snapshot.hasData &&
|
||||||
|
|
|
@ -2,6 +2,8 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart';
|
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart';
|
||||||
import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart';
|
import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart';
|
||||||
|
@ -17,6 +19,8 @@ import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
import 'package:stackwallet/widgets/trade_card.dart';
|
import 'package:stackwallet/widgets/trade_card.dart';
|
||||||
|
|
||||||
|
import '../../../db/main_db.dart';
|
||||||
|
|
||||||
class DesktopTradeHistory extends ConsumerStatefulWidget {
|
class DesktopTradeHistory extends ConsumerStatefulWidget {
|
||||||
const DesktopTradeHistory({Key? key}) : super(key: key);
|
const DesktopTradeHistory({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@ -126,10 +130,11 @@ class _DesktopTradeHistoryState extends ConsumerState<DesktopTradeHistory> {
|
||||||
//todo: check if print needed
|
//todo: check if print needed
|
||||||
// debugPrint("name: ${manager.walletName}");
|
// debugPrint("name: ${manager.walletName}");
|
||||||
|
|
||||||
// TODO store tx data completely locally in isar so we don't lock up ui here when querying txData
|
final tx = await MainDB.instance
|
||||||
final txData = await manager.transactionData;
|
.getTransactions(walletIds.first)
|
||||||
|
.filter()
|
||||||
final tx = txData.getAllTransactions()[txid];
|
.txidEqualTo(txid)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
await showDialog<void>(
|
await showDialog<void>(
|
||||||
|
|
|
@ -40,6 +40,7 @@ class CoinWalletsTable extends ConsumerWidget {
|
||||||
children: [
|
children: [
|
||||||
for (int i = 0; i < walletIds.length; i++)
|
for (int i = 0; i < walletIds.length; i++)
|
||||||
Column(
|
Column(
|
||||||
|
key: Key("${coin.name}_$runtimeType${walletIds[i]}_key"),
|
||||||
children: [
|
children: [
|
||||||
if (i != 0)
|
if (i != 0)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
|
|
@ -37,6 +37,7 @@ class _WalletTableState extends ConsumerState<WalletSummaryTable> {
|
||||||
rows: [
|
rows: [
|
||||||
for (int i = 0; i < providersByCoin.length; i++)
|
for (int i = 0; i < providersByCoin.length; i++)
|
||||||
Builder(
|
Builder(
|
||||||
|
key: Key("${providersByCoin[i].key.name}_${runtimeType}_key"),
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final providers = ref.watch(walletsChangeNotifierProvider.select(
|
final providers = ref.watch(walletsChangeNotifierProvider.select(
|
||||||
(value) => value
|
(value) => value
|
||||||
|
|
|
@ -8,6 +8,8 @@ import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
|
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
|
||||||
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
|
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/paynym_claim_view.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart';
|
||||||
|
@ -15,8 +17,12 @@ import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/recent_desktop_transactions.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/recent_desktop_transactions.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_button.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_button.dart';
|
||||||
import 'package:stackwallet/providers/global/auto_swb_service_provider.dart';
|
import 'package:stackwallet/providers/global/auto_swb_service_provider.dart';
|
||||||
|
import 'package:stackwallet/providers/global/paynym_api_provider.dart';
|
||||||
import 'package:stackwallet/providers/providers.dart';
|
import 'package:stackwallet/providers/providers.dart';
|
||||||
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
||||||
|
import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart';
|
||||||
|
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||||
|
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_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/event_bus/global_event_bus.dart';
|
||||||
|
@ -36,6 +42,7 @@ import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
import 'package:stackwallet/widgets/hover_text_field.dart';
|
import 'package:stackwallet/widgets/hover_text_field.dart';
|
||||||
|
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
@ -281,6 +288,51 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> onPaynymButtonPressed() async {
|
||||||
|
unawaited(
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const LoadingIndicator(
|
||||||
|
width: 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// todo make generic and not doge specific
|
||||||
|
final wallet = (ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(widget.walletId)
|
||||||
|
.wallet as DogecoinWallet);
|
||||||
|
|
||||||
|
final code = await wallet.getPaymentCode();
|
||||||
|
|
||||||
|
final account = await ref.read(paynymAPIProvider).nym(code.toString());
|
||||||
|
|
||||||
|
Logging.instance.log(
|
||||||
|
"my nym account: $account",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.of(context, rootNavigator: true).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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
controller = TextEditingController();
|
controller = TextEditingController();
|
||||||
|
@ -482,6 +534,21 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (coin.hasPaynymSupport)
|
||||||
|
SecondaryButton(
|
||||||
|
label: "PayNym",
|
||||||
|
width: 160,
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.user,
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonTextSecondary,
|
||||||
|
),
|
||||||
|
onPressed: onPaynymButtonPressed,
|
||||||
|
),
|
||||||
// if (coin == Coin.firo) const SizedBox(width: 16),
|
// if (coin == Coin.firo) const SizedBox(width: 16),
|
||||||
// SecondaryButton(
|
// SecondaryButton(
|
||||||
// width: 180,
|
// width: 180,
|
||||||
|
|
|
@ -97,73 +97,73 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
final manager =
|
final manager =
|
||||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||||
|
|
||||||
// TODO: remove the need for this!!
|
// // TODO: remove the need for this!!
|
||||||
final bool isOwnAddress = await manager.isOwnAddress(_address!);
|
// final bool isOwnAddress = await manager.isOwnAddress(_address!);
|
||||||
if (isOwnAddress) {
|
// if (isOwnAddress) {
|
||||||
await showDialog<dynamic>(
|
// await showDialog<dynamic>(
|
||||||
context: context,
|
// context: context,
|
||||||
useSafeArea: false,
|
// useSafeArea: false,
|
||||||
barrierDismissible: true,
|
// barrierDismissible: true,
|
||||||
builder: (context) {
|
// builder: (context) {
|
||||||
return DesktopDialog(
|
// return DesktopDialog(
|
||||||
maxWidth: 400,
|
// maxWidth: 400,
|
||||||
maxHeight: double.infinity,
|
// maxHeight: double.infinity,
|
||||||
child: Padding(
|
// child: Padding(
|
||||||
padding: const EdgeInsets.only(
|
// padding: const EdgeInsets.only(
|
||||||
left: 32,
|
// left: 32,
|
||||||
bottom: 32,
|
// bottom: 32,
|
||||||
),
|
// ),
|
||||||
child: Column(
|
// child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
// children: [
|
||||||
Row(
|
// Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
// children: [
|
||||||
Text(
|
// Text(
|
||||||
"Transaction failed",
|
// "Transaction failed",
|
||||||
style: STextStyles.desktopH3(context),
|
// style: STextStyles.desktopH3(context),
|
||||||
),
|
// ),
|
||||||
const DesktopDialogCloseButton(),
|
// const DesktopDialogCloseButton(),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
const SizedBox(
|
// const SizedBox(
|
||||||
height: 12,
|
// height: 12,
|
||||||
),
|
// ),
|
||||||
Text(
|
// Text(
|
||||||
"Sending to self is currently disabled",
|
// "Sending to self is currently disabled",
|
||||||
textAlign: TextAlign.left,
|
// textAlign: TextAlign.left,
|
||||||
style: STextStyles.desktopTextExtraExtraSmall(context)
|
// style: STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
.copyWith(
|
// .copyWith(
|
||||||
fontSize: 18,
|
// fontSize: 18,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(
|
// const SizedBox(
|
||||||
height: 40,
|
// height: 40,
|
||||||
),
|
// ),
|
||||||
Row(
|
// Row(
|
||||||
children: [
|
// children: [
|
||||||
Expanded(
|
// Expanded(
|
||||||
child: SecondaryButton(
|
// child: SecondaryButton(
|
||||||
buttonHeight: ButtonHeight.l,
|
// buttonHeight: ButtonHeight.l,
|
||||||
label: "Ok",
|
// label: "Ok",
|
||||||
onPressed: () {
|
// onPressed: () {
|
||||||
Navigator.of(context).pop();
|
// Navigator.of(context).pop();
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(
|
// const SizedBox(
|
||||||
width: 32,
|
// width: 32,
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
final amount = Format.decimalAmountToSatoshis(_amountToSend!, coin);
|
final amount = Format.decimalAmountToSatoshis(_amountToSend!, coin);
|
||||||
int availableBalance;
|
int availableBalance;
|
||||||
|
@ -171,16 +171,14 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
if (ref.read(publicPrivateBalanceStateProvider.state).state ==
|
if (ref.read(publicPrivateBalanceStateProvider.state).state ==
|
||||||
"Private") {
|
"Private") {
|
||||||
availableBalance = Format.decimalAmountToSatoshis(
|
availableBalance = Format.decimalAmountToSatoshis(
|
||||||
await (manager.wallet as FiroWallet).availablePrivateBalance(),
|
(manager.wallet as FiroWallet).availablePrivateBalance(), coin);
|
||||||
coin);
|
|
||||||
} else {
|
} else {
|
||||||
availableBalance = Format.decimalAmountToSatoshis(
|
availableBalance = Format.decimalAmountToSatoshis(
|
||||||
await (manager.wallet as FiroWallet).availablePublicBalance(),
|
(manager.wallet as FiroWallet).availablePublicBalance(), coin);
|
||||||
coin);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
availableBalance =
|
availableBalance =
|
||||||
Format.decimalAmountToSatoshis(await manager.availableBalance, coin);
|
Format.decimalAmountToSatoshis(manager.balance.getSpendable(), coin);
|
||||||
}
|
}
|
||||||
|
|
||||||
// confirm send all
|
// confirm send all
|
||||||
|
@ -568,9 +566,9 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
if (wallet != null) {
|
if (wallet != null) {
|
||||||
Decimal? balance;
|
Decimal? balance;
|
||||||
if (private) {
|
if (private) {
|
||||||
balance = await wallet.availablePrivateBalance();
|
balance = wallet.availablePrivateBalance();
|
||||||
} else {
|
} else {
|
||||||
balance = await wallet.availablePublicBalance();
|
balance = wallet.availablePublicBalance();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Format.localizedStringAsFixed(
|
return Format.localizedStringAsFixed(
|
||||||
|
@ -757,19 +755,18 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
.wallet as FiroWallet;
|
.wallet as FiroWallet;
|
||||||
if (ref.read(publicPrivateBalanceStateProvider.state).state ==
|
if (ref.read(publicPrivateBalanceStateProvider.state).state ==
|
||||||
"Private") {
|
"Private") {
|
||||||
cryptoAmountController.text =
|
cryptoAmountController.text = (firoWallet.availablePrivateBalance())
|
||||||
(await firoWallet.availablePrivateBalance())
|
|
||||||
.toStringAsFixed(Constants.decimalPlacesForCoin(coin));
|
.toStringAsFixed(Constants.decimalPlacesForCoin(coin));
|
||||||
} else {
|
} else {
|
||||||
cryptoAmountController.text =
|
cryptoAmountController.text = (firoWallet.availablePublicBalance())
|
||||||
(await firoWallet.availablePublicBalance())
|
|
||||||
.toStringAsFixed(Constants.decimalPlacesForCoin(coin));
|
.toStringAsFixed(Constants.decimalPlacesForCoin(coin));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cryptoAmountController.text = (await ref
|
cryptoAmountController.text = (ref
|
||||||
.read(walletsChangeNotifierProvider)
|
.read(walletsChangeNotifierProvider)
|
||||||
.getManager(walletId)
|
.getManager(walletId)
|
||||||
.availableBalance)
|
.balance
|
||||||
|
.getSpendable())
|
||||||
.toStringAsFixed(Constants.decimalPlacesForCoin(coin));
|
.toStringAsFixed(Constants.decimalPlacesForCoin(coin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,15 +68,17 @@ class _WDesktopWalletSummaryState extends State<DesktopWalletSummary> {
|
||||||
final firoWallet = ref.watch(
|
final firoWallet = ref.watch(
|
||||||
managerProvider.select((value) => value.wallet))
|
managerProvider.select((value) => value.wallet))
|
||||||
as FiroWallet;
|
as FiroWallet;
|
||||||
totalBalanceFuture = firoWallet.availablePublicBalance();
|
totalBalanceFuture =
|
||||||
availableBalanceFuture =
|
Future(() => firoWallet.balance.getSpendable());
|
||||||
firoWallet.availablePrivateBalance();
|
availableBalanceFuture = Future(
|
||||||
|
() => firoWallet.balancePrivate.getSpendable());
|
||||||
} else {
|
} else {
|
||||||
totalBalanceFuture = ref.watch(managerProvider
|
final manager = ref.watch(walletsChangeNotifierProvider
|
||||||
.select((value) => value.totalBalance));
|
.select((value) => value.getManager(walletId)));
|
||||||
|
totalBalanceFuture =
|
||||||
availableBalanceFuture = ref.watch(managerProvider
|
Future(() => manager.balance.getTotal());
|
||||||
.select((value) => value.availableBalance));
|
availableBalanceFuture =
|
||||||
|
Future(() => manager.balance.getSpendable());
|
||||||
}
|
}
|
||||||
|
|
||||||
final locale = ref.watch(localeServiceChangeNotifierProvider
|
final locale = ref.watch(localeServiceChangeNotifierProvider
|
||||||
|
|
|
@ -13,12 +13,17 @@ import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||||
|
|
||||||
|
import '../../hive/db.dart';
|
||||||
|
import '../../utilities/db_version_migration.dart';
|
||||||
|
import '../../utilities/logger.dart';
|
||||||
|
|
||||||
class DesktopLoginView extends ConsumerStatefulWidget {
|
class DesktopLoginView extends ConsumerStatefulWidget {
|
||||||
const DesktopLoginView({
|
const DesktopLoginView({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -43,6 +48,25 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
|
||||||
bool hidePassword = true;
|
bool hidePassword = true;
|
||||||
bool _continueEnabled = false;
|
bool _continueEnabled = false;
|
||||||
|
|
||||||
|
Future<void> _checkDesktopMigrate() async {
|
||||||
|
if (Util.isDesktop) {
|
||||||
|
int dbVersion = DB.instance.get<dynamic>(
|
||||||
|
boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ??
|
||||||
|
0;
|
||||||
|
if (dbVersion < Constants.currentHiveDbVersion) {
|
||||||
|
try {
|
||||||
|
await DbVersionMigrator().migrate(
|
||||||
|
dbVersion,
|
||||||
|
secureStore: ref.read(secureStoreProvider),
|
||||||
|
);
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log("Cannot migrate desktop database\n$e $s",
|
||||||
|
level: LogLevel.Error, printFullLength: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> login() async {
|
Future<void> login() async {
|
||||||
try {
|
try {
|
||||||
unawaited(
|
unawaited(
|
||||||
|
@ -63,12 +87,18 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
|
||||||
|
|
||||||
await Future<void>.delayed(const Duration(seconds: 1));
|
await Future<void>.delayed(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
// init security context
|
||||||
await ref
|
await ref
|
||||||
.read(storageCryptoHandlerProvider)
|
.read(storageCryptoHandlerProvider)
|
||||||
.initFromExisting(passwordController.text);
|
.initFromExisting(passwordController.text);
|
||||||
|
|
||||||
|
// init desktop secure storage
|
||||||
await (ref.read(secureStoreProvider).store as DesktopSecureStore).init();
|
await (ref.read(secureStoreProvider).store as DesktopSecureStore).init();
|
||||||
|
|
||||||
|
// check and migrate if needed
|
||||||
|
await _checkDesktopMigrate();
|
||||||
|
|
||||||
|
// load data
|
||||||
await widget.load?.call();
|
await widget.load?.call();
|
||||||
|
|
||||||
// if no errors passphrase is correct
|
// if no errors passphrase is correct
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import 'package:dart_numerics/dart_numerics.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
|
||||||
|
final currentHeightProvider =
|
||||||
|
StateProvider.family<int, Coin>((ref, coin) => int64MaxValue);
|
4
lib/providers/global/paynym_api_provider.dart
Normal file
4
lib/providers/global/paynym_api_provider.dart
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/utilities/paynym_is_api.dart';
|
||||||
|
|
||||||
|
final paynymAPIProvider = Provider<PaynymIsApi>((_) => PaynymIsApi());
|
|
@ -0,0 +1,5 @@
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||||
|
|
||||||
|
final selectedPaynymDetailsItemProvider =
|
||||||
|
StateProvider.autoDispose<PaynymAccountLite?>((_) => null);
|
|
@ -0,0 +1,5 @@
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/models/paynym/paynym_account.dart';
|
||||||
|
|
||||||
|
final myPaynymAccountStateProvider =
|
||||||
|
StateProvider<PaynymAccount?>((ref) => null);
|
|
@ -5,7 +5,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:stackwallet/models/contact_address_entry.dart';
|
import 'package:stackwallet/models/contact_address_entry.dart';
|
||||||
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
|
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
|
||||||
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
||||||
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
|
||||||
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
|
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart';
|
import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart';
|
import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart';
|
||||||
|
@ -36,6 +35,9 @@ import 'package:stackwallet/pages/home_view/home_view.dart';
|
||||||
import 'package:stackwallet/pages/intro_view.dart';
|
import 'package:stackwallet/pages/intro_view.dart';
|
||||||
import 'package:stackwallet/pages/manage_favorites_view/manage_favorites_view.dart';
|
import 'package:stackwallet/pages/manage_favorites_view/manage_favorites_view.dart';
|
||||||
import 'package:stackwallet/pages/notification_views/notifications_view.dart';
|
import 'package:stackwallet/pages/notification_views/notifications_view.dart';
|
||||||
|
import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart';
|
||||||
|
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/pinpad_views/create_pin_view.dart';
|
||||||
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_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/receive_view/receive_view.dart';
|
||||||
|
@ -119,6 +121,8 @@ import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
import 'models/isar/models/blockchain_data/transaction.dart';
|
||||||
|
|
||||||
class RouteGenerator {
|
class RouteGenerator {
|
||||||
static const bool useMaterialPageRoute = true;
|
static const bool useMaterialPageRoute = true;
|
||||||
|
|
||||||
|
@ -172,7 +176,7 @@ class RouteGenerator {
|
||||||
}
|
}
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
builder: (_) => StackPrivacyCalls(isSettings: false),
|
builder: (_) => const StackPrivacyCalls(isSettings: false),
|
||||||
settings: RouteSettings(name: settings.name));
|
settings: RouteSettings(name: settings.name));
|
||||||
|
|
||||||
case WalletsView.routeName:
|
case WalletsView.routeName:
|
||||||
|
@ -187,6 +191,48 @@ class RouteGenerator {
|
||||||
builder: (_) => const AddWalletView(),
|
builder: (_) => const AddWalletView(),
|
||||||
settings: RouteSettings(name: settings.name));
|
settings: RouteSettings(name: settings.name));
|
||||||
|
|
||||||
|
case PaynymClaimView.routeName:
|
||||||
|
if (args is String) {
|
||||||
|
return getRoute(
|
||||||
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
|
builder: (_) => PaynymClaimView(
|
||||||
|
walletId: args,
|
||||||
|
),
|
||||||
|
settings: RouteSettings(
|
||||||
|
name: settings.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
|
case PaynymHomeView.routeName:
|
||||||
|
if (args is String) {
|
||||||
|
return getRoute(
|
||||||
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
|
builder: (_) => PaynymHomeView(
|
||||||
|
walletId: args,
|
||||||
|
),
|
||||||
|
settings: RouteSettings(
|
||||||
|
name: settings.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
|
case AddNewPaynymFollowView.routeName:
|
||||||
|
if (args is String) {
|
||||||
|
return getRoute(
|
||||||
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
|
builder: (_) => AddNewPaynymFollowView(
|
||||||
|
walletId: args,
|
||||||
|
),
|
||||||
|
settings: RouteSettings(
|
||||||
|
name: settings.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
case GlobalSettingsView.routeName:
|
case GlobalSettingsView.routeName:
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
756
lib/services/coins/coin_paynym_extension.dart
Normal file
756
lib/services/coins/coin_paynym_extension.dart
Normal file
|
@ -0,0 +1,756 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:bip47/bip47.dart';
|
||||||
|
import 'package:bip47/src/util.dart';
|
||||||
|
import 'package:bitcoindart/bitcoindart.dart' as btc_dart;
|
||||||
|
import 'package:bitcoindart/src/utils/constants/op.dart' as op;
|
||||||
|
import 'package:bitcoindart/src/utils/script.dart' as bscript;
|
||||||
|
import 'package:decimal/decimal.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:pointycastle/digests/sha256.dart';
|
||||||
|
import 'package:stackwallet/hive/db.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
|
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||||
|
import 'package:stackwallet/utilities/address_utils.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
class SWException with Exception {
|
||||||
|
SWException(this.message);
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() => message;
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsufficientBalanceException extends SWException {
|
||||||
|
InsufficientBalanceException(super.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaynymSendException extends SWException {
|
||||||
|
PaynymSendException(super.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PayNym on DogecoinWallet {
|
||||||
|
// fetch or generate this wallet's bip47 payment code
|
||||||
|
Future<PaymentCode> getPaymentCode() async {
|
||||||
|
final paymentCodeString = DB.instance
|
||||||
|
.get<dynamic>(boxName: walletId, key: "paymentCodeString") as String?;
|
||||||
|
PaymentCode paymentCode;
|
||||||
|
if (paymentCodeString == null) {
|
||||||
|
final node = getBip32Root((await mnemonic).join(" "), network)
|
||||||
|
.derivePath("m/47'/0'/0'");
|
||||||
|
paymentCode =
|
||||||
|
PaymentCode.initFromPubKey(node.publicKey, node.chainCode, network);
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: walletId,
|
||||||
|
key: "paymentCodeString",
|
||||||
|
value: paymentCode.toString());
|
||||||
|
} else {
|
||||||
|
paymentCode = PaymentCode.fromPaymentCode(paymentCodeString, network);
|
||||||
|
}
|
||||||
|
return paymentCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Uint8List> signWithNotificationKey(Uint8List data) async {
|
||||||
|
final node = getBip32Root((await mnemonic).join(" "), network)
|
||||||
|
.derivePath("m/47'/0'/0'");
|
||||||
|
final pair =
|
||||||
|
btc_dart.ECPair.fromPrivateKey(node.privateKey!, network: network);
|
||||||
|
final signed = pair.sign(SHA256Digest().process(data));
|
||||||
|
return signed;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> signStringWithNotificationKey(String data) async {
|
||||||
|
final bytes =
|
||||||
|
await signWithNotificationKey(Uint8List.fromList(utf8.encode(data)));
|
||||||
|
return Format.uint8listToString(bytes);
|
||||||
|
// final bytes =
|
||||||
|
// await signWithNotificationKey(Uint8List.fromList(utf8.encode(data)));
|
||||||
|
// return Format.uint8listToString(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update cached lists of notification transaction IDs.
|
||||||
|
/// Returns true if there are new notification transactions found since last
|
||||||
|
/// checked.
|
||||||
|
Future<bool> checkForNotificationTransactions() async {
|
||||||
|
final myPCode = await getPaymentCode();
|
||||||
|
|
||||||
|
final transactionIds = await electrumXClient.getHistory(
|
||||||
|
scripthash: AddressUtils.convertToScriptHash(
|
||||||
|
myPCode.notificationAddress(),
|
||||||
|
network,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final confirmedNotificationTransactionIds = DB.instance.get<dynamic>(
|
||||||
|
boxName: walletId,
|
||||||
|
key: "confirmedNotificationTransactionIds",
|
||||||
|
) as Set? ??
|
||||||
|
{};
|
||||||
|
|
||||||
|
final unconfirmedNotificationTransactionIds = DB.instance.get<dynamic>(
|
||||||
|
boxName: walletId,
|
||||||
|
key: "unconfirmedNotificationTransactionIds",
|
||||||
|
) as Set? ??
|
||||||
|
{};
|
||||||
|
|
||||||
|
// since we are only checking for newly found transactions here we can use the sum
|
||||||
|
final totalCount = confirmedNotificationTransactionIds.length +
|
||||||
|
unconfirmedNotificationTransactionIds.length;
|
||||||
|
|
||||||
|
for (final entry in transactionIds) {
|
||||||
|
final txid = entry["tx_hash"] as String;
|
||||||
|
|
||||||
|
final tx = await cachedElectrumXClient.getTransaction(
|
||||||
|
txHash: txid,
|
||||||
|
coin: coin,
|
||||||
|
);
|
||||||
|
|
||||||
|
// check if tx is confirmed
|
||||||
|
if ((tx["confirmations"] as int? ?? 0) > MINIMUM_CONFIRMATIONS) {
|
||||||
|
// remove it from unconfirmed set
|
||||||
|
unconfirmedNotificationTransactionIds.remove(txid);
|
||||||
|
|
||||||
|
// add it to confirmed set
|
||||||
|
confirmedNotificationTransactionIds.add(txid);
|
||||||
|
} else {
|
||||||
|
// otherwise add it to the unconfirmed set
|
||||||
|
unconfirmedNotificationTransactionIds.add(txid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final newTotalCount = confirmedNotificationTransactionIds.length +
|
||||||
|
unconfirmedNotificationTransactionIds.length;
|
||||||
|
|
||||||
|
return newTotalCount > totalCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return the notification tx sent from my wallet if it exists
|
||||||
|
Future<Transaction?> hasSentNotificationTx(PaymentCode pCode) async {
|
||||||
|
final tx = await db
|
||||||
|
.getTransactions(walletId)
|
||||||
|
.filter()
|
||||||
|
.address((q) => q.valueEqualTo(pCode.notificationAddress()))
|
||||||
|
.findFirst();
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void preparePaymentCodeSend(PaymentCode pCode) async {
|
||||||
|
final notifTx = await hasSentNotificationTx(pCode);
|
||||||
|
final currentHeight = await chainHeight;
|
||||||
|
|
||||||
|
if (notifTx == null) {
|
||||||
|
throw PaynymSendException("No notification transaction sent to $pCode");
|
||||||
|
} else if (!notifTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) {
|
||||||
|
throw PaynymSendException(
|
||||||
|
"Notification transaction sent to $pCode has not confirmed yet");
|
||||||
|
} else {
|
||||||
|
final node = getBip32Root((await mnemonic).join(" "), network)
|
||||||
|
.derivePath("m/47'/0'/0'");
|
||||||
|
final sendToAddress = await nextUnusedSendAddressFrom(
|
||||||
|
pCode,
|
||||||
|
node.derive(0).privateKey!,
|
||||||
|
);
|
||||||
|
|
||||||
|
// todo: Actual transaction build
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get the next unused address to send to given the receiver's payment code
|
||||||
|
/// and your own private key
|
||||||
|
Future<String> nextUnusedSendAddressFrom(
|
||||||
|
PaymentCode pCode,
|
||||||
|
Uint8List privateKey,
|
||||||
|
) async {
|
||||||
|
// https://en.bitcoin.it/wiki/BIP_0047#Path_levels
|
||||||
|
const maxCount = 2147483647;
|
||||||
|
|
||||||
|
final paymentAddress = PaymentAddress.initWithPrivateKey(
|
||||||
|
privateKey,
|
||||||
|
pCode,
|
||||||
|
0, // initial index to check
|
||||||
|
);
|
||||||
|
|
||||||
|
for (paymentAddress.index = 0;
|
||||||
|
paymentAddress.index <= maxCount;
|
||||||
|
paymentAddress.index++) {
|
||||||
|
final address = paymentAddress.getSendAddress();
|
||||||
|
|
||||||
|
final transactionIds = await electrumXClient.getHistory(
|
||||||
|
scripthash: AddressUtils.convertToScriptHash(
|
||||||
|
address,
|
||||||
|
network,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (transactionIds.isEmpty) {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw PaynymSendException("Exhausted unused send addresses!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get your receiving addresses given the sender's payment code and your own
|
||||||
|
/// private key
|
||||||
|
List<String> deriveReceivingAddressesFor(
|
||||||
|
PaymentCode pCode,
|
||||||
|
Uint8List privateKey,
|
||||||
|
int count,
|
||||||
|
) {
|
||||||
|
// https://en.bitcoin.it/wiki/BIP_0047#Path_levels
|
||||||
|
const maxCount = 2147483647;
|
||||||
|
assert(count <= maxCount);
|
||||||
|
|
||||||
|
final paymentAddress = PaymentAddress.initWithPrivateKey(
|
||||||
|
privateKey,
|
||||||
|
pCode,
|
||||||
|
0, // initial index
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<String> result = [];
|
||||||
|
for (paymentAddress.index = 0;
|
||||||
|
paymentAddress.index < count;
|
||||||
|
paymentAddress.index++) {
|
||||||
|
final address = paymentAddress.getReceiveAddress();
|
||||||
|
|
||||||
|
result.add(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> buildNotificationTx({
|
||||||
|
required int selectedTxFeeRate,
|
||||||
|
required String targetPaymentCodeString,
|
||||||
|
int additionalOutputs = 0,
|
||||||
|
List<UTXO>? utxos,
|
||||||
|
}) async {
|
||||||
|
const amountToSend = DUST_LIMIT;
|
||||||
|
final List<UTXO> availableOutputs = utxos ?? await this.utxos;
|
||||||
|
final List<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(await chainHeight, MINIMUM_CONFIRMATIONS) ==
|
||||||
|
true) {
|
||||||
|
spendableOutputs.add(availableOutputs[i]);
|
||||||
|
spendableSatoshiValue += availableOutputs[i].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spendableSatoshiValue < amountToSend) {
|
||||||
|
// insufficient balance
|
||||||
|
throw InsufficientBalanceException(
|
||||||
|
"Spendable balance is less than the minimum required for a notification transaction.");
|
||||||
|
} else if (spendableSatoshiValue == amountToSend) {
|
||||||
|
// insufficient balance due to missing amount to cover fee
|
||||||
|
throw InsufficientBalanceException(
|
||||||
|
"Remaining balance does not cover the network fee.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort spendable by age (oldest first)
|
||||||
|
spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!));
|
||||||
|
|
||||||
|
int satoshisBeingUsed = 0;
|
||||||
|
int outputsBeingUsed = 0;
|
||||||
|
List<UTXO> utxoObjectsToUse = [];
|
||||||
|
|
||||||
|
for (int i = 0;
|
||||||
|
satoshisBeingUsed < amountToSend && i < spendableOutputs.length;
|
||||||
|
i++) {
|
||||||
|
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||||
|
satoshisBeingUsed += spendableOutputs[i].value;
|
||||||
|
outputsBeingUsed += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add additional outputs if required
|
||||||
|
for (int i = 0;
|
||||||
|
i < additionalOutputs && outputsBeingUsed < spendableOutputs.length;
|
||||||
|
i++) {
|
||||||
|
utxoObjectsToUse.add(spendableOutputs[outputsBeingUsed]);
|
||||||
|
satoshisBeingUsed += spendableOutputs[outputsBeingUsed].value;
|
||||||
|
outputsBeingUsed += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// gather required signing data
|
||||||
|
final utxoSigningData = await fetchBuildTxData(utxoObjectsToUse);
|
||||||
|
|
||||||
|
final int vSizeForNoChange = (await _createNotificationTx(
|
||||||
|
targetPaymentCodeString: targetPaymentCodeString,
|
||||||
|
utxosToUse: utxoObjectsToUse,
|
||||||
|
utxoSigningData: utxoSigningData,
|
||||||
|
change: 0))
|
||||||
|
.item2;
|
||||||
|
|
||||||
|
final int vSizeForWithChange = (await _createNotificationTx(
|
||||||
|
targetPaymentCodeString: targetPaymentCodeString,
|
||||||
|
utxosToUse: utxoObjectsToUse,
|
||||||
|
utxoSigningData: utxoSigningData,
|
||||||
|
change: satoshisBeingUsed - amountToSend))
|
||||||
|
.item2;
|
||||||
|
|
||||||
|
// Assume 2 outputs, for recipient and payment code script
|
||||||
|
int feeForNoChange = estimateTxFee(
|
||||||
|
vSize: vSizeForNoChange,
|
||||||
|
feeRatePerKB: selectedTxFeeRate,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assume 3 outputs, for recipient, payment code script, and change
|
||||||
|
int feeForWithChange = estimateTxFee(
|
||||||
|
vSize: vSizeForWithChange,
|
||||||
|
feeRatePerKB: selectedTxFeeRate,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (feeForNoChange < vSizeForNoChange * 1000) {
|
||||||
|
feeForNoChange = vSizeForNoChange * 1000;
|
||||||
|
}
|
||||||
|
if (feeForWithChange < vSizeForWithChange * 1000) {
|
||||||
|
feeForWithChange = vSizeForWithChange * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (satoshisBeingUsed - amountToSend > feeForNoChange + DUST_LIMIT) {
|
||||||
|
// try to add change output due to "left over" amount being greater than
|
||||||
|
// the estimated fee + the dust limit
|
||||||
|
int changeAmount = satoshisBeingUsed - amountToSend - feeForWithChange;
|
||||||
|
|
||||||
|
// check estimates are correct and build notification tx
|
||||||
|
if (changeAmount >= DUST_LIMIT &&
|
||||||
|
satoshisBeingUsed - amountToSend - changeAmount == feeForWithChange) {
|
||||||
|
final txn = await _createNotificationTx(
|
||||||
|
targetPaymentCodeString: targetPaymentCodeString,
|
||||||
|
utxosToUse: utxoObjectsToUse,
|
||||||
|
utxoSigningData: utxoSigningData,
|
||||||
|
change: changeAmount,
|
||||||
|
);
|
||||||
|
|
||||||
|
int feeBeingPaid = satoshisBeingUsed - amountToSend - changeAmount;
|
||||||
|
|
||||||
|
Map<String, dynamic> transactionObject = {
|
||||||
|
"hex": txn.item1,
|
||||||
|
"recipientPaynym": targetPaymentCodeString,
|
||||||
|
"amount": amountToSend,
|
||||||
|
"fee": feeBeingPaid,
|
||||||
|
"vSize": txn.item2,
|
||||||
|
};
|
||||||
|
return transactionObject;
|
||||||
|
} else {
|
||||||
|
// something broke during fee estimation or the change amount is smaller
|
||||||
|
// than the dust limit. Try without change
|
||||||
|
final txn = await _createNotificationTx(
|
||||||
|
targetPaymentCodeString: targetPaymentCodeString,
|
||||||
|
utxosToUse: utxoObjectsToUse,
|
||||||
|
utxoSigningData: utxoSigningData,
|
||||||
|
change: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
int feeBeingPaid = satoshisBeingUsed - amountToSend;
|
||||||
|
|
||||||
|
Map<String, dynamic> transactionObject = {
|
||||||
|
"hex": txn.item1,
|
||||||
|
"recipientPaynym": targetPaymentCodeString,
|
||||||
|
"amount": amountToSend,
|
||||||
|
"fee": feeBeingPaid,
|
||||||
|
"vSize": txn.item2,
|
||||||
|
};
|
||||||
|
return transactionObject;
|
||||||
|
}
|
||||||
|
} else if (satoshisBeingUsed - amountToSend >= feeForNoChange) {
|
||||||
|
// since we already checked if we need to add a change output we can just
|
||||||
|
// build without change here
|
||||||
|
final txn = await _createNotificationTx(
|
||||||
|
targetPaymentCodeString: targetPaymentCodeString,
|
||||||
|
utxosToUse: utxoObjectsToUse,
|
||||||
|
utxoSigningData: utxoSigningData,
|
||||||
|
change: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
int feeBeingPaid = satoshisBeingUsed - amountToSend;
|
||||||
|
|
||||||
|
Map<String, dynamic> transactionObject = {
|
||||||
|
"hex": txn.item1,
|
||||||
|
"recipientPaynym": targetPaymentCodeString,
|
||||||
|
"amount": amountToSend,
|
||||||
|
"fee": feeBeingPaid,
|
||||||
|
"vSize": txn.item2,
|
||||||
|
};
|
||||||
|
return transactionObject;
|
||||||
|
} else {
|
||||||
|
// if we get here we do not have enough funds to cover the tx total so we
|
||||||
|
// check if we have any more available outputs and try again
|
||||||
|
if (spendableOutputs.length > outputsBeingUsed) {
|
||||||
|
return buildNotificationTx(
|
||||||
|
selectedTxFeeRate: selectedTxFeeRate,
|
||||||
|
targetPaymentCodeString: targetPaymentCodeString,
|
||||||
|
additionalOutputs: additionalOutputs + 1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw InsufficientBalanceException(
|
||||||
|
"Remaining balance does not cover the network fee.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return tuple with string value equal to the raw tx hex and the int value
|
||||||
|
// equal to its vSize
|
||||||
|
Future<Tuple2<String, int>> _createNotificationTx({
|
||||||
|
required String targetPaymentCodeString,
|
||||||
|
required List<UTXO> utxosToUse,
|
||||||
|
required Map<String, dynamic> utxoSigningData,
|
||||||
|
required int change,
|
||||||
|
}) async {
|
||||||
|
final targetPaymentCode =
|
||||||
|
PaymentCode.fromPaymentCode(targetPaymentCodeString, network);
|
||||||
|
final myCode = await getPaymentCode();
|
||||||
|
|
||||||
|
final utxo = utxosToUse.first;
|
||||||
|
final txPoint = utxo.txid.fromHex.toList();
|
||||||
|
final txPointIndex = utxo.vout;
|
||||||
|
|
||||||
|
final rev = Uint8List(txPoint.length + 4);
|
||||||
|
Util.copyBytes(Uint8List.fromList(txPoint), 0, rev, 0, txPoint.length);
|
||||||
|
final buffer = rev.buffer.asByteData();
|
||||||
|
buffer.setUint32(txPoint.length, txPointIndex, Endian.little);
|
||||||
|
|
||||||
|
final myKeyPair = utxoSigningData[utxo.txid]["keyPair"] as btc_dart.ECPair;
|
||||||
|
|
||||||
|
final S = SecretPoint(
|
||||||
|
myKeyPair.privateKey!,
|
||||||
|
targetPaymentCode.notificationPublicKey(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final blindingMask = PaymentCode.getMask(S.ecdhSecret(), rev);
|
||||||
|
|
||||||
|
final blindedPaymentCode = PaymentCode.blind(
|
||||||
|
myCode.getPayload(),
|
||||||
|
blindingMask,
|
||||||
|
);
|
||||||
|
|
||||||
|
final opReturnScript = bscript.compile([
|
||||||
|
(op.OPS["OP_RETURN"] as int),
|
||||||
|
blindedPaymentCode,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// build a notification tx
|
||||||
|
final txb = btc_dart.TransactionBuilder(network: network);
|
||||||
|
txb.setVersion(1);
|
||||||
|
|
||||||
|
txb.addInput(
|
||||||
|
utxo.txid,
|
||||||
|
txPointIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
txb.addOutput(targetPaymentCode.notificationAddress(), DUST_LIMIT);
|
||||||
|
txb.addOutput(opReturnScript, 0);
|
||||||
|
|
||||||
|
// TODO: add possible change output and mark output as dangerous
|
||||||
|
if (change > 0) {
|
||||||
|
// generate new change address if current change address has been used
|
||||||
|
await checkChangeAddressForTransactions();
|
||||||
|
final String changeAddress = await currentChangeAddress;
|
||||||
|
txb.addOutput(changeAddress, change);
|
||||||
|
}
|
||||||
|
|
||||||
|
txb.sign(
|
||||||
|
vin: 0,
|
||||||
|
keyPair: myKeyPair,
|
||||||
|
);
|
||||||
|
|
||||||
|
// sign rest of possible inputs
|
||||||
|
for (var i = 1; i < utxosToUse.length - 1; i++) {
|
||||||
|
final txid = utxosToUse[i].txid;
|
||||||
|
txb.sign(
|
||||||
|
vin: i,
|
||||||
|
keyPair: utxoSigningData[txid]["keyPair"] as btc_dart.ECPair,
|
||||||
|
// witnessValue: utxosToUse[i].value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final builtTx = txb.build();
|
||||||
|
|
||||||
|
return Tuple2(builtTx.toHex(), builtTx.virtualSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> confirmNotificationTx(
|
||||||
|
{required Map<String, dynamic> preparedTx}) async {
|
||||||
|
try {
|
||||||
|
Logging.instance.log("confirmNotificationTx txData: $preparedTx",
|
||||||
|
level: LogLevel.Info);
|
||||||
|
final txHash = await electrumXClient.broadcastTransaction(
|
||||||
|
rawTx: preparedTx["hex"] as String);
|
||||||
|
Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info);
|
||||||
|
|
||||||
|
await updatePaynymNotificationInfo(
|
||||||
|
txid: txHash,
|
||||||
|
confirmed: false,
|
||||||
|
paymentCodeString: preparedTx["address"] as String,
|
||||||
|
);
|
||||||
|
return txHash;
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s",
|
||||||
|
level: LogLevel.Error);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Future<bool> hasConfirmedNotificationTxSentTo(
|
||||||
|
// String paymentCodeString) async {
|
||||||
|
// final targetPaymentCode =
|
||||||
|
// PaymentCode.fromPaymentCode(paymentCodeString, network);
|
||||||
|
// final targetNotificationAddress = targetPaymentCode.notificationAddress();
|
||||||
|
//
|
||||||
|
// final myTxHistory = (await transactionData)
|
||||||
|
// .getAllTransactions()
|
||||||
|
// .entries
|
||||||
|
// .map((e) => e.value)
|
||||||
|
// .where((e) =>
|
||||||
|
// e.txType == "Sent" && e.address == targetNotificationAddress);
|
||||||
|
//
|
||||||
|
// return myTxHistory.isNotEmpty;
|
||||||
|
// }
|
||||||
|
|
||||||
|
bool hasConnected(String paymentCodeString) {
|
||||||
|
return getPaynymNotificationTxInfo()
|
||||||
|
.values
|
||||||
|
.where((e) => e["paymentCodeString"] == paymentCodeString)
|
||||||
|
.isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasConnectedConfirmed(String paymentCodeString) {
|
||||||
|
return getPaynymNotificationTxInfo()
|
||||||
|
.values
|
||||||
|
.where((e) =>
|
||||||
|
e["paymentCodeString"] == paymentCodeString &&
|
||||||
|
e["confirmed"] == true)
|
||||||
|
.isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch paynym notification tx meta data
|
||||||
|
Map<String, dynamic> getPaynymNotificationTxInfo() {
|
||||||
|
final map = DB.instance.get<dynamic>(
|
||||||
|
boxName: walletId, key: "paynymNotificationTxInfo") as Map? ??
|
||||||
|
{};
|
||||||
|
|
||||||
|
return Map<String, dynamic>.from(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add/update paynym notification tx meta data entry
|
||||||
|
Future<void> updatePaynymNotificationInfo({
|
||||||
|
required String txid,
|
||||||
|
required bool confirmed,
|
||||||
|
required String paymentCodeString,
|
||||||
|
}) async {
|
||||||
|
final data = getPaynymNotificationTxInfo();
|
||||||
|
data[txid] = {
|
||||||
|
"txid": txid,
|
||||||
|
"confirmed": confirmed,
|
||||||
|
"paymentCodeString": paymentCodeString,
|
||||||
|
};
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: walletId,
|
||||||
|
key: "paynymNotificationTxInfo",
|
||||||
|
value: data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Tuple4<Transaction, List<Output>, List<Input>, Address>>
|
||||||
|
parseTransaction(
|
||||||
|
Map<String, dynamic> txData,
|
||||||
|
dynamic electrumxClient,
|
||||||
|
List<Address> myAddresses,
|
||||||
|
Coin coin,
|
||||||
|
int minConfirms,
|
||||||
|
String walletId,
|
||||||
|
) async {
|
||||||
|
Set<String> receivingAddresses = myAddresses
|
||||||
|
.where((e) => e.subType == AddressSubType.receiving)
|
||||||
|
.map((e) => e.value)
|
||||||
|
.toSet();
|
||||||
|
Set<String> changeAddresses = myAddresses
|
||||||
|
.where((e) => e.subType == AddressSubType.change)
|
||||||
|
.map((e) => e.value)
|
||||||
|
.toSet();
|
||||||
|
|
||||||
|
Set<String> inputAddresses = {};
|
||||||
|
Set<String> outputAddresses = {};
|
||||||
|
|
||||||
|
int totalInputValue = 0;
|
||||||
|
int totalOutputValue = 0;
|
||||||
|
|
||||||
|
int amountSentFromWallet = 0;
|
||||||
|
int amountReceivedInWallet = 0;
|
||||||
|
int changeAmount = 0;
|
||||||
|
|
||||||
|
// parse inputs
|
||||||
|
for (final input in txData["vin"] as List) {
|
||||||
|
final prevTxid = input["txid"] as String;
|
||||||
|
final prevOut = input["vout"] as int;
|
||||||
|
|
||||||
|
// fetch input tx to get address
|
||||||
|
final inputTx = await electrumxClient.getTransaction(
|
||||||
|
txHash: prevTxid,
|
||||||
|
coin: coin,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (final output in inputTx["vout"] as List) {
|
||||||
|
// check matching output
|
||||||
|
if (prevOut == output["n"]) {
|
||||||
|
// get value
|
||||||
|
final value = Format.decimalAmountToSatoshis(
|
||||||
|
Decimal.parse(output["value"].toString()),
|
||||||
|
coin,
|
||||||
|
);
|
||||||
|
|
||||||
|
// add value to total
|
||||||
|
totalInputValue += value;
|
||||||
|
|
||||||
|
// get input(prevOut) address
|
||||||
|
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||||
|
output["scriptPubKey"]?["address"] as String?;
|
||||||
|
|
||||||
|
if (address != null) {
|
||||||
|
inputAddresses.add(address);
|
||||||
|
|
||||||
|
// if input was from my wallet, add value to amount sent
|
||||||
|
if (receivingAddresses.contains(address) ||
|
||||||
|
changeAddresses.contains(address)) {
|
||||||
|
amountSentFromWallet += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse outputs
|
||||||
|
for (final output in txData["vout"] as List) {
|
||||||
|
// get value
|
||||||
|
final value = Format.decimalAmountToSatoshis(
|
||||||
|
Decimal.parse(output["value"].toString()),
|
||||||
|
coin,
|
||||||
|
);
|
||||||
|
|
||||||
|
// add value to total
|
||||||
|
totalOutputValue += value;
|
||||||
|
|
||||||
|
// get output address
|
||||||
|
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||||
|
output["scriptPubKey"]?["address"] as String?;
|
||||||
|
if (address != null) {
|
||||||
|
outputAddresses.add(address);
|
||||||
|
|
||||||
|
// if output was to my wallet, add value to amount received
|
||||||
|
if (receivingAddresses.contains(address)) {
|
||||||
|
amountReceivedInWallet += value;
|
||||||
|
} else if (changeAddresses.contains(address)) {
|
||||||
|
changeAmount += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final mySentFromAddresses = [
|
||||||
|
...receivingAddresses.intersection(inputAddresses),
|
||||||
|
...changeAddresses.intersection(inputAddresses)
|
||||||
|
];
|
||||||
|
final myReceivedOnAddresses =
|
||||||
|
receivingAddresses.intersection(outputAddresses);
|
||||||
|
final myChangeReceivedOnAddresses =
|
||||||
|
changeAddresses.intersection(outputAddresses);
|
||||||
|
|
||||||
|
final fee = totalInputValue - totalOutputValue;
|
||||||
|
|
||||||
|
// this is the address initially used to fetch the txid
|
||||||
|
Address transactionAddress = txData["address"] as Address;
|
||||||
|
|
||||||
|
TransactionType type;
|
||||||
|
int amount;
|
||||||
|
if (mySentFromAddresses.isNotEmpty && myReceivedOnAddresses.isNotEmpty) {
|
||||||
|
// tx is sent to self
|
||||||
|
type = TransactionType.sentToSelf;
|
||||||
|
|
||||||
|
// should be 0
|
||||||
|
amount = amountSentFromWallet - amountReceivedInWallet - fee - changeAmount;
|
||||||
|
} else if (mySentFromAddresses.isNotEmpty) {
|
||||||
|
// outgoing tx
|
||||||
|
type = TransactionType.outgoing;
|
||||||
|
amount = amountSentFromWallet - changeAmount - fee;
|
||||||
|
|
||||||
|
final possible =
|
||||||
|
outputAddresses.difference(myChangeReceivedOnAddresses).first;
|
||||||
|
|
||||||
|
if (transactionAddress.value != possible) {
|
||||||
|
transactionAddress = Address(
|
||||||
|
walletId: walletId,
|
||||||
|
value: possible,
|
||||||
|
derivationIndex: -1,
|
||||||
|
subType: AddressSubType.nonWallet,
|
||||||
|
type: AddressType.nonWallet,
|
||||||
|
publicKey: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// incoming tx
|
||||||
|
type = TransactionType.incoming;
|
||||||
|
amount = amountReceivedInWallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
final tx = Transaction(
|
||||||
|
walletId: walletId,
|
||||||
|
txid: txData["txid"] as String,
|
||||||
|
timestamp: txData["blocktime"] as int? ??
|
||||||
|
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||||
|
type: type,
|
||||||
|
subType: TransactionSubType.none,
|
||||||
|
amount: amount,
|
||||||
|
fee: fee,
|
||||||
|
height: txData["height"] as int?,
|
||||||
|
isCancelled: false,
|
||||||
|
isLelantus: false,
|
||||||
|
slateId: null,
|
||||||
|
otherData: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Output> outs = [];
|
||||||
|
List<Input> ins = [];
|
||||||
|
|
||||||
|
for (final json in txData["vin"] as List) {
|
||||||
|
bool isCoinBase = json['coinbase'] != null;
|
||||||
|
final input = Input(
|
||||||
|
walletId: walletId,
|
||||||
|
txid: json['txid'] as String,
|
||||||
|
vout: json['vout'] as int? ?? -1,
|
||||||
|
scriptSig: json['scriptSig']?['hex'] as String?,
|
||||||
|
scriptSigAsm: json['scriptSig']?['asm'] as String?,
|
||||||
|
isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?,
|
||||||
|
sequence: json['sequence'] as int?,
|
||||||
|
innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?,
|
||||||
|
);
|
||||||
|
ins.add(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final json in txData["vout"] as List) {
|
||||||
|
final output = Output(
|
||||||
|
walletId: walletId,
|
||||||
|
scriptPubKey: json['scriptPubKey']?['hex'] as String?,
|
||||||
|
scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?,
|
||||||
|
scriptPubKeyType: json['scriptPubKey']?['type'] as String?,
|
||||||
|
scriptPubKeyAddress: json["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||||
|
json['scriptPubKey']['type'] as String,
|
||||||
|
value: Format.decimalAmountToSatoshis(
|
||||||
|
Decimal.parse(json["value"].toString()),
|
||||||
|
coin,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
outs.add(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Tuple4(tx, outs, ins, transactionAddress);
|
||||||
|
}
|
|
@ -1,13 +1,15 @@
|
||||||
import 'package:decimal/decimal.dart';
|
|
||||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
||||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||||
import 'package:stackwallet/models/models.dart';
|
import 'package:stackwallet/models/balance.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
||||||
import 'package:stackwallet/models/node_model.dart';
|
import 'package:stackwallet/models/node_model.dart';
|
||||||
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart';
|
import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
|
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||||
|
import 'package:stackwallet/services/coins/litecoin/litecoin_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/monero/monero_wallet.dart';
|
import 'package:stackwallet/services/coins/monero/monero_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
|
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/particl/particl_wallet.dart';
|
import 'package:stackwallet/services/coins/particl/particl_wallet.dart';
|
||||||
|
@ -17,8 +19,6 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||||
import 'package:stackwallet/utilities/prefs.dart';
|
import 'package:stackwallet/utilities/prefs.dart';
|
||||||
|
|
||||||
import 'litecoin/litecoin_wallet.dart';
|
|
||||||
|
|
||||||
abstract class CoinServiceAPI {
|
abstract class CoinServiceAPI {
|
||||||
CoinServiceAPI();
|
CoinServiceAPI();
|
||||||
|
|
||||||
|
@ -240,30 +240,15 @@ abstract class CoinServiceAPI {
|
||||||
|
|
||||||
Future<String> confirmSend({required Map<String, dynamic> txData});
|
Future<String> confirmSend({required Map<String, dynamic> txData});
|
||||||
|
|
||||||
/// create and submit tx to network
|
|
||||||
///
|
|
||||||
/// Returns the txid of the sent tx
|
|
||||||
/// will throw exceptions on failure
|
|
||||||
Future<String> send(
|
|
||||||
{required String toAddress,
|
|
||||||
required int amount,
|
|
||||||
Map<String, String> args});
|
|
||||||
|
|
||||||
Future<FeeObject> get fees;
|
Future<FeeObject> get fees;
|
||||||
Future<int> get maxFee;
|
Future<int> get maxFee;
|
||||||
|
|
||||||
Future<String> get currentReceivingAddress;
|
Future<String> get currentReceivingAddress;
|
||||||
// Future<String> get currentLegacyReceivingAddress;
|
|
||||||
|
|
||||||
Future<Decimal> get availableBalance;
|
Balance get balance;
|
||||||
Future<Decimal> get pendingBalance;
|
|
||||||
Future<Decimal> get totalBalance;
|
|
||||||
Future<Decimal> get balanceMinusMaxFee;
|
|
||||||
|
|
||||||
Future<List<String>> get allOwnAddresses;
|
Future<List<isar_models.Transaction>> get transactions;
|
||||||
|
Future<List<isar_models.UTXO>> get utxos;
|
||||||
Future<TransactionData> get transactionData;
|
|
||||||
Future<List<UtxoObject>> get unspentOutputs;
|
|
||||||
|
|
||||||
Future<void> refresh();
|
Future<void> refresh();
|
||||||
|
|
||||||
|
@ -308,6 +293,8 @@ abstract class CoinServiceAPI {
|
||||||
// used for electrumx coins
|
// used for electrumx coins
|
||||||
Future<void> updateSentCachedTxData(Map<String, dynamic> txData);
|
Future<void> updateSentCachedTxData(Map<String, dynamic> txData);
|
||||||
|
|
||||||
|
int get storedChainHeight;
|
||||||
|
|
||||||
// Certain outputs return address as an array/list of strings like List<String> ["addresses"][0], some return it as a string like String ["address"]
|
// Certain outputs return address as an array/list of strings like List<String> ["addresses"][0], some return it as a string like String ["address"]
|
||||||
String? getAddress(dynamic output) {
|
String? getAddress(dynamic output) {
|
||||||
// Julian's code from https://github.com/cypherstack/stack_wallet/blob/35a8172d35f1b5cdbd22f0d56c4db02f795fd032/lib/services/coins/coin_paynym_extension.dart#L170 wins codegolf for this, I'd love to commit it now but need to retest this section ... should make unit tests for this case
|
// Julian's code from https://github.com/cypherstack/stack_wallet/blob/35a8172d35f1b5cdbd22f0d56c4db02f795fd032/lib/services/coins/coin_paynym_extension.dart#L170 wins codegolf for this, I'd love to commit it now but need to retest this section ... should make unit tests for this case
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,9 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:decimal/decimal.dart';
|
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stackwallet/models/balance.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
||||||
import 'package:stackwallet/models/models.dart';
|
import 'package:stackwallet/models/models.dart';
|
||||||
import 'package:stackwallet/services/coins/coin_service.dart';
|
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/node_connection_status_changed_event.dart';
|
||||||
|
@ -59,7 +60,6 @@ class Manager with ChangeNotifier {
|
||||||
Future<void> updateNode(bool shouldRefresh) async {
|
Future<void> updateNode(bool shouldRefresh) async {
|
||||||
await _currentWallet.updateNode(shouldRefresh);
|
await _currentWallet.updateNode(shouldRefresh);
|
||||||
}
|
}
|
||||||
// Function(bool isActive)? onIsActiveWalletChanged;
|
|
||||||
|
|
||||||
CoinServiceAPI get wallet => _currentWallet;
|
CoinServiceAPI get wallet => _currentWallet;
|
||||||
|
|
||||||
|
@ -120,73 +120,17 @@ class Manager with ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// create and submit tx to network
|
|
||||||
///
|
|
||||||
/// Returns the txid of the sent tx
|
|
||||||
/// will throw exceptions on failure
|
|
||||||
Future<String> send({
|
|
||||||
required String toAddress,
|
|
||||||
required int amount,
|
|
||||||
Map<String, String> args = const {},
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
final txid = await _currentWallet.send(
|
|
||||||
toAddress: toAddress,
|
|
||||||
amount: amount,
|
|
||||||
args: args,
|
|
||||||
);
|
|
||||||
notifyListeners();
|
|
||||||
return txid;
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log("$e\n $s", level: LogLevel.Error);
|
|
||||||
// rethrow to pass error in alert
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<FeeObject> get fees => _currentWallet.fees;
|
Future<FeeObject> get fees => _currentWallet.fees;
|
||||||
Future<int> get maxFee => _currentWallet.maxFee;
|
Future<int> get maxFee => _currentWallet.maxFee;
|
||||||
|
|
||||||
Future<String> get currentReceivingAddress =>
|
Future<String> get currentReceivingAddress =>
|
||||||
_currentWallet.currentReceivingAddress;
|
_currentWallet.currentReceivingAddress;
|
||||||
// Future<String> get currentLegacyReceivingAddress =>
|
|
||||||
// _currentWallet.currentLegacyReceivingAddress;
|
|
||||||
|
|
||||||
Future<Decimal> get availableBalance async {
|
Balance get balance => _currentWallet.balance;
|
||||||
_cachedAvailableBalance = await _currentWallet.availableBalance;
|
|
||||||
return _cachedAvailableBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Decimal _cachedAvailableBalance = Decimal.zero;
|
Future<List<isar_models.Transaction>> get transactions =>
|
||||||
Decimal get cachedAvailableBalance => _cachedAvailableBalance;
|
_currentWallet.transactions;
|
||||||
|
Future<List<isar_models.UTXO>> get utxos => _currentWallet.utxos;
|
||||||
Future<Decimal> get pendingBalance => _currentWallet.pendingBalance;
|
|
||||||
Future<Decimal> get balanceMinusMaxFee => _currentWallet.balanceMinusMaxFee;
|
|
||||||
|
|
||||||
Future<Decimal> get totalBalance async {
|
|
||||||
_cachedTotalBalance = await _currentWallet.totalBalance;
|
|
||||||
return _cachedTotalBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Decimal _cachedTotalBalance = Decimal.zero;
|
|
||||||
Decimal get cachedTotalBalance => _cachedTotalBalance;
|
|
||||||
|
|
||||||
// Future<Decimal> get fiatBalance async {
|
|
||||||
// final balance = await _currentWallet.availableBalance;
|
|
||||||
// final price = await _currentWallet.basePrice;
|
|
||||||
// return balance * price;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Future<Decimal> get fiatTotalBalance async {
|
|
||||||
// final balance = await _currentWallet.totalBalance;
|
|
||||||
// final price = await _currentWallet.basePrice;
|
|
||||||
// return balance * price;
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<List<String>> get allOwnAddresses => _currentWallet.allOwnAddresses;
|
|
||||||
|
|
||||||
Future<TransactionData> get transactionData => _currentWallet.transactionData;
|
|
||||||
Future<List<UtxoObject>> get unspentOutputs => _currentWallet.unspentOutputs;
|
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
await _currentWallet.refresh();
|
await _currentWallet.refresh();
|
||||||
|
@ -233,11 +177,6 @@ class Manager with ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<bool> initializeWallet() async {
|
|
||||||
// final success = await _currentWallet.initializeWallet();
|
|
||||||
// return success;
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<void> exitCurrentWallet() async {
|
Future<void> exitCurrentWallet() async {
|
||||||
final name = _currentWallet.walletName;
|
final name = _currentWallet.walletName;
|
||||||
final id = _currentWallet.walletId;
|
final id = _currentWallet.walletId;
|
||||||
|
@ -260,11 +199,6 @@ class Manager with ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isOwnAddress(String address) async {
|
|
||||||
final allOwnAddresses = await this.allOwnAddresses;
|
|
||||||
return allOwnAddresses.contains(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isConnected => _currentWallet.isConnected;
|
bool get isConnected => _currentWallet.isConnected;
|
||||||
|
|
||||||
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
|
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
|
||||||
|
@ -278,4 +212,6 @@ class Manager with ChangeNotifier {
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get currentHeight => _currentWallet.storedChainHeight;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,21 +21,23 @@ import 'package:flutter_libmonero/core/key_service.dart';
|
||||||
import 'package:flutter_libmonero/core/wallet_creation_service.dart';
|
import 'package:flutter_libmonero/core/wallet_creation_service.dart';
|
||||||
import 'package:flutter_libmonero/monero/monero.dart';
|
import 'package:flutter_libmonero/monero/monero.dart';
|
||||||
import 'package:flutter_libmonero/view_model/send/output.dart' as monero_output;
|
import 'package:flutter_libmonero/view_model/send/output.dart' as monero_output;
|
||||||
import 'package:http/http.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
|
import 'package:stackwallet/db/main_db.dart';
|
||||||
import 'package:stackwallet/hive/db.dart';
|
import 'package:stackwallet/hive/db.dart';
|
||||||
|
import 'package:stackwallet/models/balance.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
||||||
import 'package:stackwallet/models/node_model.dart';
|
import 'package:stackwallet/models/node_model.dart';
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
|
||||||
import 'package:stackwallet/models/paymint/utxo_model.dart';
|
|
||||||
import 'package:stackwallet/services/coins/coin_service.dart';
|
import 'package:stackwallet/services/coins/coin_service.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart';
|
import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
|
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
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/events/global/wallet_sync_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||||
|
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||||
|
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||||
import 'package:stackwallet/services/node_service.dart';
|
import 'package:stackwallet/services/node_service.dart';
|
||||||
import 'package:stackwallet/services/price.dart';
|
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
@ -45,17 +47,18 @@ import 'package:stackwallet/utilities/format.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/prefs.dart';
|
import 'package:stackwallet/utilities/prefs.dart';
|
||||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
const int MINIMUM_CONFIRMATIONS = 10;
|
const int MINIMUM_CONFIRMATIONS = 10;
|
||||||
|
|
||||||
class MoneroWallet extends CoinServiceAPI {
|
class MoneroWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||||
final String _walletId;
|
late final String _walletId;
|
||||||
final Coin _coin;
|
late final Coin _coin;
|
||||||
final PriceAPI _priceAPI;
|
late final SecureStorageInterface _secureStorage;
|
||||||
final SecureStorageInterface _secureStorage;
|
late final Prefs _prefs;
|
||||||
final Prefs _prefs;
|
|
||||||
|
late String _walletName;
|
||||||
|
|
||||||
String _walletName;
|
|
||||||
bool _shouldAutoSync = false;
|
bool _shouldAutoSync = false;
|
||||||
bool _isConnected = false;
|
bool _isConnected = false;
|
||||||
bool _hasCalledExit = false;
|
bool _hasCalledExit = false;
|
||||||
|
@ -68,9 +71,9 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
WalletCreationService? _walletCreationService;
|
WalletCreationService? _walletCreationService;
|
||||||
Timer? _autoSaveTimer;
|
Timer? _autoSaveTimer;
|
||||||
|
|
||||||
Future<String>? _currentReceivingAddress;
|
Future<isar_models.Address?> get _currentReceivingAddress =>
|
||||||
|
db.getAddresses(walletId).sortByDerivationIndexDesc().findFirst();
|
||||||
Future<FeeObject>? _feeObject;
|
Future<FeeObject>? _feeObject;
|
||||||
Future<TransactionData>? _transactionData;
|
|
||||||
|
|
||||||
Mutex prepareSendMutex = Mutex();
|
Mutex prepareSendMutex = Mutex();
|
||||||
Mutex estimateFeeMutex = Mutex();
|
Mutex estimateFeeMutex = Mutex();
|
||||||
|
@ -80,34 +83,29 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
required String walletName,
|
required String walletName,
|
||||||
required Coin coin,
|
required Coin coin,
|
||||||
required SecureStorageInterface secureStorage,
|
required SecureStorageInterface secureStorage,
|
||||||
PriceAPI? priceAPI,
|
|
||||||
Prefs? prefs,
|
Prefs? prefs,
|
||||||
}) : _walletId = walletId,
|
MainDB? mockableOverride,
|
||||||
_walletName = walletName,
|
}) {
|
||||||
_coin = coin,
|
_walletId = walletId;
|
||||||
_priceAPI = priceAPI ?? PriceAPI(Client()),
|
_walletName = walletName;
|
||||||
_secureStorage = secureStorage,
|
_coin = coin;
|
||||||
|
_secureStorage = secureStorage;
|
||||||
_prefs = prefs ?? Prefs.instance;
|
_prefs = prefs ?? Prefs.instance;
|
||||||
|
initCache(walletId, coin);
|
||||||
@override
|
isarInit(mockableOverride: mockableOverride);
|
||||||
bool get isFavorite {
|
|
||||||
try {
|
|
||||||
return DB.instance.get<dynamic>(boxName: walletId, key: "isFavorite")
|
|
||||||
as bool;
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"isFavorite fetch failed (returning false by default): $e\n$s",
|
|
||||||
level: LogLevel.Error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
set isFavorite(bool markFavorite) {
|
set isFavorite(bool markFavorite) {
|
||||||
DB.instance.put<dynamic>(
|
_isFavorite = markFavorite;
|
||||||
boxName: walletId, key: "isFavorite", value: markFavorite);
|
updateCachedIsFavorite(markFavorite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isFavorite => _isFavorite ??= getCachedIsFavorite();
|
||||||
|
|
||||||
|
bool? _isFavorite;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get shouldAutoSync => _shouldAutoSync;
|
bool get shouldAutoSync => _shouldAutoSync;
|
||||||
|
|
||||||
|
@ -139,23 +137,6 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
@override
|
@override
|
||||||
set walletName(String newName) => _walletName = newName;
|
set walletName(String newName) => _walletName = newName;
|
||||||
|
|
||||||
@override
|
|
||||||
// not used for monero
|
|
||||||
Future<List<String>> get allOwnAddresses async => [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Decimal> get availableBalance async {
|
|
||||||
int runningBalance = 0;
|
|
||||||
for (final entry in walletBase!.balance!.entries) {
|
|
||||||
runningBalance += entry.value.unlockedBalance;
|
|
||||||
}
|
|
||||||
return Format.satoshisToAmount(runningBalance, coin: coin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
// not used
|
|
||||||
Future<Decimal> get balanceMinusMaxFee => throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Coin get coin => _coin;
|
Coin get coin => _coin;
|
||||||
|
|
||||||
|
@ -184,8 +165,9 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> get currentReceivingAddress =>
|
Future<String> get currentReceivingAddress async =>
|
||||||
_currentReceivingAddress ??= _getCurrentAddressForChain(0);
|
(await _currentReceivingAddress)?.value ??
|
||||||
|
(await _generateAddressForChain(0, 0)).value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
|
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
|
||||||
|
@ -236,30 +218,30 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
int maxUnusedAddressGap,
|
int maxUnusedAddressGap,
|
||||||
int maxNumberOfIndexesToCheck,
|
int maxNumberOfIndexesToCheck,
|
||||||
) async {
|
) async {
|
||||||
|
// clear blockchain info
|
||||||
|
await db.deleteWalletBlockchainData(walletId);
|
||||||
|
|
||||||
var restoreHeight = walletBase?.walletInfo.restoreHeight;
|
var restoreHeight = walletBase?.walletInfo.restoreHeight;
|
||||||
highestPercentCached = 0;
|
highestPercentCached = 0;
|
||||||
await walletBase?.rescan(height: restoreHeight);
|
await walletBase?.rescan(height: restoreHeight);
|
||||||
|
await refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> generateNewAddress() async {
|
Future<bool> generateNewAddress() async {
|
||||||
try {
|
try {
|
||||||
const String indexKey = "receivingIndex";
|
final currentReceiving = await _currentReceivingAddress;
|
||||||
// First increment the receiving index
|
|
||||||
await _incrementAddressIndexForChain(0);
|
final newReceivingIndex = currentReceiving!.derivationIndex + 1;
|
||||||
final newReceivingIndex =
|
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
|
|
||||||
|
|
||||||
// Use new index to derive a new receiving address
|
// Use new index to derive a new receiving address
|
||||||
final newReceivingAddress =
|
final newReceivingAddress = await _generateAddressForChain(
|
||||||
await _generateAddressForChain(0, newReceivingIndex);
|
0,
|
||||||
|
newReceivingIndex,
|
||||||
|
);
|
||||||
|
|
||||||
// Add that new receiving address to the array of receiving addresses
|
// Add that new receiving address
|
||||||
await _addToAddressesArrayForChain(newReceivingAddress, 0);
|
await db.putAddress(newReceivingAddress);
|
||||||
|
|
||||||
// Set the new receiving address that the service
|
|
||||||
|
|
||||||
_currentReceivingAddress = Future(() => newReceivingAddress);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
@ -280,7 +262,7 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
level: LogLevel.Info,
|
level: LogLevel.Info,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ((DB.instance.get<dynamic>(boxName: walletId, key: "id")) == null) {
|
if (getCachedId() == null) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"Attempted to initialize an existing wallet using an unknown wallet ID!");
|
"Attempted to initialize an existing wallet using an unknown wallet ID!");
|
||||||
}
|
}
|
||||||
|
@ -290,6 +272,7 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
keysStorage = KeyService(_secureStorage);
|
keysStorage = KeyService(_secureStorage);
|
||||||
|
|
||||||
await _prefs.init();
|
await _prefs.init();
|
||||||
|
|
||||||
// final data =
|
// final data =
|
||||||
// DB.instance.get<dynamic>(boxName: walletId, key: "latest_tx_model")
|
// DB.instance.get<dynamic>(boxName: walletId, key: "latest_tx_model")
|
||||||
// as TransactionData?;
|
// as TransactionData?;
|
||||||
|
@ -314,14 +297,14 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
);
|
);
|
||||||
// Wallet already exists, triggers for a returning user
|
// Wallet already exists, triggers for a returning user
|
||||||
|
|
||||||
String indexKey = "receivingIndex";
|
// String indexKey = "receivingIndex";
|
||||||
final curIndex =
|
// final curIndex =
|
||||||
await DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
|
// await DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
|
||||||
// Use new index to derive a new receiving address
|
// // Use new index to derive a new receiving address
|
||||||
final newReceivingAddress = await _generateAddressForChain(0, curIndex);
|
// final newReceivingAddress = await _generateAddressForChain(0, curIndex);
|
||||||
Logging.instance.log("xmr address in init existing: $newReceivingAddress",
|
// Logging.instance.log("xmr address in init existing: $newReceivingAddress",
|
||||||
level: LogLevel.Info);
|
// level: LogLevel.Info);
|
||||||
_currentReceivingAddress = Future(() => newReceivingAddress);
|
// _currentReceivingAddress = Future(() => newReceivingAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -398,47 +381,20 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
final node = await _getCurrentNode();
|
final node = await _getCurrentNode();
|
||||||
final host = Uri.parse(node.host).host;
|
final host = Uri.parse(node.host).host;
|
||||||
await walletBase!.connectToNode(
|
await walletBase!.connectToNode(
|
||||||
node: Node(
|
node: Node(uri: "$host:${node.port}", type: WalletType.monero));
|
||||||
uri: "$host:${node.port}",
|
|
||||||
type: WalletType.monero,
|
|
||||||
trusted: node.trusted ?? false));
|
|
||||||
await walletBase!.startSync();
|
await walletBase!.startSync();
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
|
||||||
|
|
||||||
// Set relevant indexes
|
await Future.wait([
|
||||||
await DB.instance
|
updateCachedId(walletId),
|
||||||
.put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0);
|
updateCachedIsFavorite(false),
|
||||||
await DB.instance
|
]);
|
||||||
.put<dynamic>(boxName: walletId, key: "changeIndex", value: 0);
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'blocked_tx_hashes',
|
|
||||||
value: ["0xdefault"],
|
|
||||||
); // A list of transaction hashes to represent frozen utxos in wallet
|
|
||||||
// initialize address book entries
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'addressBookEntries',
|
|
||||||
value: <String, String>{});
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "isFavorite", value: false);
|
|
||||||
|
|
||||||
// Generate and add addresses to relevant arrays
|
// Generate and add addresses to relevant arrays
|
||||||
final initialReceivingAddress = await _generateAddressForChain(0, 0);
|
final initialReceivingAddress = await _generateAddressForChain(0, 0);
|
||||||
// final initialChangeAddress = await _generateAddressForChain(1, 0);
|
// final initialChangeAddress = await _generateAddressForChain(1, 0);
|
||||||
|
|
||||||
await _addToAddressesArrayForChain(initialReceivingAddress, 0);
|
await db.putAddress(initialReceivingAddress);
|
||||||
// await _addToAddressesArrayForChain(initialChangeAddress, 1);
|
|
||||||
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'receivingAddresses',
|
|
||||||
value: [initialReceivingAddress]);
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0);
|
|
||||||
|
|
||||||
_currentReceivingAddress = Future(() => initialReceivingAddress);
|
|
||||||
walletBase?.close();
|
walletBase?.close();
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("initializeNew for $walletName $walletId", level: LogLevel.Info);
|
.log("initializeNew for $walletName $walletId", level: LogLevel.Info);
|
||||||
|
@ -465,10 +421,6 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
// not used in xmr
|
|
||||||
Future<Decimal> get pendingBalance => throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>> prepareSend({
|
Future<Map<String, dynamic>> prepareSend({
|
||||||
required String address,
|
required String address,
|
||||||
|
@ -496,15 +448,14 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
try {
|
try {
|
||||||
// check for send all
|
// check for send all
|
||||||
bool isSendAll = false;
|
bool isSendAll = false;
|
||||||
final balance = await availableBalance;
|
final balance = await _availableBalance;
|
||||||
final satInDecimal =
|
if (satoshiAmount == balance) {
|
||||||
Format.satoshisToAmount(satoshiAmount, coin: coin);
|
|
||||||
if (satInDecimal == balance) {
|
|
||||||
isSendAll = true;
|
isSendAll = true;
|
||||||
}
|
}
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("$toAddress $satoshiAmount $args", level: LogLevel.Info);
|
.log("$toAddress $satoshiAmount $args", level: LogLevel.Info);
|
||||||
String amountToSend = satInDecimal
|
String amountToSend =
|
||||||
|
Format.satoshisToAmount(satoshiAmount, coin: coin)
|
||||||
.toStringAsFixed(Constants.decimalPlacesForCoin(coin));
|
.toStringAsFixed(Constants.decimalPlacesForCoin(coin));
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("$satoshiAmount $amountToSend", level: LogLevel.Info);
|
.log("$satoshiAmount $amountToSend", level: LogLevel.Info);
|
||||||
|
@ -640,28 +591,11 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
// walletBase!.onNewBlock = onNewBlock;
|
// walletBase!.onNewBlock = onNewBlock;
|
||||||
// walletBase!.onNewTransaction = onNewTransaction;
|
// walletBase!.onNewTransaction = onNewTransaction;
|
||||||
// walletBase!.syncStatusChanged = syncStatusChanged;
|
// walletBase!.syncStatusChanged = syncStatusChanged;
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
await Future.wait([
|
||||||
key: 'receivingAddresses',
|
updateCachedId(walletId),
|
||||||
value: [walletInfo.address!]);
|
updateCachedIsFavorite(false),
|
||||||
await DB.instance
|
]);
|
||||||
.put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0);
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "changeIndex", value: 0);
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'blocked_tx_hashes',
|
|
||||||
value: ["0xdefault"],
|
|
||||||
); // A list of transaction hashes to represent frozen utxos in wallet
|
|
||||||
// initialize address book entries
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'addressBookEntries',
|
|
||||||
value: <String, String>{});
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "isFavorite", value: false);
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
debugPrint(e.toString());
|
debugPrint(e.toString());
|
||||||
debugPrint(s.toString());
|
debugPrint(s.toString());
|
||||||
|
@ -669,10 +603,7 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
final node = await _getCurrentNode();
|
final node = await _getCurrentNode();
|
||||||
final host = Uri.parse(node.host).host;
|
final host = Uri.parse(node.host).host;
|
||||||
await walletBase!.connectToNode(
|
await walletBase!.connectToNode(
|
||||||
node: Node(
|
node: Node(uri: "$host:${node.port}", type: WalletType.monero));
|
||||||
uri: "$host:${node.port}",
|
|
||||||
type: WalletType.monero,
|
|
||||||
trusted: node.trusted ?? false));
|
|
||||||
await walletBase!.rescan(height: credentials.height);
|
await walletBase!.rescan(height: credentials.height);
|
||||||
walletBase!.close();
|
walletBase!.close();
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
@ -708,22 +639,10 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final newTxData = await _fetchTransactionData();
|
await _refreshTransactions();
|
||||||
_transactionData = Future(() => newTxData);
|
await _updateBalance();
|
||||||
|
|
||||||
await _checkCurrentReceivingAddressesForTransactions();
|
await _checkCurrentReceivingAddressesForTransactions();
|
||||||
String indexKey = "receivingIndex";
|
|
||||||
final curIndex =
|
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
|
|
||||||
// Use new index to derive a new receiving address
|
|
||||||
try {
|
|
||||||
final newReceivingAddress = await _generateAddressForChain(0, curIndex);
|
|
||||||
_currentReceivingAddress = Future(() => newReceivingAddress);
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to call _generateAddressForChain(0, $curIndex): $e\n$s",
|
|
||||||
level: LogLevel.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (walletBase?.syncStatus is SyncedSyncStatus) {
|
if (walletBase?.syncStatus is SyncedSyncStatus) {
|
||||||
refreshMutex = false;
|
refreshMutex = false;
|
||||||
|
@ -737,16 +656,6 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> send({
|
|
||||||
required String toAddress,
|
|
||||||
required int amount,
|
|
||||||
Map<String, String> args = const {},
|
|
||||||
}) {
|
|
||||||
// not used for xmr
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> testNetworkConnection() async {
|
Future<bool> testNetworkConnection() async {
|
||||||
return await walletBase?.isConnected() ?? false;
|
return await walletBase?.isConnected() ?? false;
|
||||||
|
@ -781,10 +690,7 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
final node = await _getCurrentNode();
|
final node = await _getCurrentNode();
|
||||||
final host = Uri.parse(node.host).host;
|
final host = Uri.parse(node.host).host;
|
||||||
await walletBase?.connectToNode(
|
await walletBase?.connectToNode(
|
||||||
node: Node(
|
node: Node(uri: "$host:${node.port}", type: WalletType.monero));
|
||||||
uri: "$host:${node.port}",
|
|
||||||
type: WalletType.monero,
|
|
||||||
trusted: node.trusted ?? false));
|
|
||||||
}
|
}
|
||||||
await walletBase?.startSync();
|
await walletBase?.startSync();
|
||||||
await refresh();
|
await refresh();
|
||||||
|
@ -816,8 +722,32 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
) as int? ??
|
) as int? ??
|
||||||
0;
|
0;
|
||||||
|
|
||||||
@override
|
Future<void> _updateBalance() async {
|
||||||
Future<Decimal> get totalBalance async {
|
final total = await _totalBalance;
|
||||||
|
final available = await _availableBalance;
|
||||||
|
_balance = Balance(
|
||||||
|
coin: coin,
|
||||||
|
total: total,
|
||||||
|
spendable: available,
|
||||||
|
blockedTotal: 0,
|
||||||
|
pendingSpendable: total - available,
|
||||||
|
);
|
||||||
|
await updateCachedBalance(_balance!);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> get _availableBalance async {
|
||||||
|
try {
|
||||||
|
int runningBalance = 0;
|
||||||
|
for (final entry in walletBase!.balance!.entries) {
|
||||||
|
runningBalance += entry.value.unlockedBalance;
|
||||||
|
}
|
||||||
|
return runningBalance;
|
||||||
|
} catch (_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> get _totalBalance async {
|
||||||
try {
|
try {
|
||||||
final balanceEntries = walletBase?.balance?.entries;
|
final balanceEntries = walletBase?.balance?.entries;
|
||||||
if (balanceEntries != null) {
|
if (balanceEntries != null) {
|
||||||
|
@ -826,7 +756,7 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
bal = bal + element.value.fullBalance;
|
bal = bal + element.value.fullBalance;
|
||||||
}
|
}
|
||||||
await _updateCachedBalance(bal);
|
await _updateCachedBalance(bal);
|
||||||
return Format.satoshisToAmount(bal, coin: coin);
|
return bal;
|
||||||
} else {
|
} else {
|
||||||
final transactions = walletBase!.transactionHistory!.transactions;
|
final transactions = walletBase!.transactionHistory!.transactions;
|
||||||
int transactionBalance = 0;
|
int transactionBalance = 0;
|
||||||
|
@ -839,31 +769,20 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
await _updateCachedBalance(transactionBalance);
|
await _updateCachedBalance(transactionBalance);
|
||||||
return Format.satoshisToAmount(transactionBalance, coin: coin);
|
return transactionBalance;
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return Format.satoshisToAmount(_getCachedBalance(), coin: coin);
|
return _getCachedBalance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<TransactionData> get transactionData =>
|
|
||||||
_transactionData ??= _fetchTransactionData();
|
|
||||||
|
|
||||||
@override
|
|
||||||
// not used for xmr
|
|
||||||
Future<List<UtxoObject>> get unspentOutputs => throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateNode(bool shouldRefresh) async {
|
Future<void> updateNode(bool shouldRefresh) async {
|
||||||
final node = await _getCurrentNode();
|
final node = await _getCurrentNode();
|
||||||
|
|
||||||
final host = Uri.parse(node.host).host;
|
final host = Uri.parse(node.host).host;
|
||||||
await walletBase?.connectToNode(
|
await walletBase?.connectToNode(
|
||||||
node: Node(
|
node: Node(uri: "$host:${node.port}", type: WalletType.monero));
|
||||||
uri: "$host:${node.port}",
|
|
||||||
type: WalletType.monero,
|
|
||||||
trusted: node.trusted ?? false));
|
|
||||||
|
|
||||||
// TODO: is this sync call needed? Do we need to notify ui here?
|
// TODO: is this sync call needed? Do we need to notify ui here?
|
||||||
await walletBase?.startSync();
|
await walletBase?.startSync();
|
||||||
|
@ -885,66 +804,23 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
@override
|
@override
|
||||||
String get walletId => _walletId;
|
String get walletId => _walletId;
|
||||||
|
|
||||||
/// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain]
|
Future<isar_models.Address> _generateAddressForChain(
|
||||||
/// and
|
int chain,
|
||||||
/// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value!
|
int index,
|
||||||
Future<String> _getCurrentAddressForChain(int chain) async {
|
) async {
|
||||||
// Here, we assume that chain == 1 if it isn't 0
|
|
||||||
String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses";
|
|
||||||
final internalChainArray = (DB.instance
|
|
||||||
.get<dynamic>(boxName: walletId, key: arrayKey)) as List<dynamic>;
|
|
||||||
return internalChainArray.last as String;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Increases the index for either the internal or external chain, depending on [chain].
|
|
||||||
/// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value!
|
|
||||||
Future<void> _incrementAddressIndexForChain(int chain) async {
|
|
||||||
// Here we assume chain == 1 if it isn't 0
|
|
||||||
String indexKey = chain == 0 ? "receivingIndex" : "changeIndex";
|
|
||||||
|
|
||||||
final newIndex =
|
|
||||||
(DB.instance.get<dynamic>(boxName: walletId, key: indexKey)) + 1;
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: indexKey, value: newIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> _generateAddressForChain(int chain, int index) async {
|
|
||||||
//
|
//
|
||||||
String address = walletBase!.getTransactionAddress(chain, index);
|
String address = walletBase!.getTransactionAddress(chain, index);
|
||||||
|
|
||||||
return address;
|
return isar_models.Address(
|
||||||
}
|
walletId: walletId,
|
||||||
|
derivationIndex: index,
|
||||||
/// Adds [address] to the relevant chain's address array, which is determined by [chain].
|
value: address,
|
||||||
/// [address] - Expects a standard native segwit address
|
publicKey: [],
|
||||||
/// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value!
|
type: isar_models.AddressType.cryptonote,
|
||||||
Future<void> _addToAddressesArrayForChain(String address, int chain) async {
|
subType: chain == 0
|
||||||
String chainArray = '';
|
? isar_models.AddressSubType.receiving
|
||||||
if (chain == 0) {
|
: isar_models.AddressSubType.change,
|
||||||
chainArray = 'receivingAddresses';
|
);
|
||||||
} else {
|
|
||||||
chainArray = 'changeAddresses';
|
|
||||||
}
|
|
||||||
|
|
||||||
final addressArray =
|
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: chainArray);
|
|
||||||
if (addressArray == null) {
|
|
||||||
Logging.instance.log(
|
|
||||||
'Attempting to add the following to $chainArray array for chain $chain:${[
|
|
||||||
address
|
|
||||||
]}',
|
|
||||||
level: LogLevel.Info);
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: chainArray, value: [address]);
|
|
||||||
} else {
|
|
||||||
// Make a deep copy of the existing list
|
|
||||||
final List<String> newArray = [];
|
|
||||||
addressArray
|
|
||||||
.forEach((dynamic _address) => newArray.add(_address as String));
|
|
||||||
newArray.add(address); // Add the address passed into the method
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: chainArray, value: newArray);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<FeeObject> _getFees() async {
|
Future<FeeObject> _getFees() async {
|
||||||
|
@ -959,7 +835,7 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<TransactionData> _fetchTransactionData() async {
|
Future<void> _refreshTransactions() async {
|
||||||
await walletBase!.updateTransactions();
|
await walletBase!.updateTransactions();
|
||||||
final transactions = walletBase?.transactionHistory!.transactions;
|
final transactions = walletBase?.transactionHistory!.transactions;
|
||||||
|
|
||||||
|
@ -977,123 +853,75 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
//
|
//
|
||||||
// final Set<String> cachedTxids = Set<String>.from(txidsList);
|
// final Set<String> cachedTxids = Set<String>.from(txidsList);
|
||||||
|
|
||||||
// sort thing stuff
|
final List<
|
||||||
// change to get Monero price
|
Tuple4<isar_models.Transaction, List<isar_models.Output>,
|
||||||
final priceData =
|
List<isar_models.Input>, isar_models.Address?>> txnsData = [];
|
||||||
await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
|
||||||
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
|
||||||
final List<Map<String, dynamic>> midSortedArray = [];
|
|
||||||
|
|
||||||
if (transactions != null) {
|
if (transactions != null) {
|
||||||
for (var tx in transactions.entries) {
|
for (var tx in transactions.entries) {
|
||||||
// cachedTxids.add(tx.value.id);
|
// cachedTxids.add(tx.value.id);
|
||||||
Logging.instance.log(
|
// Logging.instance.log(
|
||||||
"${tx.value.accountIndex} ${tx.value.addressIndex} ${tx.value.amount} ${tx.value.date} "
|
// "${tx.value.accountIndex} ${tx.value.addressIndex} ${tx.value.amount} ${tx.value.date} "
|
||||||
"${tx.value.direction} ${tx.value.fee} ${tx.value.height} ${tx.value.id} ${tx.value.isPending} ${tx.value.key} "
|
// "${tx.value.direction} ${tx.value.fee} ${tx.value.height} ${tx.value.id} ${tx.value.isPending} ${tx.value.key} "
|
||||||
"${tx.value.recipientAddress}, ${tx.value.additionalInfo} con:${tx.value.confirmations}"
|
// "${tx.value.recipientAddress}, ${tx.value.additionalInfo} con:${tx.value.confirmations}"
|
||||||
" ${tx.value.keyIndex}",
|
// " ${tx.value.keyIndex}",
|
||||||
level: LogLevel.Info);
|
// level: LogLevel.Info);
|
||||||
final worthNow = (currentPrice *
|
|
||||||
Format.satoshisToAmount(
|
isar_models.Address? address;
|
||||||
tx.value.amount!,
|
isar_models.TransactionType type;
|
||||||
coin: coin,
|
|
||||||
))
|
|
||||||
.toStringAsFixed(2);
|
|
||||||
Map<String, dynamic> midSortedTx = {};
|
|
||||||
// // create final tx map
|
|
||||||
midSortedTx["txid"] = tx.value.id;
|
|
||||||
midSortedTx["confirmed_status"] = !tx.value.isPending &&
|
|
||||||
tx.value.confirmations! >= MINIMUM_CONFIRMATIONS;
|
|
||||||
midSortedTx["confirmations"] = tx.value.confirmations ?? 0;
|
|
||||||
midSortedTx["timestamp"] =
|
|
||||||
(tx.value.date.millisecondsSinceEpoch ~/ 1000);
|
|
||||||
midSortedTx["txType"] =
|
|
||||||
tx.value.direction == TransactionDirection.incoming
|
|
||||||
? "Received"
|
|
||||||
: "Sent";
|
|
||||||
midSortedTx["amount"] = tx.value.amount;
|
|
||||||
midSortedTx["worthNow"] = worthNow;
|
|
||||||
midSortedTx["worthAtBlockTimestamp"] = worthNow;
|
|
||||||
midSortedTx["fees"] = tx.value.fee;
|
|
||||||
if (tx.value.direction == TransactionDirection.incoming) {
|
if (tx.value.direction == TransactionDirection.incoming) {
|
||||||
final addressInfo = tx.value.additionalInfo;
|
final addressInfo = tx.value.additionalInfo;
|
||||||
|
|
||||||
midSortedTx["address"] = walletBase?.getTransactionAddress(
|
final addressString = walletBase?.getTransactionAddress(
|
||||||
addressInfo!['accountIndex'] as int,
|
addressInfo!['accountIndex'] as int,
|
||||||
addressInfo['addressIndex'] as int,
|
addressInfo['addressIndex'] as int,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (addressString != null) {
|
||||||
|
address = await db
|
||||||
|
.getAddresses(walletId)
|
||||||
|
.filter()
|
||||||
|
.valueEqualTo(addressString)
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
type = isar_models.TransactionType.incoming;
|
||||||
} else {
|
} else {
|
||||||
midSortedTx["address"] = "";
|
// txn.address = "";
|
||||||
|
type = isar_models.TransactionType.outgoing;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int txHeight = tx.value.height ?? 0;
|
final txn = isar_models.Transaction(
|
||||||
midSortedTx["height"] = txHeight;
|
walletId: walletId,
|
||||||
// if (txHeight >= latestTxnBlockHeight) {
|
txid: tx.value.id,
|
||||||
// latestTxnBlockHeight = txHeight;
|
timestamp: (tx.value.date.millisecondsSinceEpoch ~/ 1000),
|
||||||
// }
|
type: type,
|
||||||
|
subType: isar_models.TransactionSubType.none,
|
||||||
|
amount: tx.value.amount ?? 0,
|
||||||
|
fee: tx.value.fee ?? 0,
|
||||||
|
height: tx.value.height,
|
||||||
|
isCancelled: false,
|
||||||
|
isLelantus: false,
|
||||||
|
slateId: null,
|
||||||
|
otherData: null,
|
||||||
|
);
|
||||||
|
|
||||||
midSortedTx["aliens"] = <dynamic>[];
|
txnsData.add(Tuple4(txn, [], [], address));
|
||||||
midSortedTx["inputSize"] = 0;
|
|
||||||
midSortedTx["outputSize"] = 0;
|
|
||||||
midSortedTx["inputs"] = <dynamic>[];
|
|
||||||
midSortedTx["outputs"] = <dynamic>[];
|
|
||||||
midSortedArray.add(midSortedTx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort by date ----
|
await db.addNewTransactionData(txnsData, walletId);
|
||||||
midSortedArray
|
|
||||||
.sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int));
|
|
||||||
Logging.instance.log(midSortedArray, level: LogLevel.Info);
|
|
||||||
|
|
||||||
// buildDateTimeChunks
|
// quick hack to notify manager to call notifyListeners if
|
||||||
final Map<String, dynamic> result = {"dateTimeChunks": <dynamic>[]};
|
// transactions changed
|
||||||
final dateArray = <dynamic>[];
|
if (txnsData.isNotEmpty) {
|
||||||
|
GlobalEventBus.instance.fire(
|
||||||
for (int i = 0; i < midSortedArray.length; i++) {
|
UpdatedInBackgroundEvent(
|
||||||
final txObject = midSortedArray[i];
|
"Transactions updated/added for: $walletId $walletName ",
|
||||||
final date = extractDateFromTimestamp(txObject["timestamp"] as int);
|
walletId,
|
||||||
final txTimeArray = [txObject["timestamp"], date];
|
),
|
||||||
|
);
|
||||||
if (dateArray.contains(txTimeArray[1])) {
|
|
||||||
result["dateTimeChunks"].forEach((dynamic chunk) {
|
|
||||||
if (extractDateFromTimestamp(chunk["timestamp"] as int) ==
|
|
||||||
txTimeArray[1]) {
|
|
||||||
if (chunk["transactions"] == null) {
|
|
||||||
chunk["transactions"] = <Map<String, dynamic>>[];
|
|
||||||
}
|
}
|
||||||
chunk["transactions"].add(txObject);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
dateArray.add(txTimeArray[1]);
|
|
||||||
final chunk = {
|
|
||||||
"timestamp": txTimeArray[0],
|
|
||||||
"transactions": [txObject],
|
|
||||||
};
|
|
||||||
result["dateTimeChunks"].add(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// final transactionsMap = cachedTransactions?.getAllTransactions() ?? {};
|
|
||||||
final Map<String, Transaction> transactionsMap = {};
|
|
||||||
transactionsMap
|
|
||||||
.addAll(TransactionData.fromJson(result).getAllTransactions());
|
|
||||||
|
|
||||||
final txModel = TransactionData.fromMap(transactionsMap);
|
|
||||||
|
|
||||||
// await DB.instance.put<dynamic>(
|
|
||||||
// boxName: walletId,
|
|
||||||
// key: 'storedTxnDataHeight',
|
|
||||||
// value: latestTxnBlockHeight);
|
|
||||||
// await DB.instance.put<dynamic>(
|
|
||||||
// boxName: walletId, key: 'latest_tx_model', value: txModel);
|
|
||||||
// await DB.instance.put<dynamic>(
|
|
||||||
// boxName: walletId,
|
|
||||||
// key: 'cachedTxids',
|
|
||||||
// value: cachedTxids.toList(growable: false));
|
|
||||||
|
|
||||||
return txModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _pathForWalletDir({
|
Future<String> _pathForWalletDir({
|
||||||
|
@ -1126,11 +954,12 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
DefaultNodes.getNodeFor(coin);
|
DefaultNodes.getNodeFor(coin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onNewBlock() {
|
void onNewBlock({required int height, required int blocksLeft}) {
|
||||||
//
|
//
|
||||||
print("=============================");
|
print("=============================");
|
||||||
print("New Block! :: $walletName");
|
print("New Block! :: $walletName");
|
||||||
print("=============================");
|
print("=============================");
|
||||||
|
updateCachedChainHeight(height);
|
||||||
_refreshTxDataHelper();
|
_refreshTxDataHelper();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1176,12 +1005,12 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _refreshTxData() async {
|
Future<void> _refreshTxData() async {
|
||||||
final txnData = await _fetchTransactionData();
|
await _refreshTransactions();
|
||||||
final count = txnData.getAllTransactions().length;
|
final count = await db.getTransactions(walletId).count();
|
||||||
|
|
||||||
if (count > _txCount) {
|
if (count > _txCount) {
|
||||||
_txCount = count;
|
_txCount = count;
|
||||||
_transactionData = Future(() => txnData);
|
await _updateBalance();
|
||||||
GlobalEventBus.instance.fire(
|
GlobalEventBus.instance.fire(
|
||||||
UpdatedInBackgroundEvent(
|
UpdatedInBackgroundEvent(
|
||||||
"New transaction data found in $walletId $walletName!",
|
"New transaction data found in $walletId $walletName!",
|
||||||
|
@ -1217,6 +1046,7 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
if (highestPercentCached < percent) {
|
if (highestPercentCached < percent) {
|
||||||
highestPercentCached = percent;
|
highestPercentCached = percent;
|
||||||
}
|
}
|
||||||
|
await updateCachedChainHeight(height);
|
||||||
|
|
||||||
GlobalEventBus.instance.fire(
|
GlobalEventBus.instance.fire(
|
||||||
RefreshPercentChangedEvent(
|
RefreshPercentChangedEvent(
|
||||||
|
@ -1305,23 +1135,34 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the new receiving index
|
// Check the new receiving index
|
||||||
String indexKey = "receivingIndex";
|
final currentReceiving = await _currentReceivingAddress;
|
||||||
final curIndex =
|
final curIndex = currentReceiving?.derivationIndex ?? -1;
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
|
|
||||||
if (highestIndex >= curIndex) {
|
if (highestIndex >= curIndex) {
|
||||||
// First increment the receiving index
|
// First increment the receiving index
|
||||||
await _incrementAddressIndexForChain(0);
|
final newReceivingIndex = curIndex + 1;
|
||||||
final newReceivingIndex =
|
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
|
|
||||||
|
|
||||||
// Use new index to derive a new receiving address
|
// Use new index to derive a new receiving address
|
||||||
final newReceivingAddress =
|
final newReceivingAddress =
|
||||||
await _generateAddressForChain(0, newReceivingIndex);
|
await _generateAddressForChain(0, newReceivingIndex);
|
||||||
|
|
||||||
// Add that new receiving address to the array of receiving addresses
|
final existing = await db
|
||||||
await _addToAddressesArrayForChain(newReceivingAddress, 0);
|
.getAddresses(walletId)
|
||||||
|
.filter()
|
||||||
|
.valueEqualTo(newReceivingAddress.value)
|
||||||
|
.findFirst();
|
||||||
|
if (existing == null) {
|
||||||
|
// Add that new change address
|
||||||
|
await db.putAddress(newReceivingAddress);
|
||||||
|
} else {
|
||||||
|
// we need to update the address
|
||||||
|
await db.updateAddress(existing, newReceivingAddress);
|
||||||
|
|
||||||
_currentReceivingAddress = Future(() => newReceivingAddress);
|
// since we updated an existing address there is a chance it has
|
||||||
|
// some tx history. To prevent address reuse we will call check again
|
||||||
|
// recursively
|
||||||
|
await _checkReceivingAddressForTransactions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} on SocketException catch (se, s) {
|
} on SocketException catch (se, s) {
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
|
@ -1346,4 +1187,19 @@ class MoneroWallet extends CoinServiceAPI {
|
||||||
key: "highestPercentCached",
|
key: "highestPercentCached",
|
||||||
value: value,
|
value: value,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get storedChainHeight => getCachedChainHeight();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Balance get balance => _balance ??= getCachedBalance();
|
||||||
|
Balance? _balance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<isar_models.Transaction>> get transactions =>
|
||||||
|
db.getTransactions(walletId).sortByTimestampDesc().findAll();
|
||||||
|
|
||||||
|
@override
|
||||||
|
// TODO: implement utxos
|
||||||
|
Future<List<isar_models.UTXO>> get utxos => throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -15,7 +15,6 @@ import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cw_wownero/api/exceptions/creation_transaction_exception.dart';
|
import 'package:cw_wownero/api/exceptions/creation_transaction_exception.dart';
|
||||||
import 'package:cw_wownero/api/wallet.dart';
|
import 'package:cw_wownero/api/wallet.dart';
|
||||||
import 'package:cw_wownero/pending_wownero_transaction.dart';
|
import 'package:cw_wownero/pending_wownero_transaction.dart';
|
||||||
import 'package:cw_wownero/wownero_amount_format.dart';
|
|
||||||
import 'package:cw_wownero/wownero_wallet.dart';
|
import 'package:cw_wownero/wownero_wallet.dart';
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -24,21 +23,23 @@ import 'package:flutter_libmonero/core/wallet_creation_service.dart';
|
||||||
import 'package:flutter_libmonero/view_model/send/output.dart'
|
import 'package:flutter_libmonero/view_model/send/output.dart'
|
||||||
as wownero_output;
|
as wownero_output;
|
||||||
import 'package:flutter_libmonero/wownero/wownero.dart';
|
import 'package:flutter_libmonero/wownero/wownero.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
|
import 'package:stackwallet/db/main_db.dart';
|
||||||
import 'package:stackwallet/hive/db.dart';
|
import 'package:stackwallet/hive/db.dart';
|
||||||
|
import 'package:stackwallet/models/balance.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
||||||
import 'package:stackwallet/models/node_model.dart';
|
import 'package:stackwallet/models/node_model.dart';
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
|
||||||
import 'package:stackwallet/models/paymint/utxo_model.dart';
|
|
||||||
import 'package:stackwallet/services/coins/coin_service.dart';
|
import 'package:stackwallet/services/coins/coin_service.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart';
|
import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
|
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
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/events/global/wallet_sync_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||||
|
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||||
|
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||||
import 'package:stackwallet/services/node_service.dart';
|
import 'package:stackwallet/services/node_service.dart';
|
||||||
import 'package:stackwallet/services/price.dart';
|
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
@ -48,17 +49,18 @@ import 'package:stackwallet/utilities/format.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/prefs.dart';
|
import 'package:stackwallet/utilities/prefs.dart';
|
||||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
const int MINIMUM_CONFIRMATIONS = 10;
|
const int MINIMUM_CONFIRMATIONS = 10;
|
||||||
|
|
||||||
class WowneroWallet extends CoinServiceAPI {
|
class WowneroWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||||
final String _walletId;
|
late final String _walletId;
|
||||||
final Coin _coin;
|
late final Coin _coin;
|
||||||
final PriceAPI _priceAPI;
|
late final SecureStorageInterface _secureStorage;
|
||||||
final SecureStorageInterface _secureStorage;
|
late final Prefs _prefs;
|
||||||
final Prefs _prefs;
|
|
||||||
|
late String _walletName;
|
||||||
|
|
||||||
String _walletName;
|
|
||||||
bool _shouldAutoSync = false;
|
bool _shouldAutoSync = false;
|
||||||
bool _isConnected = false;
|
bool _isConnected = false;
|
||||||
bool _hasCalledExit = false;
|
bool _hasCalledExit = false;
|
||||||
|
@ -71,9 +73,9 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
WalletCreationService? _walletCreationService;
|
WalletCreationService? _walletCreationService;
|
||||||
Timer? _autoSaveTimer;
|
Timer? _autoSaveTimer;
|
||||||
|
|
||||||
Future<String>? _currentReceivingAddress;
|
Future<isar_models.Address?> get _currentReceivingAddress =>
|
||||||
|
db.getAddresses(walletId).sortByDerivationIndexDesc().findFirst();
|
||||||
Future<FeeObject>? _feeObject;
|
Future<FeeObject>? _feeObject;
|
||||||
Future<TransactionData>? _transactionData;
|
|
||||||
|
|
||||||
Mutex prepareSendMutex = Mutex();
|
Mutex prepareSendMutex = Mutex();
|
||||||
Mutex estimateFeeMutex = Mutex();
|
Mutex estimateFeeMutex = Mutex();
|
||||||
|
@ -83,34 +85,29 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
required String walletName,
|
required String walletName,
|
||||||
required Coin coin,
|
required Coin coin,
|
||||||
required SecureStorageInterface secureStorage,
|
required SecureStorageInterface secureStorage,
|
||||||
PriceAPI? priceAPI,
|
|
||||||
Prefs? prefs,
|
Prefs? prefs,
|
||||||
}) : _walletId = walletId,
|
MainDB? mockableOverride,
|
||||||
_walletName = walletName,
|
}) {
|
||||||
_coin = coin,
|
_walletId = walletId;
|
||||||
_priceAPI = priceAPI ?? PriceAPI(Client()),
|
_walletName = walletName;
|
||||||
_secureStorage = secureStorage,
|
_coin = coin;
|
||||||
|
_secureStorage = secureStorage;
|
||||||
_prefs = prefs ?? Prefs.instance;
|
_prefs = prefs ?? Prefs.instance;
|
||||||
|
initCache(walletId, coin);
|
||||||
@override
|
isarInit(mockableOverride: mockableOverride);
|
||||||
bool get isFavorite {
|
|
||||||
try {
|
|
||||||
return DB.instance.get<dynamic>(boxName: walletId, key: "isFavorite")
|
|
||||||
as bool;
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"isFavorite fetch failed (returning false by default): $e\n$s",
|
|
||||||
level: LogLevel.Error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
set isFavorite(bool markFavorite) {
|
set isFavorite(bool markFavorite) {
|
||||||
DB.instance.put<dynamic>(
|
_isFavorite = markFavorite;
|
||||||
boxName: walletId, key: "isFavorite", value: markFavorite);
|
updateCachedIsFavorite(markFavorite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isFavorite => _isFavorite ??= getCachedIsFavorite();
|
||||||
|
|
||||||
|
bool? _isFavorite;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get shouldAutoSync => _shouldAutoSync;
|
bool get shouldAutoSync => _shouldAutoSync;
|
||||||
|
|
||||||
|
@ -142,23 +139,6 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
@override
|
@override
|
||||||
set walletName(String newName) => _walletName = newName;
|
set walletName(String newName) => _walletName = newName;
|
||||||
|
|
||||||
@override
|
|
||||||
// not really used for wownero
|
|
||||||
Future<List<String>> get allOwnAddresses async => [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Decimal> get availableBalance async {
|
|
||||||
int runningBalance = 0;
|
|
||||||
for (final entry in walletBase!.balance!.entries) {
|
|
||||||
runningBalance += entry.value.unlockedBalance;
|
|
||||||
}
|
|
||||||
return Format.satoshisToAmount(runningBalance, coin: coin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
// not used
|
|
||||||
Future<Decimal> get balanceMinusMaxFee => throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Coin get coin => _coin;
|
Coin get coin => _coin;
|
||||||
|
|
||||||
|
@ -187,8 +167,9 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> get currentReceivingAddress =>
|
Future<String> get currentReceivingAddress async =>
|
||||||
_currentReceivingAddress ??= _getCurrentAddressForChain(0);
|
(await _currentReceivingAddress)?.value ??
|
||||||
|
(await _generateAddressForChain(0, 0)).value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
|
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
|
||||||
|
@ -260,30 +241,30 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
int maxUnusedAddressGap,
|
int maxUnusedAddressGap,
|
||||||
int maxNumberOfIndexesToCheck,
|
int maxNumberOfIndexesToCheck,
|
||||||
) async {
|
) async {
|
||||||
|
// clear blockchain info
|
||||||
|
await db.deleteWalletBlockchainData(walletId);
|
||||||
|
|
||||||
var restoreHeight = walletBase?.walletInfo.restoreHeight;
|
var restoreHeight = walletBase?.walletInfo.restoreHeight;
|
||||||
highestPercentCached = 0;
|
highestPercentCached = 0;
|
||||||
await walletBase?.rescan(height: restoreHeight);
|
await walletBase?.rescan(height: restoreHeight);
|
||||||
|
await refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> generateNewAddress() async {
|
Future<bool> generateNewAddress() async {
|
||||||
try {
|
try {
|
||||||
const String indexKey = "receivingIndex";
|
final currentReceiving = await _currentReceivingAddress;
|
||||||
// First increment the receiving index
|
|
||||||
await _incrementAddressIndexForChain(0);
|
final newReceivingIndex = currentReceiving!.derivationIndex + 1;
|
||||||
final newReceivingIndex =
|
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
|
|
||||||
|
|
||||||
// Use new index to derive a new receiving address
|
// Use new index to derive a new receiving address
|
||||||
final newReceivingAddress =
|
final newReceivingAddress = await _generateAddressForChain(
|
||||||
await _generateAddressForChain(0, newReceivingIndex);
|
0,
|
||||||
|
newReceivingIndex,
|
||||||
|
);
|
||||||
|
|
||||||
// Add that new receiving address to the array of receiving addresses
|
// Add that new receiving address
|
||||||
await _addToAddressesArrayForChain(newReceivingAddress, 0);
|
await db.putAddress(newReceivingAddress);
|
||||||
|
|
||||||
// Set the new receiving address that the service
|
|
||||||
|
|
||||||
_currentReceivingAddress = Future(() => newReceivingAddress);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
@ -303,7 +284,7 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
"Opening existing ${coin.prettyName} wallet $walletName...",
|
"Opening existing ${coin.prettyName} wallet $walletName...",
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
|
|
||||||
if ((DB.instance.get<dynamic>(boxName: walletId, key: "id")) == null) {
|
if (getCachedId() == null) {
|
||||||
//todo: check if print needed
|
//todo: check if print needed
|
||||||
// debugPrint("Exception was thrown");
|
// debugPrint("Exception was thrown");
|
||||||
throw Exception(
|
throw Exception(
|
||||||
|
@ -315,12 +296,6 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
keysStorage = KeyService(_secureStorage);
|
keysStorage = KeyService(_secureStorage);
|
||||||
|
|
||||||
await _prefs.init();
|
await _prefs.init();
|
||||||
// final data =
|
|
||||||
// DB.instance.get<dynamic>(boxName: walletId, key: "latest_tx_model")
|
|
||||||
// as TransactionData?;
|
|
||||||
// if (data != null) {
|
|
||||||
// _transactionData = Future(() => data);
|
|
||||||
// }
|
|
||||||
|
|
||||||
String? password;
|
String? password;
|
||||||
try {
|
try {
|
||||||
|
@ -335,17 +310,6 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
"Opened existing ${coin.prettyName} wallet $walletName",
|
"Opened existing ${coin.prettyName} wallet $walletName",
|
||||||
level: LogLevel.Info,
|
level: LogLevel.Info,
|
||||||
);
|
);
|
||||||
// Wallet already exists, triggers for a returning user
|
|
||||||
|
|
||||||
String indexKey = "receivingIndex";
|
|
||||||
final curIndex =
|
|
||||||
await DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
|
|
||||||
// Use new index to derive a new receiving address
|
|
||||||
final newReceivingAddress = await _generateAddressForChain(0, curIndex);
|
|
||||||
Logging.instance.log(
|
|
||||||
"wownero address in init existing: $newReceivingAddress",
|
|
||||||
level: LogLevel.Info);
|
|
||||||
_currentReceivingAddress = Future(() => newReceivingAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -427,42 +391,18 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
await walletBase?.connectToNode(
|
await walletBase?.connectToNode(
|
||||||
node: Node(uri: "$host:${node.port}", type: WalletType.wownero));
|
node: Node(uri: "$host:${node.port}", type: WalletType.wownero));
|
||||||
await walletBase?.startSync();
|
await walletBase?.startSync();
|
||||||
await DB.instance
|
await Future.wait([
|
||||||
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
updateCachedId(walletId),
|
||||||
|
updateCachedIsFavorite(false),
|
||||||
// Set relevant indexes
|
]);
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0);
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "changeIndex", value: 0);
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'blocked_tx_hashes',
|
|
||||||
value: ["0xdefault"],
|
|
||||||
); // A list of transaction hashes to represent frozen utxos in wallet
|
|
||||||
// initialize address book entries
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'addressBookEntries',
|
|
||||||
value: <String, String>{});
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "isFavorite", value: false);
|
|
||||||
|
|
||||||
// Generate and add addresses to relevant arrays
|
// Generate and add addresses to relevant arrays
|
||||||
final initialReceivingAddress = await _generateAddressForChain(0, 0);
|
final initialReceivingAddress = await _generateAddressForChain(0, 0);
|
||||||
// final initialChangeAddress = await _generateAddressForChain(1, 0);
|
// final initialChangeAddress = await _generateAddressForChain(1, 0);
|
||||||
|
|
||||||
await _addToAddressesArrayForChain(initialReceivingAddress, 0);
|
await db.putAddress(initialReceivingAddress);
|
||||||
// await _addToAddressesArrayForChain(initialChangeAddress, 1);
|
|
||||||
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'receivingAddresses',
|
|
||||||
value: [initialReceivingAddress]);
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0);
|
|
||||||
walletBase?.close();
|
walletBase?.close();
|
||||||
_currentReceivingAddress = Future(() => initialReceivingAddress);
|
|
||||||
|
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("initializeNew for $walletName $walletId", level: LogLevel.Info);
|
.log("initializeNew for $walletName $walletId", level: LogLevel.Info);
|
||||||
|
@ -489,10 +429,6 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
// not used in wow
|
|
||||||
Future<Decimal> get pendingBalance => throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>> prepareSend({
|
Future<Map<String, dynamic>> prepareSend({
|
||||||
required String address,
|
required String address,
|
||||||
|
@ -519,16 +455,14 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
try {
|
try {
|
||||||
// check for send all
|
// check for send all
|
||||||
bool isSendAll = false;
|
bool isSendAll = false;
|
||||||
final balance = await availableBalance;
|
final balance = await _availableBalance;
|
||||||
final satInDecimal =
|
if (satoshiAmount == balance) {
|
||||||
Format.satoshisToAmount(satoshiAmount, coin: coin);
|
|
||||||
|
|
||||||
if (satInDecimal == balance) {
|
|
||||||
isSendAll = true;
|
isSendAll = true;
|
||||||
}
|
}
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("$address $satoshiAmount $args", level: LogLevel.Info);
|
.log("$address $satoshiAmount $args", level: LogLevel.Info);
|
||||||
String amountToSend = satInDecimal
|
String amountToSend =
|
||||||
|
Format.satoshisToAmount(satoshiAmount, coin: coin)
|
||||||
.toStringAsFixed(Constants.decimalPlacesForCoin(coin));
|
.toStringAsFixed(Constants.decimalPlacesForCoin(coin));
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("$satoshiAmount $amountToSend", level: LogLevel.Info);
|
.log("$satoshiAmount $amountToSend", level: LogLevel.Info);
|
||||||
|
@ -667,28 +601,11 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
.add<WalletInfo>(boxName: WalletInfo.boxName, value: walletInfo);
|
.add<WalletInfo>(boxName: WalletInfo.boxName, value: walletInfo);
|
||||||
walletBase?.close();
|
walletBase?.close();
|
||||||
walletBase = wallet as WowneroWalletBase;
|
walletBase = wallet as WowneroWalletBase;
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
await Future.wait([
|
||||||
key: 'receivingAddresses',
|
updateCachedId(walletId),
|
||||||
value: [walletInfo.address!]);
|
updateCachedIsFavorite(false),
|
||||||
await DB.instance
|
]);
|
||||||
.put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0);
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "changeIndex", value: 0);
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'blocked_tx_hashes',
|
|
||||||
value: ["0xdefault"],
|
|
||||||
); // A list of transaction hashes to represent frozen utxos in wallet
|
|
||||||
// initialize address book entries
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'addressBookEntries',
|
|
||||||
value: <String, String>{});
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: "isFavorite", value: false);
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
//todo: come back to this
|
//todo: come back to this
|
||||||
debugPrint(e.toString());
|
debugPrint(e.toString());
|
||||||
|
@ -733,22 +650,10 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final newTxData = await _fetchTransactionData();
|
await _refreshTransactions();
|
||||||
_transactionData = Future(() => newTxData);
|
await _updateBalance();
|
||||||
|
|
||||||
await _checkCurrentReceivingAddressesForTransactions();
|
await _checkCurrentReceivingAddressesForTransactions();
|
||||||
String indexKey = "receivingIndex";
|
|
||||||
final curIndex =
|
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
|
|
||||||
// Use new index to derive a new receiving address
|
|
||||||
try {
|
|
||||||
final newReceivingAddress = await _generateAddressForChain(0, curIndex);
|
|
||||||
_currentReceivingAddress = Future(() => newReceivingAddress);
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to call _generateAddressForChain(0, $curIndex): $e\n$s",
|
|
||||||
level: LogLevel.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (walletBase?.syncStatus is SyncedSyncStatus) {
|
if (walletBase?.syncStatus is SyncedSyncStatus) {
|
||||||
refreshMutex = false;
|
refreshMutex = false;
|
||||||
|
@ -762,16 +667,6 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> send({
|
|
||||||
required String toAddress,
|
|
||||||
required int amount,
|
|
||||||
Map<String, String> args = const {},
|
|
||||||
}) {
|
|
||||||
// not used for xmr
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> testNetworkConnection() async {
|
Future<bool> testNetworkConnection() async {
|
||||||
return await walletBase?.isConnected() ?? false;
|
return await walletBase?.isConnected() ?? false;
|
||||||
|
@ -835,8 +730,32 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
) as int? ??
|
) as int? ??
|
||||||
0;
|
0;
|
||||||
|
|
||||||
@override
|
Future<void> _updateBalance() async {
|
||||||
Future<Decimal> get totalBalance async {
|
final total = await _totalBalance;
|
||||||
|
final available = await _availableBalance;
|
||||||
|
_balance = Balance(
|
||||||
|
coin: coin,
|
||||||
|
total: total,
|
||||||
|
spendable: available,
|
||||||
|
blockedTotal: 0,
|
||||||
|
pendingSpendable: total - available,
|
||||||
|
);
|
||||||
|
await updateCachedBalance(_balance!);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> get _availableBalance async {
|
||||||
|
try {
|
||||||
|
int runningBalance = 0;
|
||||||
|
for (final entry in walletBase!.balance!.entries) {
|
||||||
|
runningBalance += entry.value.unlockedBalance;
|
||||||
|
}
|
||||||
|
return runningBalance;
|
||||||
|
} catch (_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> get _totalBalance async {
|
||||||
try {
|
try {
|
||||||
final balanceEntries = walletBase?.balance?.entries;
|
final balanceEntries = walletBase?.balance?.entries;
|
||||||
if (balanceEntries != null) {
|
if (balanceEntries != null) {
|
||||||
|
@ -845,7 +764,7 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
bal = bal + element.value.fullBalance;
|
bal = bal + element.value.fullBalance;
|
||||||
}
|
}
|
||||||
await _updateCachedBalance(bal);
|
await _updateCachedBalance(bal);
|
||||||
return Format.satoshisToAmount(bal, coin: coin);
|
return bal;
|
||||||
} else {
|
} else {
|
||||||
final transactions = walletBase!.transactionHistory!.transactions;
|
final transactions = walletBase!.transactionHistory!.transactions;
|
||||||
int transactionBalance = 0;
|
int transactionBalance = 0;
|
||||||
|
@ -858,21 +777,13 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
await _updateCachedBalance(transactionBalance);
|
await _updateCachedBalance(transactionBalance);
|
||||||
return Format.satoshisToAmount(transactionBalance, coin: coin);
|
return transactionBalance;
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return Format.satoshisToAmount(_getCachedBalance(), coin: coin);
|
return _getCachedBalance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<TransactionData> get transactionData =>
|
|
||||||
_transactionData ??= _fetchTransactionData();
|
|
||||||
|
|
||||||
@override
|
|
||||||
// not used for xmr
|
|
||||||
Future<List<UtxoObject>> get unspentOutputs => throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateNode(bool shouldRefresh) async {
|
Future<void> updateNode(bool shouldRefresh) async {
|
||||||
final node = await _getCurrentNode();
|
final node = await _getCurrentNode();
|
||||||
|
@ -901,66 +812,23 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
@override
|
@override
|
||||||
String get walletId => _walletId;
|
String get walletId => _walletId;
|
||||||
|
|
||||||
/// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain]
|
Future<isar_models.Address> _generateAddressForChain(
|
||||||
/// and
|
int chain,
|
||||||
/// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value!
|
int index,
|
||||||
Future<String> _getCurrentAddressForChain(int chain) async {
|
) async {
|
||||||
// Here, we assume that chain == 1 if it isn't 0
|
|
||||||
String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses";
|
|
||||||
final internalChainArray = (DB.instance
|
|
||||||
.get<dynamic>(boxName: walletId, key: arrayKey)) as List<dynamic>;
|
|
||||||
return internalChainArray.last as String;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Increases the index for either the internal or external chain, depending on [chain].
|
|
||||||
/// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value!
|
|
||||||
Future<void> _incrementAddressIndexForChain(int chain) async {
|
|
||||||
// Here we assume chain == 1 if it isn't 0
|
|
||||||
String indexKey = chain == 0 ? "receivingIndex" : "changeIndex";
|
|
||||||
|
|
||||||
final newIndex =
|
|
||||||
(DB.instance.get<dynamic>(boxName: walletId, key: indexKey)) + 1;
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: indexKey, value: newIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> _generateAddressForChain(int chain, int index) async {
|
|
||||||
//
|
//
|
||||||
String address = walletBase!.getTransactionAddress(chain, index);
|
String address = walletBase!.getTransactionAddress(chain, index);
|
||||||
|
|
||||||
return address;
|
return isar_models.Address(
|
||||||
}
|
walletId: walletId,
|
||||||
|
derivationIndex: index,
|
||||||
/// Adds [address] to the relevant chain's address array, which is determined by [chain].
|
value: address,
|
||||||
/// [address] - Expects a standard native segwit address
|
publicKey: [],
|
||||||
/// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value!
|
type: isar_models.AddressType.cryptonote,
|
||||||
Future<void> _addToAddressesArrayForChain(String address, int chain) async {
|
subType: chain == 0
|
||||||
String chainArray = '';
|
? isar_models.AddressSubType.receiving
|
||||||
if (chain == 0) {
|
: isar_models.AddressSubType.change,
|
||||||
chainArray = 'receivingAddresses';
|
);
|
||||||
} else {
|
|
||||||
chainArray = 'changeAddresses';
|
|
||||||
}
|
|
||||||
|
|
||||||
final addressArray =
|
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: chainArray);
|
|
||||||
if (addressArray == null) {
|
|
||||||
Logging.instance.log(
|
|
||||||
'Attempting to add the following to $chainArray array for chain $chain:${[
|
|
||||||
address
|
|
||||||
]}',
|
|
||||||
level: LogLevel.Info);
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: chainArray, value: [address]);
|
|
||||||
} else {
|
|
||||||
// Make a deep copy of the existing list
|
|
||||||
final List<String> newArray = [];
|
|
||||||
addressArray
|
|
||||||
.forEach((dynamic _address) => newArray.add(_address as String));
|
|
||||||
newArray.add(address); // Add the address passed into the method
|
|
||||||
await DB.instance
|
|
||||||
.put<dynamic>(boxName: walletId, key: chainArray, value: newArray);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<FeeObject> _getFees() async {
|
Future<FeeObject> _getFees() async {
|
||||||
|
@ -975,7 +843,7 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<TransactionData> _fetchTransactionData() async {
|
Future<void> _refreshTransactions() async {
|
||||||
await walletBase!.updateTransactions();
|
await walletBase!.updateTransactions();
|
||||||
final transactions = walletBase?.transactionHistory!.transactions;
|
final transactions = walletBase?.transactionHistory!.transactions;
|
||||||
|
|
||||||
|
@ -1011,121 +879,117 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// sort thing stuff
|
final List<
|
||||||
// change to get Wownero price
|
Tuple4<isar_models.Transaction, List<isar_models.Output>,
|
||||||
final priceData =
|
List<isar_models.Input>, isar_models.Address?>> txnsData = [];
|
||||||
await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
|
||||||
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
|
||||||
final List<Map<String, dynamic>> midSortedArray = [];
|
|
||||||
|
|
||||||
if (transactions != null) {
|
if (transactions != null) {
|
||||||
for (var tx in transactions.entries) {
|
for (var tx in transactions.entries) {
|
||||||
// cachedTxids.add(tx.value.id);
|
// cachedTxids.add(tx.value.id);
|
||||||
Logging.instance.log(
|
// Logging.instance.log(
|
||||||
"${tx.value.accountIndex} ${tx.value.addressIndex} ${tx.value.amount} ${tx.value.date} "
|
// "${tx.value.accountIndex} ${tx.value.addressIndex} ${tx.value.amount} ${tx.value.date} "
|
||||||
"${tx.value.direction} ${tx.value.fee} ${tx.value.height} ${tx.value.id} ${tx.value.isPending} ${tx.value.key} "
|
// "${tx.value.direction} ${tx.value.fee} ${tx.value.height} ${tx.value.id} ${tx.value.isPending} ${tx.value.key} "
|
||||||
"${tx.value.recipientAddress}, ${tx.value.additionalInfo} con:${tx.value.confirmations}"
|
// "${tx.value.recipientAddress}, ${tx.value.additionalInfo} con:${tx.value.confirmations}"
|
||||||
" ${tx.value.keyIndex}",
|
// " ${tx.value.keyIndex}",
|
||||||
level: LogLevel.Info);
|
// level: LogLevel.Info);
|
||||||
String am = wowneroAmountToString(amount: tx.value.amount!);
|
// String am = wowneroAmountToString(amount: tx.value.amount!);
|
||||||
final worthNow = (currentPrice * Decimal.parse(am)).toStringAsFixed(2);
|
// final worthNow = (currentPrice * Decimal.parse(am)).toStringAsFixed(2);
|
||||||
Map<String, dynamic> midSortedTx = {};
|
// Map<String, dynamic> midSortedTx = {};
|
||||||
// // create final tx map
|
// // // create final tx map
|
||||||
midSortedTx["txid"] = tx.value.id;
|
// midSortedTx["txid"] = tx.value.id;
|
||||||
midSortedTx["confirmed_status"] = !tx.value.isPending &&
|
// midSortedTx["confirmed_status"] = !tx.value.isPending &&
|
||||||
tx.value.confirmations != null &&
|
// tx.value.confirmations != null &&
|
||||||
tx.value.confirmations! >= MINIMUM_CONFIRMATIONS;
|
// tx.value.confirmations! >= MINIMUM_CONFIRMATIONS;
|
||||||
midSortedTx["confirmations"] = tx.value.confirmations ?? 0;
|
// midSortedTx["confirmations"] = tx.value.confirmations ?? 0;
|
||||||
midSortedTx["timestamp"] =
|
// midSortedTx["timestamp"] =
|
||||||
(tx.value.date.millisecondsSinceEpoch ~/ 1000);
|
// (tx.value.date.millisecondsSinceEpoch ~/ 1000);
|
||||||
midSortedTx["txType"] =
|
// midSortedTx["txType"] =
|
||||||
tx.value.direction == TransactionDirection.incoming
|
// tx.value.direction == TransactionDirection.incoming
|
||||||
? "Received"
|
// ? "Received"
|
||||||
: "Sent";
|
// : "Sent";
|
||||||
midSortedTx["amount"] = tx.value.amount;
|
// midSortedTx["amount"] = tx.value.amount;
|
||||||
midSortedTx["worthNow"] = worthNow;
|
// midSortedTx["worthNow"] = worthNow;
|
||||||
midSortedTx["worthAtBlockTimestamp"] = worthNow;
|
// midSortedTx["worthAtBlockTimestamp"] = worthNow;
|
||||||
midSortedTx["fees"] = tx.value.fee;
|
// midSortedTx["fees"] = tx.value.fee;
|
||||||
// TODO: shouldn't wownero have an address I can grab
|
// if (tx.value.direction == TransactionDirection.incoming) {
|
||||||
|
// final addressInfo = tx.value.additionalInfo;
|
||||||
|
//
|
||||||
|
// midSortedTx["address"] = walletBase?.getTransactionAddress(
|
||||||
|
// addressInfo!['accountIndex'] as int,
|
||||||
|
// addressInfo['addressIndex'] as int,
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// midSortedTx["address"] = "";
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// final int txHeight = tx.value.height ?? 0;
|
||||||
|
// midSortedTx["height"] = txHeight;
|
||||||
|
// // if (txHeight >= latestTxnBlockHeight) {
|
||||||
|
// // latestTxnBlockHeight = txHeight;
|
||||||
|
// // }
|
||||||
|
//
|
||||||
|
// midSortedTx["aliens"] = <dynamic>[];
|
||||||
|
// midSortedTx["inputSize"] = 0;
|
||||||
|
// midSortedTx["outputSize"] = 0;
|
||||||
|
// midSortedTx["inputs"] = <dynamic>[];
|
||||||
|
// midSortedTx["outputs"] = <dynamic>[];
|
||||||
|
// midSortedArray.add(midSortedTx);
|
||||||
|
|
||||||
|
isar_models.Address? address;
|
||||||
|
isar_models.TransactionType type;
|
||||||
if (tx.value.direction == TransactionDirection.incoming) {
|
if (tx.value.direction == TransactionDirection.incoming) {
|
||||||
final addressInfo = tx.value.additionalInfo;
|
final addressInfo = tx.value.additionalInfo;
|
||||||
|
|
||||||
midSortedTx["address"] = walletBase?.getTransactionAddress(
|
final addressString = walletBase?.getTransactionAddress(
|
||||||
addressInfo!['accountIndex'] as int,
|
addressInfo!['accountIndex'] as int,
|
||||||
addressInfo['addressIndex'] as int,
|
addressInfo['addressIndex'] as int,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (addressString != null) {
|
||||||
|
address = await db
|
||||||
|
.getAddresses(walletId)
|
||||||
|
.filter()
|
||||||
|
.valueEqualTo(addressString)
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
type = isar_models.TransactionType.incoming;
|
||||||
} else {
|
} else {
|
||||||
midSortedTx["address"] = "";
|
// txn.address = "";
|
||||||
|
type = isar_models.TransactionType.outgoing;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int txHeight = tx.value.height ?? 0;
|
final txn = isar_models.Transaction(
|
||||||
midSortedTx["height"] = txHeight;
|
walletId: walletId,
|
||||||
// if (txHeight >= latestTxnBlockHeight) {
|
txid: tx.value.id,
|
||||||
// latestTxnBlockHeight = txHeight;
|
timestamp: (tx.value.date.millisecondsSinceEpoch ~/ 1000),
|
||||||
// }
|
type: type,
|
||||||
|
subType: isar_models.TransactionSubType.none,
|
||||||
|
amount: tx.value.amount ?? 0,
|
||||||
|
fee: tx.value.fee ?? 0,
|
||||||
|
height: tx.value.height,
|
||||||
|
isCancelled: false,
|
||||||
|
isLelantus: false,
|
||||||
|
slateId: null,
|
||||||
|
otherData: null,
|
||||||
|
);
|
||||||
|
|
||||||
midSortedTx["aliens"] = <dynamic>[];
|
txnsData.add(Tuple4(txn, [], [], address));
|
||||||
midSortedTx["inputSize"] = 0;
|
|
||||||
midSortedTx["outputSize"] = 0;
|
|
||||||
midSortedTx["inputs"] = <dynamic>[];
|
|
||||||
midSortedTx["outputs"] = <dynamic>[];
|
|
||||||
midSortedArray.add(midSortedTx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort by date ----
|
await db.addNewTransactionData(txnsData, walletId);
|
||||||
midSortedArray
|
|
||||||
.sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int));
|
|
||||||
Logging.instance.log(midSortedArray, level: LogLevel.Info);
|
|
||||||
|
|
||||||
// buildDateTimeChunks
|
// quick hack to notify manager to call notifyListeners if
|
||||||
final Map<String, dynamic> result = {"dateTimeChunks": <dynamic>[]};
|
// transactions changed
|
||||||
final dateArray = <dynamic>[];
|
if (txnsData.isNotEmpty) {
|
||||||
|
GlobalEventBus.instance.fire(
|
||||||
for (int i = 0; i < midSortedArray.length; i++) {
|
UpdatedInBackgroundEvent(
|
||||||
final txObject = midSortedArray[i];
|
"Transactions updated/added for: $walletId $walletName ",
|
||||||
final date = extractDateFromTimestamp(txObject["timestamp"] as int);
|
walletId,
|
||||||
final txTimeArray = [txObject["timestamp"], date];
|
),
|
||||||
|
);
|
||||||
if (dateArray.contains(txTimeArray[1])) {
|
|
||||||
result["dateTimeChunks"].forEach((dynamic chunk) {
|
|
||||||
if (extractDateFromTimestamp(chunk["timestamp"] as int) ==
|
|
||||||
txTimeArray[1]) {
|
|
||||||
if (chunk["transactions"] == null) {
|
|
||||||
chunk["transactions"] = <Map<String, dynamic>>[];
|
|
||||||
}
|
}
|
||||||
chunk["transactions"].add(txObject);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
dateArray.add(txTimeArray[1]);
|
|
||||||
final chunk = {
|
|
||||||
"timestamp": txTimeArray[0],
|
|
||||||
"transactions": [txObject],
|
|
||||||
};
|
|
||||||
result["dateTimeChunks"].add(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// final transactionsMap = cachedTransactions?.getAllTransactions() ?? {};
|
|
||||||
final Map<String, Transaction> transactionsMap = {};
|
|
||||||
transactionsMap
|
|
||||||
.addAll(TransactionData.fromJson(result).getAllTransactions());
|
|
||||||
|
|
||||||
final txModel = TransactionData.fromMap(transactionsMap);
|
|
||||||
//
|
|
||||||
// await DB.instance.put<dynamic>(
|
|
||||||
// boxName: walletId,
|
|
||||||
// key: 'storedTxnDataHeight',
|
|
||||||
// value: latestTxnBlockHeight);
|
|
||||||
// await DB.instance.put<dynamic>(
|
|
||||||
// boxName: walletId, key: 'latest_tx_model', value: txModel);
|
|
||||||
// await DB.instance.put<dynamic>(
|
|
||||||
// boxName: walletId,
|
|
||||||
// key: 'cachedTxids',
|
|
||||||
// value: cachedTxids.toList(growable: false));
|
|
||||||
|
|
||||||
return txModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _pathForWalletDir({
|
Future<String> _pathForWalletDir({
|
||||||
|
@ -1158,11 +1022,12 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
DefaultNodes.getNodeFor(coin);
|
DefaultNodes.getNodeFor(coin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onNewBlock() {
|
void onNewBlock({required int height, required int blocksLeft}) {
|
||||||
//
|
//
|
||||||
print("=============================");
|
print("=============================");
|
||||||
print("New Wownero Block! :: $walletName");
|
print("New Wownero Block! :: $walletName");
|
||||||
print("=============================");
|
print("=============================");
|
||||||
|
updateCachedChainHeight(height);
|
||||||
_refreshTxDataHelper();
|
_refreshTxDataHelper();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1193,12 +1058,12 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _refreshTxData() async {
|
Future<void> _refreshTxData() async {
|
||||||
final txnData = await _fetchTransactionData();
|
await _refreshTransactions();
|
||||||
final count = txnData.getAllTransactions().length;
|
final count = await db.getTransactions(walletId).count();
|
||||||
|
|
||||||
if (count > _txCount) {
|
if (count > _txCount) {
|
||||||
_txCount = count;
|
_txCount = count;
|
||||||
_transactionData = Future(() => txnData);
|
await _updateBalance();
|
||||||
GlobalEventBus.instance.fire(
|
GlobalEventBus.instance.fire(
|
||||||
UpdatedInBackgroundEvent(
|
UpdatedInBackgroundEvent(
|
||||||
"New transaction data found in $walletId $walletName!",
|
"New transaction data found in $walletId $walletName!",
|
||||||
|
@ -1249,6 +1114,7 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
if (highestPercentCached < percent) {
|
if (highestPercentCached < percent) {
|
||||||
highestPercentCached = percent;
|
highestPercentCached = percent;
|
||||||
}
|
}
|
||||||
|
await updateCachedChainHeight(height);
|
||||||
|
|
||||||
GlobalEventBus.instance.fire(
|
GlobalEventBus.instance.fire(
|
||||||
RefreshPercentChangedEvent(
|
RefreshPercentChangedEvent(
|
||||||
|
@ -1337,23 +1203,34 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the new receiving index
|
// Check the new receiving index
|
||||||
String indexKey = "receivingIndex";
|
final currentReceiving = await _currentReceivingAddress;
|
||||||
final curIndex =
|
final curIndex = currentReceiving?.derivationIndex ?? -1;
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
|
|
||||||
if (highestIndex >= curIndex) {
|
if (highestIndex >= curIndex) {
|
||||||
// First increment the receiving index
|
// First increment the receiving index
|
||||||
await _incrementAddressIndexForChain(0);
|
final newReceivingIndex = curIndex + 1;
|
||||||
final newReceivingIndex =
|
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
|
|
||||||
|
|
||||||
// Use new index to derive a new receiving address
|
// Use new index to derive a new receiving address
|
||||||
final newReceivingAddress =
|
final newReceivingAddress =
|
||||||
await _generateAddressForChain(0, newReceivingIndex);
|
await _generateAddressForChain(0, newReceivingIndex);
|
||||||
|
|
||||||
// Add that new receiving address to the array of receiving addresses
|
final existing = await db
|
||||||
await _addToAddressesArrayForChain(newReceivingAddress, 0);
|
.getAddresses(walletId)
|
||||||
|
.filter()
|
||||||
|
.valueEqualTo(newReceivingAddress.value)
|
||||||
|
.findFirst();
|
||||||
|
if (existing == null) {
|
||||||
|
// Add that new change address
|
||||||
|
await db.putAddress(newReceivingAddress);
|
||||||
|
} else {
|
||||||
|
// we need to update the address
|
||||||
|
await db.updateAddress(existing, newReceivingAddress);
|
||||||
|
|
||||||
_currentReceivingAddress = Future(() => newReceivingAddress);
|
// since we updated an existing address there is a chance it has
|
||||||
|
// some tx history. To prevent address reuse we will call check again
|
||||||
|
// recursively
|
||||||
|
await _checkReceivingAddressForTransactions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} on SocketException catch (se, s) {
|
} on SocketException catch (se, s) {
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
|
@ -1378,4 +1255,19 @@ class WowneroWallet extends CoinServiceAPI {
|
||||||
key: "highestPercentCached",
|
key: "highestPercentCached",
|
||||||
value: value,
|
value: value,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get storedChainHeight => getCachedChainHeight();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Balance get balance => _balance ??= getCachedBalance();
|
||||||
|
Balance? _balance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<isar_models.Transaction>> get transactions =>
|
||||||
|
db.getTransactions(walletId).sortByTimestampDesc().findAll();
|
||||||
|
|
||||||
|
@override
|
||||||
|
// TODO: implement utxos
|
||||||
|
Future<List<isar_models.UTXO>> get utxos => throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
112
lib/services/mixins/epic_cash_hive.dart
Normal file
112
lib/services/mixins/epic_cash_hive.dart
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
import 'package:stackwallet/hive/db.dart';
|
||||||
|
|
||||||
|
mixin EpicCashHive {
|
||||||
|
late final String _walletId;
|
||||||
|
|
||||||
|
void initEpicCashHive(String walletId) {
|
||||||
|
_walletId = walletId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// receiving index
|
||||||
|
int? epicGetReceivingIndex() {
|
||||||
|
return DB.instance.get<dynamic>(boxName: _walletId, key: "receivingIndex")
|
||||||
|
as int?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> epicUpdateReceivingIndex(int index) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: "receivingIndex",
|
||||||
|
value: index,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// change index
|
||||||
|
int? epicGetChangeIndex() {
|
||||||
|
return DB.instance.get<dynamic>(boxName: _walletId, key: "changeIndex")
|
||||||
|
as int?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> epicUpdateChangeIndex(int index) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: "changeIndex",
|
||||||
|
value: index,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// slateToAddresses
|
||||||
|
Map epicGetSlatesToAddresses() {
|
||||||
|
return DB.instance.get<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: "slate_to_address",
|
||||||
|
) as Map? ??
|
||||||
|
{};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> epicUpdateSlatesToAddresses(Map map) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: "slate_to_address",
|
||||||
|
value: map,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// slatesToCommits
|
||||||
|
Map? epicGetSlatesToCommits() {
|
||||||
|
return DB.instance.get<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: "slatesToCommits",
|
||||||
|
) as Map?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> epicUpdateSlatesToCommits(Map map) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: "slatesToCommits",
|
||||||
|
value: map,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// last scanned block
|
||||||
|
int? epicGetLastScannedBlock() {
|
||||||
|
return DB.instance.get<dynamic>(boxName: _walletId, key: "lastScannedBlock")
|
||||||
|
as int?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> epicUpdateLastScannedBlock(int blockHeight) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: "lastScannedBlock",
|
||||||
|
value: blockHeight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// epic restore height
|
||||||
|
int? epicGetRestoreHeight() {
|
||||||
|
return DB.instance.get<dynamic>(boxName: _walletId, key: "restoreHeight")
|
||||||
|
as int?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> epicUpdateRestoreHeight(int height) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: "restoreHeight",
|
||||||
|
value: height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// epic creation height
|
||||||
|
int? epicGetCreationHeight() {
|
||||||
|
return DB.instance.get<dynamic>(boxName: _walletId, key: "creationHeight")
|
||||||
|
as int?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> epicUpdateCreationHeight(int height) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: "creationHeight",
|
||||||
|
value: height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
50
lib/services/mixins/firo_hive.dart
Normal file
50
lib/services/mixins/firo_hive.dart
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import 'package:stackwallet/hive/db.dart';
|
||||||
|
|
||||||
|
mixin FiroHive {
|
||||||
|
late final String _walletId;
|
||||||
|
|
||||||
|
void initFiroHive(String walletId) {
|
||||||
|
_walletId = walletId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// jindex
|
||||||
|
List? firoGetJIndex() {
|
||||||
|
return DB.instance.get<dynamic>(boxName: _walletId, key: "jindex") as List?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> firoUpdateJIndex(List jIndex) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: "jindex",
|
||||||
|
value: jIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// _lelantus_coins
|
||||||
|
List? firoGetLelantusCoins() {
|
||||||
|
return DB.instance.get<dynamic>(boxName: _walletId, key: "_lelantus_coins")
|
||||||
|
as List?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> firoUpdateLelantusCoins(List lelantusCoins) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: "_lelantus_coins",
|
||||||
|
value: lelantusCoins,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mintIndex
|
||||||
|
int? firoGetMintIndex() {
|
||||||
|
return DB.instance.get<dynamic>(boxName: _walletId, key: "mintIndex")
|
||||||
|
as int?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> firoUpdateMintIndex(int mintIndex) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: "mintIndex",
|
||||||
|
value: mintIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
115
lib/services/mixins/wallet_cache.dart
Normal file
115
lib/services/mixins/wallet_cache.dart
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import 'package:stackwallet/hive/db.dart';
|
||||||
|
import 'package:stackwallet/models/balance.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
|
||||||
|
mixin WalletCache {
|
||||||
|
late final String _walletId;
|
||||||
|
late final Coin _coin;
|
||||||
|
|
||||||
|
void initCache(String walletId, Coin coin) {
|
||||||
|
_walletId = walletId;
|
||||||
|
_coin = coin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cached wallet id
|
||||||
|
String? getCachedId() {
|
||||||
|
return DB.instance.get<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: DBKeys.id,
|
||||||
|
) as String?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateCachedId(String? id) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: DBKeys.id,
|
||||||
|
value: id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cached Chain Height
|
||||||
|
int getCachedChainHeight() {
|
||||||
|
return DB.instance.get<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: DBKeys.storedChainHeight,
|
||||||
|
) as int? ??
|
||||||
|
0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateCachedChainHeight(int height) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: DBKeys.storedChainHeight,
|
||||||
|
value: height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wallet favorite flag
|
||||||
|
bool getCachedIsFavorite() {
|
||||||
|
return DB.instance.get<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: DBKeys.isFavorite,
|
||||||
|
) as bool? ??
|
||||||
|
false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateCachedIsFavorite(bool isFavorite) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: DBKeys.isFavorite,
|
||||||
|
value: isFavorite,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// main balance cache
|
||||||
|
Balance getCachedBalance() {
|
||||||
|
final jsonString = DB.instance.get<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: DBKeys.cachedBalance,
|
||||||
|
) as String?;
|
||||||
|
if (jsonString == null) {
|
||||||
|
return Balance(
|
||||||
|
coin: _coin,
|
||||||
|
total: 0,
|
||||||
|
spendable: 0,
|
||||||
|
blockedTotal: 0,
|
||||||
|
pendingSpendable: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Balance.fromJson(jsonString, _coin);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateCachedBalance(Balance balance) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: DBKeys.cachedBalance,
|
||||||
|
value: balance.toJsonIgnoreCoin(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// secondary balance cache for coins such as firo
|
||||||
|
Balance getCachedBalanceSecondary() {
|
||||||
|
final jsonString = DB.instance.get<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: DBKeys.cachedBalanceSecondary,
|
||||||
|
) as String?;
|
||||||
|
if (jsonString == null) {
|
||||||
|
return Balance(
|
||||||
|
coin: _coin,
|
||||||
|
total: 0,
|
||||||
|
spendable: 0,
|
||||||
|
blockedTotal: 0,
|
||||||
|
pendingSpendable: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Balance.fromJson(jsonString, _coin);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateCachedBalanceSecondary(Balance balance) async {
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: _walletId,
|
||||||
|
key: DBKeys.cachedBalanceSecondary,
|
||||||
|
value: balance.toJsonIgnoreCoin(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
10
lib/services/mixins/wallet_db.dart
Normal file
10
lib/services/mixins/wallet_db.dart
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import 'package:stackwallet/db/main_db.dart';
|
||||||
|
|
||||||
|
mixin WalletDB {
|
||||||
|
MainDB? _db;
|
||||||
|
MainDB get db => _db!;
|
||||||
|
|
||||||
|
void isarInit({MainDB? mockableOverride}) async {
|
||||||
|
_db = mockableOverride ?? MainDB.instance;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_libmonero/monero/monero.dart';
|
import 'package:flutter_libmonero/monero/monero.dart';
|
||||||
import 'package:flutter_libmonero/wownero/wownero.dart';
|
import 'package:flutter_libmonero/wownero/wownero.dart';
|
||||||
|
import 'package:stackwallet/db/main_db.dart';
|
||||||
import 'package:stackwallet/hive/db.dart';
|
import 'package:stackwallet/hive/db.dart';
|
||||||
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
||||||
import 'package:stackwallet/services/notifications_service.dart';
|
import 'package:stackwallet/services/notifications_service.dart';
|
||||||
|
@ -385,6 +386,9 @@ class WalletsService extends ChangeNotifier {
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delete wallet data in main db
|
||||||
|
await MainDB.instance.deleteWalletBlockchainData(walletId);
|
||||||
|
|
||||||
// box data may currently still be read/written to if wallet was refreshing
|
// box data may currently still be read/written to if wallet was refreshing
|
||||||
// when delete was requested so instead of deleting now we mark the wallet
|
// when delete was requested so instead of deleting now we mark the wallet
|
||||||
// as needs delete by adding it's id to a list which gets checked on app start
|
// as needs delete by adding it's id to a list which gets checked on app start
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue