Merge pull request #334 from cypherstack/paynyms

Paynyms
This commit is contained in:
Diego Salazar 2023-02-01 16:25:31 -07:00 committed by GitHub
commit bd05d6dddf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 316 additions and 162 deletions

View file

@ -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);
}
} }
} }

View 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";
}
}

View file

@ -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(

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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(

View file

@ -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

View file

@ -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:

View file

@ -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,
); );

View file

@ -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