mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-18 02:07:43 +00:00
commit
bd05d6dddf
13 changed files with 316 additions and 162 deletions
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:stackwallet/exceptions/main_db/main_db_exception.dart';
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
@ -40,64 +41,101 @@ class MainDB {
|
||||||
String walletId) =>
|
String walletId) =>
|
||||||
isar.addresses.where().walletIdEqualTo(walletId);
|
isar.addresses.where().walletIdEqualTo(walletId);
|
||||||
|
|
||||||
Future<void> putAddress(Address address) => isar.writeTxn(() async {
|
Future<int> putAddress(Address address) async {
|
||||||
await isar.addresses.put(address);
|
try {
|
||||||
|
return await isar.writeTxn(() async {
|
||||||
|
return await isar.addresses.put(address);
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw MainDBException("failed putAddress: $address", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> putAddresses(List<Address> addresses) => isar.writeTxn(() async {
|
Future<List<int>> putAddresses(List<Address> addresses) async {
|
||||||
await isar.addresses.putAll(addresses);
|
try {
|
||||||
|
return await isar.writeTxn(() async {
|
||||||
|
return await isar.addresses.putAll(addresses);
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw MainDBException("failed putAddresses: $addresses", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updateOrPutAddresses(List<Address> addresses) async {
|
Future<List<int>> updateOrPutAddresses(List<Address> addresses) async {
|
||||||
await isar.writeTxn(() async {
|
try {
|
||||||
for (final address in addresses) {
|
List<int> ids = [];
|
||||||
final storedAddress = await isar.addresses
|
await isar.writeTxn(() async {
|
||||||
.getByValueWalletId(address.value, address.walletId);
|
for (final address in addresses) {
|
||||||
|
final storedAddress = await isar.addresses
|
||||||
|
.getByValueWalletId(address.value, address.walletId);
|
||||||
|
|
||||||
if (storedAddress == null) {
|
int id;
|
||||||
await isar.addresses.put(address);
|
if (storedAddress == null) {
|
||||||
} else {
|
id = await isar.addresses.put(address);
|
||||||
address.id = storedAddress.id;
|
} else {
|
||||||
await storedAddress.transactions.load();
|
address.id = storedAddress.id;
|
||||||
final txns = storedAddress.transactions.toList();
|
await storedAddress.transactions.load();
|
||||||
await isar.addresses.delete(storedAddress.id);
|
final txns = storedAddress.transactions.toList();
|
||||||
await isar.addresses.put(address);
|
await isar.addresses.delete(storedAddress.id);
|
||||||
address.transactions.addAll(txns);
|
id = await isar.addresses.put(address);
|
||||||
await address.transactions.save();
|
address.transactions.addAll(txns);
|
||||||
|
await address.transactions.save();
|
||||||
|
}
|
||||||
|
ids.add(id);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
return ids;
|
||||||
|
} catch (e) {
|
||||||
|
throw MainDBException("failed updateOrPutAddresses: $addresses", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Address?> getAddress(String walletId, String address) async {
|
Future<Address?> getAddress(String walletId, String address) async {
|
||||||
return isar.addresses.getByValueWalletId(address, walletId);
|
return isar.addresses.getByValueWalletId(address, walletId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateAddress(Address oldAddress, Address newAddress) =>
|
Future<int> updateAddress(Address oldAddress, Address newAddress) async {
|
||||||
isar.writeTxn(() async {
|
try {
|
||||||
|
return await isar.writeTxn(() async {
|
||||||
newAddress.id = oldAddress.id;
|
newAddress.id = oldAddress.id;
|
||||||
await oldAddress.transactions.load();
|
await oldAddress.transactions.load();
|
||||||
final txns = oldAddress.transactions.toList();
|
final txns = oldAddress.transactions.toList();
|
||||||
await isar.addresses.delete(oldAddress.id);
|
await isar.addresses.delete(oldAddress.id);
|
||||||
await isar.addresses.put(newAddress);
|
final id = await isar.addresses.put(newAddress);
|
||||||
newAddress.transactions.addAll(txns);
|
newAddress.transactions.addAll(txns);
|
||||||
await newAddress.transactions.save();
|
await newAddress.transactions.save();
|
||||||
|
return id;
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw MainDBException(
|
||||||
|
"failed updateAddress: from=$oldAddress to=$newAddress", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// transactions
|
// transactions
|
||||||
QueryBuilder<Transaction, Transaction, QAfterWhereClause> getTransactions(
|
QueryBuilder<Transaction, Transaction, QAfterWhereClause> getTransactions(
|
||||||
String walletId) =>
|
String walletId) =>
|
||||||
isar.transactions.where().walletIdEqualTo(walletId);
|
isar.transactions.where().walletIdEqualTo(walletId);
|
||||||
|
|
||||||
Future<void> putTransaction(Transaction transaction) =>
|
Future<int> putTransaction(Transaction transaction) async {
|
||||||
isar.writeTxn(() async {
|
try {
|
||||||
await isar.transactions.put(transaction);
|
return await isar.writeTxn(() async {
|
||||||
|
return await isar.transactions.put(transaction);
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw MainDBException("failed putTransaction: $transaction", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> putTransactions(List<Transaction> transactions) =>
|
Future<List<int>> putTransactions(List<Transaction> transactions) async {
|
||||||
isar.writeTxn(() async {
|
try {
|
||||||
await isar.transactions.putAll(transactions);
|
return await isar.writeTxn(() async {
|
||||||
|
return await isar.transactions.putAll(transactions);
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw MainDBException("failed putTransactions: $transactions", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Transaction?> getTransaction(String walletId, String txid) async {
|
Future<Transaction?> getTransaction(String walletId, String txid) async {
|
||||||
return isar.transactions.getByTxidWalletId(txid, walletId);
|
return isar.transactions.getByTxidWalletId(txid, walletId);
|
||||||
|
@ -212,55 +250,60 @@ class MainDB {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addNewTransactionData(
|
Future<void> addNewTransactionData(
|
||||||
List<Tuple4<Transaction, List<Output>, List<Input>, Address?>>
|
List<Tuple4<Transaction, List<Output>, List<Input>, Address?>>
|
||||||
transactionsData,
|
transactionsData,
|
||||||
String walletId) async {
|
String walletId,
|
||||||
await isar.writeTxn(() async {
|
) async {
|
||||||
for (final data in transactionsData) {
|
try {
|
||||||
final tx = data.item1;
|
await isar.writeTxn(() async {
|
||||||
|
for (final data in transactionsData) {
|
||||||
|
final tx = data.item1;
|
||||||
|
|
||||||
final potentiallyUnconfirmedTx = await getTransactions(walletId)
|
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()
|
.filter()
|
||||||
.valueEqualTo(data.item4!.value)
|
.txidEqualTo(tx.txid)
|
||||||
.findFirst();
|
.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);
|
||||||
|
|
||||||
// check if address exists in db and add if it does not
|
// link and save outputs
|
||||||
if (address == null) {
|
if (data.item2.isNotEmpty) {
|
||||||
await isar.addresses.put(data.item4!);
|
await isar.outputs.putAll(data.item2);
|
||||||
|
tx.outputs.addAll(data.item2);
|
||||||
|
await tx.outputs.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// link and save address
|
// link and save inputs
|
||||||
tx.address.value = address ?? data.item4!;
|
if (data.item3.isNotEmpty) {
|
||||||
await tx.address.save();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
} catch (e) {
|
||||||
|
throw MainDBException("failed addNewTransactionData", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
lib/exceptions/main_db/main_db_exception.dart
Normal file
12
lib/exceptions/main_db/main_db_exception.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import 'package:stackwallet/exceptions/sw_exception.dart';
|
||||||
|
|
||||||
|
class MainDBException extends SWException {
|
||||||
|
MainDBException(super.message, this.originalError);
|
||||||
|
|
||||||
|
final Object originalError;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "$message: originalError=$originalError";
|
||||||
|
}
|
||||||
|
}
|
|
@ -176,7 +176,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
||||||
children: [
|
children: [
|
||||||
PayNymBot(
|
PayNymBot(
|
||||||
paymentCodeString: widget.accountLite.code,
|
paymentCodeString: widget.accountLite.code,
|
||||||
size: 32,
|
size: 36,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 12,
|
width: 12,
|
||||||
|
@ -186,7 +186,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.accountLite.nymName,
|
widget.accountLite.nymName,
|
||||||
style: STextStyles.w600_12(context),
|
style: STextStyles.w600_14(context),
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future:
|
future:
|
||||||
|
@ -204,7 +204,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"Connected",
|
"Connected",
|
||||||
style: STextStyles.w500_10(context)
|
style: STextStyles.w500_12(context)
|
||||||
.copyWith(
|
.copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
|
@ -230,33 +230,33 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
||||||
if (snapshot.data!) {
|
if (snapshot.data!) {
|
||||||
return PrimaryButton(
|
return PrimaryButton(
|
||||||
label: "Send",
|
label: "Send",
|
||||||
buttonHeight: ButtonHeight.l,
|
buttonHeight: ButtonHeight.xl,
|
||||||
icon: SvgPicture.asset(
|
icon: SvgPicture.asset(
|
||||||
Assets.svg.circleArrowUpRight,
|
Assets.svg.circleArrowUpRight,
|
||||||
width: 10,
|
width: 14,
|
||||||
height: 10,
|
height: 14,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.buttonTextPrimary,
|
.buttonTextPrimary,
|
||||||
),
|
),
|
||||||
iconSpacing: 4,
|
iconSpacing: 8,
|
||||||
width: 86,
|
width: 100,
|
||||||
onPressed: _onSend,
|
onPressed: _onSend,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return PrimaryButton(
|
return PrimaryButton(
|
||||||
label: "Connect",
|
label: "Connect",
|
||||||
buttonHeight: ButtonHeight.l,
|
buttonHeight: ButtonHeight.xl,
|
||||||
icon: SvgPicture.asset(
|
icon: SvgPicture.asset(
|
||||||
Assets.svg.circlePlusFilled,
|
Assets.svg.circlePlusFilled,
|
||||||
width: 10,
|
width: 13,
|
||||||
height: 10,
|
height: 13,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.buttonTextPrimary,
|
.buttonTextPrimary,
|
||||||
),
|
),
|
||||||
iconSpacing: 4,
|
iconSpacing: 8,
|
||||||
width: 86,
|
width: 128,
|
||||||
onPressed: _onConnectPressed,
|
onPressed: _onConnectPressed,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -291,6 +291,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.warningForeground,
|
.warningForeground,
|
||||||
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -321,7 +322,9 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"PayNym address",
|
"PayNym address",
|
||||||
style: STextStyles.infoSmall(context),
|
style: STextStyles.infoSmall(context).copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 6,
|
height: 6,
|
||||||
|
@ -332,6 +335,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.textDark,
|
.textDark,
|
||||||
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
@ -346,7 +350,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
||||||
),
|
),
|
||||||
QrImage(
|
QrImage(
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
size: 86,
|
size: 100,
|
||||||
data: widget.accountLite.code,
|
data: widget.accountLite.code,
|
||||||
foregroundColor:
|
foregroundColor:
|
||||||
Theme.of(context).extension<StackColors>()!.textDark,
|
Theme.of(context).extension<StackColors>()!.textDark,
|
||||||
|
@ -375,16 +379,16 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SecondaryButton(
|
child: SecondaryButton(
|
||||||
label: "Copy",
|
label: "Copy",
|
||||||
buttonHeight: ButtonHeight.l,
|
buttonHeight: ButtonHeight.xl,
|
||||||
|
iconSpacing: 8,
|
||||||
icon: SvgPicture.asset(
|
icon: SvgPicture.asset(
|
||||||
Assets.svg.copy,
|
Assets.svg.copy,
|
||||||
width: 10,
|
width: 12,
|
||||||
height: 10,
|
height: 12,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.buttonTextSecondary,
|
.buttonTextSecondary,
|
||||||
),
|
),
|
||||||
iconSpacing: 4,
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await Clipboard.setData(
|
await Clipboard.setData(
|
||||||
ClipboardData(
|
ClipboardData(
|
||||||
|
|
|
@ -56,7 +56,7 @@ class PaynymQrPopup extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
PayNymBot(
|
PayNymBot(
|
||||||
paymentCodeString: paynymAccount.codes.first.code,
|
paymentCodeString: paynymAccount.codes.first.code,
|
||||||
size: isDesktop ? 56 : 32,
|
size: isDesktop ? 56 : 36,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 12,
|
width: 12,
|
||||||
|
@ -65,7 +65,7 @@ class PaynymQrPopup extends StatelessWidget {
|
||||||
paynymAccount.nymName,
|
paynymAccount.nymName,
|
||||||
style: isDesktop
|
style: isDesktop
|
||||||
? STextStyles.w500_24(context)
|
? STextStyles.w500_24(context)
|
||||||
: STextStyles.w600_12(context),
|
: STextStyles.w600_14(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -87,7 +87,7 @@ class PaynymQrPopup extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(minHeight: 107),
|
constraints: const BoxConstraints(minHeight: 130),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
@ -100,7 +100,9 @@ class PaynymQrPopup extends StatelessWidget {
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.textSubtitle1,
|
.textSubtitle1,
|
||||||
)
|
)
|
||||||
: STextStyles.infoSmall(context),
|
: STextStyles.infoSmall(context).copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 6,
|
height: 6,
|
||||||
|
@ -113,6 +115,7 @@ class PaynymQrPopup extends StatelessWidget {
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.textDark,
|
.textDark,
|
||||||
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
@ -120,7 +123,7 @@ class PaynymQrPopup extends StatelessWidget {
|
||||||
),
|
),
|
||||||
CustomTextButton(
|
CustomTextButton(
|
||||||
text: "Copy",
|
text: "Copy",
|
||||||
textSize: isDesktop ? 18 : 10,
|
textSize: isDesktop ? 18 : 14,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await Clipboard.setData(
|
await Clipboard.setData(
|
||||||
ClipboardData(
|
ClipboardData(
|
||||||
|
@ -146,7 +149,7 @@ class PaynymQrPopup extends StatelessWidget {
|
||||||
),
|
),
|
||||||
QrImage(
|
QrImage(
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
size: 107,
|
size: 130,
|
||||||
data: paynymAccount.codes.first.code,
|
data: paynymAccount.codes.first.code,
|
||||||
foregroundColor:
|
foregroundColor:
|
||||||
Theme.of(context).extension<StackColors>()!.textDark,
|
Theme.of(context).extension<StackColors>()!.textDark,
|
||||||
|
|
|
@ -303,7 +303,9 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
||||||
.code,
|
.code,
|
||||||
12,
|
12,
|
||||||
5),
|
5),
|
||||||
style: STextStyles.label(context),
|
style: STextStyles.label(context).copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 11,
|
height: 11,
|
||||||
|
@ -313,11 +315,11 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SecondaryButton(
|
child: SecondaryButton(
|
||||||
label: "Copy",
|
label: "Copy",
|
||||||
buttonHeight: ButtonHeight.l,
|
buttonHeight: ButtonHeight.xl,
|
||||||
iconSpacing: 4,
|
iconSpacing: 8,
|
||||||
icon: CopyIcon(
|
icon: CopyIcon(
|
||||||
width: 10,
|
width: 12,
|
||||||
height: 10,
|
height: 12,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.textDark,
|
.textDark,
|
||||||
|
@ -350,11 +352,11 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SecondaryButton(
|
child: SecondaryButton(
|
||||||
label: "Share",
|
label: "Share",
|
||||||
buttonHeight: ButtonHeight.l,
|
buttonHeight: ButtonHeight.xl,
|
||||||
iconSpacing: 4,
|
iconSpacing: 8,
|
||||||
icon: ShareIcon(
|
icon: ShareIcon(
|
||||||
width: 10,
|
width: 12,
|
||||||
height: 10,
|
height: 12,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.textDark,
|
.textDark,
|
||||||
|
@ -387,11 +389,11 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SecondaryButton(
|
child: SecondaryButton(
|
||||||
label: "Address",
|
label: "Address",
|
||||||
buttonHeight: ButtonHeight.l,
|
buttonHeight: ButtonHeight.xl,
|
||||||
iconSpacing: 4,
|
iconSpacing: 8,
|
||||||
icon: QrCodeIcon(
|
icon: QrCodeIcon(
|
||||||
width: 10,
|
width: 12,
|
||||||
height: 10,
|
height: 12,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.textDark,
|
.textDark,
|
||||||
|
@ -554,7 +556,7 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: isDesktop ? 56 : 40,
|
height: isDesktop ? 56 : 48,
|
||||||
width: isDesktop ? 490 : null,
|
width: isDesktop ? 490 : null,
|
||||||
child: Toggle(
|
child: Toggle(
|
||||||
onColor: Theme.of(context).extension<StackColors>()!.popupBG,
|
onColor: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||||
|
|
|
@ -171,7 +171,7 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
|
||||||
children: [
|
children: [
|
||||||
PayNymBot(
|
PayNymBot(
|
||||||
paymentCodeString: widget.accountLite.code,
|
paymentCodeString: widget.accountLite.code,
|
||||||
size: 32,
|
size: 36,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 12,
|
width: 12,
|
||||||
|
|
|
@ -37,7 +37,7 @@ class _PaynymCardState extends State<PaynymCard> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
PayNymBot(
|
PayNymBot(
|
||||||
size: 32,
|
size: 36,
|
||||||
paymentCodeString: widget.paymentCodeString,
|
paymentCodeString: widget.paymentCodeString,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
@ -56,7 +56,7 @@ class _PaynymCardState extends State<PaynymCard> {
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.textFieldActiveText,
|
.textFieldActiveText,
|
||||||
)
|
)
|
||||||
: STextStyles.w500_12(context),
|
: STextStyles.w500_14(context),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 2,
|
height: 2,
|
||||||
|
@ -65,7 +65,7 @@ class _PaynymCardState extends State<PaynymCard> {
|
||||||
Format.shorten(widget.paymentCodeString, 12, 5),
|
Format.shorten(widget.paymentCodeString, 12, 5),
|
||||||
style: isDesktop
|
style: isDesktop
|
||||||
? STextStyles.desktopTextExtraExtraSmall(context)
|
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
: STextStyles.w500_12(context).copyWith(
|
: STextStyles.w500_14(context).copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.textSubtitle1,
|
.textSubtitle1,
|
||||||
|
|
|
@ -77,7 +77,7 @@ class _PaynymCardButtonState extends ConsumerState<PaynymCardButton> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
PayNymBot(
|
PayNymBot(
|
||||||
size: 32,
|
size: 36,
|
||||||
paymentCodeString: widget.accountLite.code,
|
paymentCodeString: widget.accountLite.code,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
@ -96,7 +96,7 @@ class _PaynymCardButtonState extends ConsumerState<PaynymCardButton> {
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.textFieldActiveText,
|
.textFieldActiveText,
|
||||||
)
|
)
|
||||||
: STextStyles.w500_12(context),
|
: STextStyles.w500_14(context),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 2,
|
height: 2,
|
||||||
|
@ -105,7 +105,7 @@ class _PaynymCardButtonState extends ConsumerState<PaynymCardButton> {
|
||||||
Format.shorten(widget.accountLite.code, 12, 5),
|
Format.shorten(widget.accountLite.code, 12, 5),
|
||||||
style: isDesktop
|
style: isDesktop
|
||||||
? STextStyles.desktopTextExtraExtraSmall(context)
|
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||||
: STextStyles.w500_12(context).copyWith(
|
: STextStyles.w500_14(context).copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.textSubtitle1,
|
.textSubtitle1,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:bech32/bech32.dart';
|
import 'package:bech32/bech32.dart';
|
||||||
import 'package:bip32/bip32.dart' as bip32;
|
import 'package:bip32/bip32.dart' as bip32;
|
||||||
|
@ -980,6 +981,7 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
|
||||||
await _checkCurrentReceivingAddressesForTransactions();
|
await _checkCurrentReceivingAddressesForTransactions();
|
||||||
|
|
||||||
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.4, walletId));
|
||||||
await checkAllCurrentReceivingPaynymAddressesForTransactions();
|
await checkAllCurrentReceivingPaynymAddressesForTransactions();
|
||||||
|
|
||||||
final fetchFuture = _refreshTransactions();
|
final fetchFuture = _refreshTransactions();
|
||||||
|
@ -2372,27 +2374,38 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int vSizeForOneOutput = (await buildTransaction(
|
final int vSizeForOneOutput;
|
||||||
utxosToUse: utxoObjectsToUse,
|
try {
|
||||||
utxoSigningData: utxoSigningData,
|
vSizeForOneOutput = (await buildTransaction(
|
||||||
recipients: [_recipientAddress],
|
utxosToUse: utxoObjectsToUse,
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
utxoSigningData: utxoSigningData,
|
||||||
))["vSize"] as int;
|
recipients: [_recipientAddress],
|
||||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
utxosToUse: utxoObjectsToUse,
|
))["vSize"] as int;
|
||||||
utxoSigningData: utxoSigningData,
|
} catch (e) {
|
||||||
recipients: [
|
Logging.instance.log("vSizeForOneOutput: $e", level: LogLevel.Error);
|
||||||
_recipientAddress,
|
rethrow;
|
||||||
await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)),
|
}
|
||||||
],
|
|
||||||
satoshiAmounts: [
|
final int vSizeForTwoOutPuts;
|
||||||
satoshiAmountToSend,
|
try {
|
||||||
// this can cause a problem where the output value is negative so commenting out for now
|
vSizeForTwoOutPuts = (await buildTransaction(
|
||||||
// satoshisBeingUsed - satoshiAmountToSend - 1
|
utxosToUse: utxoObjectsToUse,
|
||||||
// and using dust limit instead
|
utxoSigningData: utxoSigningData,
|
||||||
DUST_LIMIT,
|
recipients: [
|
||||||
], // dust limit is the minimum amount a change output should be
|
_recipientAddress,
|
||||||
))["vSize"] as int;
|
await _getCurrentAddressForChain(
|
||||||
|
1, DerivePathTypeExt.primaryFor(coin)),
|
||||||
|
],
|
||||||
|
satoshiAmounts: [
|
||||||
|
satoshiAmountToSend,
|
||||||
|
max(0, satoshisBeingUsed - satoshiAmountToSend - 1),
|
||||||
|
],
|
||||||
|
))["vSize"] as int;
|
||||||
|
} catch (e) {
|
||||||
|
Logging.instance.log("vSizeForTwoOutPuts: $e", level: LogLevel.Error);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
// Assume 1 output, only for recipient and no change
|
// Assume 1 output, only for recipient and no change
|
||||||
final feeForOneOutput = estimateTxFee(
|
final feeForOneOutput = estimateTxFee(
|
||||||
|
|
|
@ -148,15 +148,13 @@ mixin PaynymWalletInterface {
|
||||||
btc_dart.NetworkType get networkType => _network;
|
btc_dart.NetworkType get networkType => _network;
|
||||||
|
|
||||||
Future<Address> currentReceivingPaynymAddress(PaymentCode sender) async {
|
Future<Address> currentReceivingPaynymAddress(PaymentCode sender) async {
|
||||||
final key = await lookupKey(sender.toString());
|
final keys = await lookupKey(sender.toString());
|
||||||
final address = await _db
|
final address = await _db
|
||||||
.getAddresses(_walletId)
|
.getAddresses(_walletId)
|
||||||
.filter()
|
.filter()
|
||||||
.subTypeEqualTo(AddressSubType.paynymReceive)
|
.subTypeEqualTo(AddressSubType.paynymReceive)
|
||||||
.and()
|
.and()
|
||||||
.otherDataEqualTo(key)
|
.anyOf<String, Address>(keys, (q, String e) => q.otherDataEqualTo(e))
|
||||||
.and()
|
|
||||||
.otherDataIsNotNull()
|
|
||||||
.sortByDerivationIndexDesc()
|
.sortByDerivationIndexDesc()
|
||||||
.findFirst();
|
.findFirst();
|
||||||
|
|
||||||
|
@ -331,15 +329,13 @@ mixin PaynymWalletInterface {
|
||||||
const maxCount = 2147483647;
|
const maxCount = 2147483647;
|
||||||
|
|
||||||
for (int i = startIndex; i < maxCount; i++) {
|
for (int i = startIndex; i < maxCount; i++) {
|
||||||
final key = await lookupKey(pCode.toString());
|
final keys = await lookupKey(pCode.toString());
|
||||||
final address = await _db
|
final address = await _db
|
||||||
.getAddresses(_walletId)
|
.getAddresses(_walletId)
|
||||||
.filter()
|
.filter()
|
||||||
.subTypeEqualTo(AddressSubType.paynymSend)
|
.subTypeEqualTo(AddressSubType.paynymSend)
|
||||||
.and()
|
.and()
|
||||||
.otherDataEqualTo(key)
|
.anyOf<String, Address>(keys, (q, String e) => q.otherDataEqualTo(e))
|
||||||
.and()
|
|
||||||
.otherDataIsNotNull()
|
|
||||||
.and()
|
.and()
|
||||||
.derivationIndexEqualTo(i)
|
.derivationIndexEqualTo(i)
|
||||||
.findFirst();
|
.findFirst();
|
||||||
|
@ -1215,16 +1211,17 @@ mixin PaynymWalletInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// look up a key that corresponds to a payment code string
|
/// look up a key that corresponds to a payment code string
|
||||||
Future<String?> lookupKey(String paymentCodeString) async {
|
Future<List<String>> lookupKey(String paymentCodeString) async {
|
||||||
final keys =
|
final keys =
|
||||||
(await _secureStorage.keys).where((e) => e.startsWith(kPCodeKeyPrefix));
|
(await _secureStorage.keys).where((e) => e.startsWith(kPCodeKeyPrefix));
|
||||||
|
final List<String> result = [];
|
||||||
for (final key in keys) {
|
for (final key in keys) {
|
||||||
final value = await _secureStorage.read(key: key);
|
final value = await _secureStorage.read(key: key);
|
||||||
if (value == paymentCodeString) {
|
if (value == paymentCodeString) {
|
||||||
return key;
|
result.add(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// fetch a payment code string
|
/// fetch a payment code string
|
||||||
|
|
|
@ -13,31 +13,31 @@ class STextStyles {
|
||||||
return GoogleFonts.inter(
|
return GoogleFonts.inter(
|
||||||
color: _theme(context).textDark3,
|
color: _theme(context).textDark3,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 12,
|
fontSize: 14,
|
||||||
);
|
);
|
||||||
case ThemeType.oceanBreeze:
|
case ThemeType.oceanBreeze:
|
||||||
return GoogleFonts.inter(
|
return GoogleFonts.inter(
|
||||||
color: _theme(context).textDark3,
|
color: _theme(context).textDark3,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 12,
|
fontSize: 14,
|
||||||
);
|
);
|
||||||
case ThemeType.dark:
|
case ThemeType.dark:
|
||||||
return GoogleFonts.inter(
|
return GoogleFonts.inter(
|
||||||
color: _theme(context).textDark3,
|
color: _theme(context).textDark3,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 12,
|
fontSize: 14,
|
||||||
);
|
);
|
||||||
case ThemeType.oledBlack:
|
case ThemeType.oledBlack:
|
||||||
return GoogleFonts.inter(
|
return GoogleFonts.inter(
|
||||||
color: _theme(context).textDark3,
|
color: _theme(context).textDark3,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 12,
|
fontSize: 14,
|
||||||
);
|
);
|
||||||
case ThemeType.fruitSorbet:
|
case ThemeType.fruitSorbet:
|
||||||
return GoogleFonts.inter(
|
return GoogleFonts.inter(
|
||||||
color: _theme(context).textDark3,
|
color: _theme(context).textDark3,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 12,
|
fontSize: 14,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -932,6 +932,76 @@ class STextStyles {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static TextStyle w600_14(BuildContext context) {
|
||||||
|
switch (_theme(context).themeType) {
|
||||||
|
case ThemeType.light:
|
||||||
|
return GoogleFonts.inter(
|
||||||
|
color: _theme(context).textDark,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
case ThemeType.oceanBreeze:
|
||||||
|
return GoogleFonts.inter(
|
||||||
|
color: _theme(context).textDark,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
case ThemeType.dark:
|
||||||
|
return GoogleFonts.inter(
|
||||||
|
color: _theme(context).textDark,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
case ThemeType.oledBlack:
|
||||||
|
return GoogleFonts.inter(
|
||||||
|
color: _theme(context).textDark,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
case ThemeType.fruitSorbet:
|
||||||
|
return GoogleFonts.inter(
|
||||||
|
color: _theme(context).textDark,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TextStyle w500_14(BuildContext context) {
|
||||||
|
switch (_theme(context).themeType) {
|
||||||
|
case ThemeType.light:
|
||||||
|
return GoogleFonts.inter(
|
||||||
|
color: _theme(context).textDark,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
case ThemeType.oceanBreeze:
|
||||||
|
return GoogleFonts.inter(
|
||||||
|
color: _theme(context).textDark,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
case ThemeType.dark:
|
||||||
|
return GoogleFonts.inter(
|
||||||
|
color: _theme(context).textDark,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
case ThemeType.oledBlack:
|
||||||
|
return GoogleFonts.inter(
|
||||||
|
color: _theme(context).textDark,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
case ThemeType.fruitSorbet:
|
||||||
|
return GoogleFonts.inter(
|
||||||
|
color: _theme(context).textDark,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static TextStyle w500_12(BuildContext context) {
|
static TextStyle w500_12(BuildContext context) {
|
||||||
switch (_theme(context).themeType) {
|
switch (_theme(context).themeType) {
|
||||||
case ThemeType.light:
|
case ThemeType.light:
|
||||||
|
|
|
@ -272,8 +272,8 @@ class _PaynymFollowToggleButtonState
|
||||||
switch (widget.style) {
|
switch (widget.style) {
|
||||||
case PaynymFollowToggleButtonStyle.primary:
|
case PaynymFollowToggleButtonStyle.primary:
|
||||||
return PrimaryButton(
|
return PrimaryButton(
|
||||||
width: isDesktop ? 120 : 84,
|
width: isDesktop ? 120 : 100,
|
||||||
buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.l,
|
buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.xl,
|
||||||
label: isFollowing ? "Unfollow" : "Follow",
|
label: isFollowing ? "Unfollow" : "Follow",
|
||||||
onPressed: _onPressed,
|
onPressed: _onPressed,
|
||||||
);
|
);
|
||||||
|
@ -281,15 +281,15 @@ class _PaynymFollowToggleButtonState
|
||||||
case PaynymFollowToggleButtonStyle.detailsPopup:
|
case PaynymFollowToggleButtonStyle.detailsPopup:
|
||||||
return SecondaryButton(
|
return SecondaryButton(
|
||||||
label: isFollowing ? "Unfollow" : "Follow",
|
label: isFollowing ? "Unfollow" : "Follow",
|
||||||
buttonHeight: ButtonHeight.l,
|
buttonHeight: ButtonHeight.xl,
|
||||||
|
iconSpacing: 8,
|
||||||
icon: SvgPicture.asset(
|
icon: SvgPicture.asset(
|
||||||
isFollowing ? Assets.svg.userMinus : Assets.svg.userPlus,
|
isFollowing ? Assets.svg.userMinus : Assets.svg.userPlus,
|
||||||
width: 10,
|
width: 16,
|
||||||
height: 10,
|
height: 16,
|
||||||
color:
|
color:
|
||||||
Theme.of(context).extension<StackColors>()!.buttonTextSecondary,
|
Theme.of(context).extension<StackColors>()!.buttonTextSecondary,
|
||||||
),
|
),
|
||||||
iconSpacing: 4,
|
|
||||||
onPressed: _onPressed,
|
onPressed: _onPressed,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,16 @@ class SecondaryButton extends StatelessWidget {
|
||||||
.buttonTextSecondaryDisabled,
|
.buttonTextSecondaryDisabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (buttonHeight == ButtonHeight.xl) {
|
||||||
|
return STextStyles.button(context).copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
color: enabled
|
||||||
|
? Theme.of(context).extension<StackColors>()!.buttonTextSecondary
|
||||||
|
: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonTextSecondaryDisabled,
|
||||||
|
);
|
||||||
|
}
|
||||||
return STextStyles.button(context).copyWith(
|
return STextStyles.button(context).copyWith(
|
||||||
color: enabled
|
color: enabled
|
||||||
? Theme.of(context).extension<StackColors>()!.buttonTextSecondary
|
? Theme.of(context).extension<StackColors>()!.buttonTextSecondary
|
||||||
|
|
Loading…
Reference in a new issue