mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-23 20:09:23 +00:00
commit
5252c3efff
21 changed files with 366 additions and 236 deletions
|
@ -108,9 +108,36 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
static Decimal maxCrypto = Decimal.parse((10000.00000000).toString());
|
||||
static String boundedCryptoTicker = '';
|
||||
|
||||
void fiatFieldOnChanged(String value) async {}
|
||||
String _amountOutOfRangeErrorString = "";
|
||||
void validateAmount() {
|
||||
if (_buyAmountController.text.isEmpty) {
|
||||
setState(() {
|
||||
_amountOutOfRangeErrorString = "";
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
void cryptoFieldOnChanged(String value) async {}
|
||||
final value = Decimal.tryParse(_buyAmountController.text);
|
||||
if (value == null) {
|
||||
setState(() {
|
||||
_amountOutOfRangeErrorString = "Invalid amount";
|
||||
});
|
||||
} else if (value > maxFiat && buyWithFiat) {
|
||||
setState(() {
|
||||
_amountOutOfRangeErrorString =
|
||||
"Maximum amount: ${maxFiat.toStringAsFixed(2)}";
|
||||
});
|
||||
} else if (value < minFiat && buyWithFiat) {
|
||||
setState(() {
|
||||
_amountOutOfRangeErrorString =
|
||||
"Minimum amount: ${minFiat.toStringAsFixed(2)}";
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_amountOutOfRangeErrorString = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void selectCrypto() async {
|
||||
if (ref.read(simplexProvider).supportedCryptos.isEmpty) {
|
||||
|
@ -253,6 +280,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
minFiat = fiat.minAmount != minFiat ? fiat.minAmount : minFiat;
|
||||
maxFiat = fiat.maxAmount != maxFiat ? fiat.maxAmount : maxFiat;
|
||||
});
|
||||
validateAmount();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -359,11 +387,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
}
|
||||
|
||||
String? _fetchIconUrlFromTicker(String? ticker) {
|
||||
if (ticker == null) return null;
|
||||
|
||||
return null;
|
||||
}
|
||||
// String? _fetchIconUrlFromTicker(String? ticker) {
|
||||
// if (ticker == null) return null;
|
||||
//
|
||||
// return null;
|
||||
// }
|
||||
|
||||
bool isStackCoin(String? ticker) {
|
||||
if (ticker == null) return false;
|
||||
|
@ -377,13 +405,15 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
|
||||
Widget? getIconForTicker(String ticker) {
|
||||
String? iconAsset = /*isStackCoin(ticker)
|
||||
String iconAsset = /*isStackCoin(ticker)
|
||||
?*/
|
||||
Assets.svg.iconFor(coin: coinFromTickerCaseInsensitive(ticker));
|
||||
// : Assets.svg.buyIconFor(ticker);
|
||||
return (iconAsset != null)
|
||||
? SvgPicture.asset(iconAsset, height: 20, width: 20)
|
||||
: null;
|
||||
// return (iconAsset != null)
|
||||
// ? SvgPicture.asset(iconAsset, height: 20, width: 20)
|
||||
// : null;
|
||||
|
||||
return SvgPicture.asset(iconAsset, height: 20, width: 20);
|
||||
}
|
||||
|
||||
Future<void> previewQuote(SimplexQuote quote) async {
|
||||
|
@ -501,25 +531,25 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
} else {
|
||||
// Error; probably amount out of bounds
|
||||
String errorMessage = "${quoteResponse.exception?.errorMessage}";
|
||||
if (errorMessage.contains('must be between')) {
|
||||
errorMessage = errorMessage.substring(
|
||||
(errorMessage.indexOf('getQuote exception: ') ?? 19) + 20,
|
||||
errorMessage.indexOf(", value: null"));
|
||||
_BuyFormState.boundedCryptoTicker = errorMessage.substring(
|
||||
errorMessage.indexOf('The ') + 4,
|
||||
errorMessage.indexOf(' amount must be between'));
|
||||
_BuyFormState.minCrypto = Decimal.parse(errorMessage.substring(
|
||||
errorMessage.indexOf('must be between ') + 16,
|
||||
errorMessage.indexOf(' and ')));
|
||||
_BuyFormState.maxCrypto = Decimal.parse(errorMessage.substring(
|
||||
errorMessage.indexOf("$minCrypto and ") + "$minCrypto and ".length,
|
||||
errorMessage.length));
|
||||
if (Decimal.parse(_buyAmountController.text) >
|
||||
_BuyFormState.maxCrypto) {
|
||||
_buyAmountController.text = _BuyFormState.maxCrypto.toString();
|
||||
}
|
||||
}
|
||||
// String errorMessage = "${quoteResponse.exception?.errorMessage}";
|
||||
// if (errorMessage.contains('must be between')) {
|
||||
// errorMessage = errorMessage.substring(
|
||||
// errorMessage.indexOf('getQuote exception: ') + 20,
|
||||
// errorMessage.indexOf(", value: null"));
|
||||
// _BuyFormState.boundedCryptoTicker = errorMessage.substring(
|
||||
// errorMessage.indexOf('The ') + 4,
|
||||
// errorMessage.indexOf(' amount must be between'));
|
||||
// _BuyFormState.minCrypto = Decimal.parse(errorMessage.substring(
|
||||
// errorMessage.indexOf('must be between ') + 16,
|
||||
// errorMessage.indexOf(' and ')));
|
||||
// _BuyFormState.maxCrypto = Decimal.parse(errorMessage.substring(
|
||||
// errorMessage.indexOf("$minCrypto and ") + "$minCrypto and ".length,
|
||||
// errorMessage.length));
|
||||
// if (Decimal.parse(_buyAmountController.text) >
|
||||
// _BuyFormState.maxCrypto) {
|
||||
// _buyAmountController.text = _BuyFormState.maxCrypto.toString();
|
||||
// }
|
||||
// }
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
|
@ -541,7 +571,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
height: 24,
|
||||
),
|
||||
Text(
|
||||
errorMessage,
|
||||
quoteResponse.exception!.errorMessage,
|
||||
style: STextStyles.smallMed14(context),
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -603,7 +633,8 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
level: LogLevel.Warning,
|
||||
);
|
||||
return BuyResponse(
|
||||
exception: BuyException(
|
||||
exception: response.exception ??
|
||||
BuyException(
|
||||
response.toString(),
|
||||
BuyExceptionType.generic,
|
||||
),
|
||||
|
@ -852,7 +883,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
.textFieldDefaultBG,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0, top: 12.0, right: 12.0, bottom: 12.0),
|
||||
left: 12.0,
|
||||
top: 12.0,
|
||||
right: 12.0,
|
||||
bottom: 12.0,
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
|
@ -878,7 +913,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
width: 8,
|
||||
),
|
||||
Text(
|
||||
"${selectedFiat?.ticker ?? 'ERR'}",
|
||||
selectedFiat?.ticker ?? 'ERR',
|
||||
style: STextStyles.largeMedium14(context),
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -886,7 +921,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"${selectedFiat?.name ?? 'Error'}",
|
||||
selectedFiat?.name ?? 'Error',
|
||||
style: STextStyles.largeMedium14(context),
|
||||
),
|
||||
),
|
||||
|
@ -924,6 +959,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
setState(() {
|
||||
buyWithFiat = !buyWithFiat;
|
||||
});
|
||||
validateAmount();
|
||||
},
|
||||
)
|
||||
],
|
||||
|
@ -938,10 +974,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||
),
|
||||
key: const Key("buyAmountInputFieldTextFieldKey"),
|
||||
controller: _buyAmountController
|
||||
..text = _BuyFormState.buyWithFiat
|
||||
? _BuyFormState.minFiat.toStringAsFixed(2) ?? '50.00'
|
||||
: _BuyFormState.minCrypto.toStringAsFixed(8),
|
||||
controller: _buyAmountController,
|
||||
// note: setting the text value here will set it every time this widget rebuilds
|
||||
// ..text = _BuyFormState.buyWithFiat
|
||||
// ? _BuyFormState.minFiat.toStringAsFixed(2) ?? '50.00'
|
||||
// : _BuyFormState.minCrypto.toStringAsFixed(8),
|
||||
focusNode: _buyAmountFocusNode,
|
||||
keyboardType: Util.isDesktop
|
||||
? null
|
||||
|
@ -950,7 +987,10 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
decimal: true,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
inputFormatters: [NumericalRangeFormatter()],
|
||||
// inputFormatters: [NumericalRangeFormatter()],
|
||||
onChanged: (_) {
|
||||
validateAmount();
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
// top: 22,
|
||||
|
@ -985,8 +1025,8 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
),
|
||||
child: Text(
|
||||
format.simpleCurrencySymbol(
|
||||
selectedFiat?.ticker ??
|
||||
"ERR".toUpperCase()),
|
||||
selectedFiat?.ticker.toUpperCase() ??
|
||||
"ERR"),
|
||||
textAlign: TextAlign.center,
|
||||
style: STextStyles.smallMed12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
|
@ -1023,18 +1063,20 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
key: const Key(
|
||||
"buyViewClearAmountFieldButtonKey"),
|
||||
onTap: () {
|
||||
if (_BuyFormState.buyWithFiat) {
|
||||
_buyAmountController.text = _BuyFormState
|
||||
.minFiat
|
||||
.toStringAsFixed(2);
|
||||
} else {
|
||||
if (selectedCrypto?.ticker ==
|
||||
_BuyFormState.boundedCryptoTicker) {
|
||||
_buyAmountController.text = _BuyFormState
|
||||
.minCrypto
|
||||
.toStringAsFixed(8);
|
||||
}
|
||||
}
|
||||
// if (_BuyFormState.buyWithFiat) {
|
||||
// _buyAmountController.text = _BuyFormState
|
||||
// .minFiat
|
||||
// .toStringAsFixed(2);
|
||||
// } else {
|
||||
// if (selectedCrypto?.ticker ==
|
||||
// _BuyFormState.boundedCryptoTicker) {
|
||||
// _buyAmountController.text = _BuyFormState
|
||||
// .minCrypto
|
||||
// .toStringAsFixed(8);
|
||||
// }
|
||||
// }
|
||||
_buyAmountController.text = "";
|
||||
validateAmount();
|
||||
},
|
||||
child: const XIcon(),
|
||||
)
|
||||
|
@ -1051,7 +1093,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
_buyAmountController.text =
|
||||
amountString.toString();
|
||||
|
||||
setState(() {});
|
||||
validateAmount();
|
||||
}
|
||||
},
|
||||
child: _buyAmountController.text.isEmpty
|
||||
|
@ -1064,6 +1106,14 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 10 : 4,
|
||||
),
|
||||
if (_amountOutOfRangeErrorString.isNotEmpty)
|
||||
Text(
|
||||
_amountOutOfRangeErrorString,
|
||||
style: STextStyles.errorSmall(context),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 20 : 12,
|
||||
),
|
||||
|
@ -1171,7 +1221,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
_receiveAddressController.text = "";
|
||||
_address = "";
|
||||
setState(() {
|
||||
_addressToggleFlag = true;
|
||||
_addressToggleFlag = false;
|
||||
});
|
||||
},
|
||||
child: const XIcon(),
|
||||
|
@ -1342,27 +1392,15 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
SizedBox(
|
||||
height: isDesktop ? 20 : 12,
|
||||
),
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (_receiveAddressController.text.isNotEmpty &&
|
||||
_buyAmountController.text.isNotEmpty) {
|
||||
previewQuote(quote);
|
||||
}
|
||||
},
|
||||
child: PrimaryButton(
|
||||
PrimaryButton(
|
||||
buttonHeight: isDesktop ? ButtonHeight.l : null,
|
||||
enabled: _receiveAddressController.text.isNotEmpty &&
|
||||
enabled: _addressToggleFlag &&
|
||||
_amountOutOfRangeErrorString.isEmpty &&
|
||||
_buyAmountController.text.isNotEmpty,
|
||||
onPressed: () {
|
||||
if (_receiveAddressController.text.isNotEmpty &&
|
||||
_buyAmountController.text.isNotEmpty) {
|
||||
previewQuote(quote);
|
||||
}
|
||||
},
|
||||
label: "Preview quote",
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -1371,51 +1409,53 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
}
|
||||
|
||||
// See https://stackoverflow.com/a/68072967
|
||||
class NumericalRangeFormatter extends TextInputFormatter {
|
||||
NumericalRangeFormatter();
|
||||
// might need this again in the future
|
||||
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue,
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
TextSelection newSelection = newValue.selection;
|
||||
String newVal = _BuyFormState.buyWithFiat
|
||||
? Decimal.parse(newValue.text).toStringAsFixed(2)
|
||||
: Decimal.parse(newValue.text).toStringAsFixed(8);
|
||||
if (newValue.text == '') {
|
||||
return newValue;
|
||||
} else {
|
||||
if (_BuyFormState.buyWithFiat) {
|
||||
if (Decimal.parse(newValue.text) < _BuyFormState.minFiat) {
|
||||
newVal = _BuyFormState.minFiat.toStringAsFixed(2);
|
||||
// _BuyFormState._buyAmountController.selection =
|
||||
// TextSelection.collapsed(
|
||||
// offset: _BuyFormState.buyWithFiat
|
||||
// ? _BuyFormState._buyAmountController.text.length - 2
|
||||
// : _BuyFormState._buyAmountController.text.length - 8);
|
||||
} else if (Decimal.parse(newValue.text) > _BuyFormState.maxFiat) {
|
||||
newVal = _BuyFormState.maxFiat.toStringAsFixed(2);
|
||||
}
|
||||
} else if (!_BuyFormState.buyWithFiat &&
|
||||
_BuyFormState.selectedCrypto?.ticker ==
|
||||
_BuyFormState.boundedCryptoTicker) {
|
||||
if (Decimal.parse(newValue.text) < _BuyFormState.minCrypto) {
|
||||
newVal = _BuyFormState.minCrypto.toStringAsFixed(8);
|
||||
} else if (Decimal.parse(newValue.text) > _BuyFormState.maxCrypto) {
|
||||
newVal = _BuyFormState.maxCrypto.toStringAsFixed(8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final regexString = _BuyFormState.buyWithFiat
|
||||
? r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$'
|
||||
: r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
|
||||
|
||||
// return RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
|
||||
return RegExp(regexString).hasMatch(newVal)
|
||||
? TextEditingValue(text: newVal, selection: newSelection)
|
||||
: oldValue;
|
||||
}
|
||||
}
|
||||
// // See https://stackoverflow.com/a/68072967
|
||||
// class NumericalRangeFormatter extends TextInputFormatter {
|
||||
// NumericalRangeFormatter();
|
||||
//
|
||||
// @override
|
||||
// TextEditingValue formatEditUpdate(
|
||||
// TextEditingValue oldValue,
|
||||
// TextEditingValue newValue,
|
||||
// ) {
|
||||
// TextSelection newSelection = newValue.selection;
|
||||
// String newVal = _BuyFormState.buyWithFiat
|
||||
// ? Decimal.parse(newValue.text).toStringAsFixed(2)
|
||||
// : Decimal.parse(newValue.text).toStringAsFixed(8);
|
||||
// if (newValue.text == '') {
|
||||
// return newValue;
|
||||
// } else {
|
||||
// if (_BuyFormState.buyWithFiat) {
|
||||
// if (Decimal.parse(newValue.text) < _BuyFormState.minFiat) {
|
||||
// newVal = _BuyFormState.minFiat.toStringAsFixed(2);
|
||||
// // _BuyFormState._buyAmountController.selection =
|
||||
// // TextSelection.collapsed(
|
||||
// // offset: _BuyFormState.buyWithFiat
|
||||
// // ? _BuyFormState._buyAmountController.text.length - 2
|
||||
// // : _BuyFormState._buyAmountController.text.length - 8);
|
||||
// } else if (Decimal.parse(newValue.text) > _BuyFormState.maxFiat) {
|
||||
// newVal = _BuyFormState.maxFiat.toStringAsFixed(2);
|
||||
// }
|
||||
// } else if (!_BuyFormState.buyWithFiat &&
|
||||
// _BuyFormState.selectedCrypto?.ticker ==
|
||||
// _BuyFormState.boundedCryptoTicker) {
|
||||
// if (Decimal.parse(newValue.text) < _BuyFormState.minCrypto) {
|
||||
// newVal = _BuyFormState.minCrypto.toStringAsFixed(8);
|
||||
// } else if (Decimal.parse(newValue.text) > _BuyFormState.maxCrypto) {
|
||||
// newVal = _BuyFormState.maxCrypto.toStringAsFixed(8);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// final regexString = _BuyFormState.buyWithFiat
|
||||
// ? r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$'
|
||||
// : r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
|
||||
//
|
||||
// // return RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
|
||||
// return RegExp(regexString).hasMatch(newVal)
|
||||
// ? TextEditingValue(text: newVal, selection: newSelection)
|
||||
// : oldValue;
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:stackwallet/models/isar/models/isar_models.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/providers/blockchain/dogecoin/current_height_provider.dart';
|
||||
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
|
@ -85,7 +84,13 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
children: [
|
||||
TransactionCard(
|
||||
// this may mess with combined firo transactions
|
||||
key: Key(tx.txid + tx.type.name + tx.address.value.toString()), //
|
||||
key: Key(tx.txid +
|
||||
tx.type.name +
|
||||
tx.address.value.toString() +
|
||||
ref
|
||||
.watch(widget.managerProvider
|
||||
.select((value) => value.currentHeight))
|
||||
.toString()), //
|
||||
transaction: tx,
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
|
@ -180,7 +185,13 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
),
|
||||
child: TransactionCard(
|
||||
// this may mess with combined firo transactions
|
||||
key: Key(tx.txid + tx.type.name + tx.address.value.toString()), //
|
||||
key: Key(tx.txid +
|
||||
tx.type.name +
|
||||
tx.address.value.toString() +
|
||||
ref
|
||||
.watch(widget.managerProvider
|
||||
.select((value) => value.currentHeight))
|
||||
.toString()), //
|
||||
transaction: tx,
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
|
@ -188,13 +199,6 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
}
|
||||
}
|
||||
|
||||
void updateHeightProvider(Manager manager) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
ref.read(currentHeightProvider(manager.coin).state).state =
|
||||
manager.currentHeight;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
managerProvider = widget.managerProvider;
|
||||
|
@ -203,14 +207,9 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// final managerProvider = ref
|
||||
// .watch(walletsChangeNotifierProvider)
|
||||
// .getManagerProvider(widget.walletId);
|
||||
|
||||
final manager = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId)));
|
||||
|
||||
updateHeightProvider(manager);
|
||||
return FutureBuilder(
|
||||
future: manager.transactions,
|
||||
builder: (fbContext, AsyncSnapshot<List<Transaction>> snapshot) {
|
||||
|
|
|
@ -360,62 +360,6 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (ref.watch(walletsChangeNotifierProvider.select((value) =>
|
||||
value.getManager(widget.walletId).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(
|
||||
width: 12,
|
||||
),
|
||||
if (widget.coin.hasBuySupport)
|
||||
RawMaterialButton(
|
||||
constraints: const BoxConstraints(
|
||||
|
@ -455,7 +399,62 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (widget.coin.hasBuySupport)
|
||||
if (ref.watch(walletsChangeNotifierProvider.select((value) =>
|
||||
value.getManager(widget.walletId).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,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.bottomNavIconIcon,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Text(
|
||||
"More",
|
||||
style: STextStyles.buttonSmall(context),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
|
|
|
@ -10,7 +10,6 @@ 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/transaction_views/transaction_details_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/providers.dart';
|
||||
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
||||
|
@ -131,7 +130,9 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
// check if address book name contains
|
||||
contains |= contacts
|
||||
.where((e) =>
|
||||
e.addresses.where((a) => a.address == tx.address).isNotEmpty &&
|
||||
e.addresses
|
||||
.where((a) => a.address == tx.address.value?.value)
|
||||
.isNotEmpty &&
|
||||
e.name.toLowerCase().contains(keyword))
|
||||
.isNotEmpty;
|
||||
|
||||
|
@ -853,7 +854,8 @@ class _DesktopTransactionCardRowState
|
|||
prefix = "";
|
||||
}
|
||||
|
||||
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
|
||||
final currentHeight = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).currentHeight));
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
|
|
|
@ -11,7 +11,6 @@ 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/edit_note_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/providers.dart';
|
||||
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
||||
|
@ -298,7 +297,8 @@ class _TransactionDetailsViewState
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
|
||||
final currentHeight = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).currentHeight));
|
||||
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
|
|
|
@ -774,9 +774,6 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
);
|
||||
},
|
||||
onBuyPressed: () {
|
||||
// TODO set default coin to currently open wallet here by passing it as an argument
|
||||
// final coin = ref.read(managerProvider).coin;
|
||||
|
||||
unawaited(Navigator.of(context).pushNamed(
|
||||
BuyInWalletView.routeName,
|
||||
arguments: coin,
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
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);
|
|
@ -1,4 +1,8 @@
|
|||
enum BuyExceptionType { generic, serializeResponseError }
|
||||
enum BuyExceptionType {
|
||||
generic,
|
||||
serializeResponseError,
|
||||
cryptoAmountOutOfRange,
|
||||
}
|
||||
|
||||
class BuyException implements Exception {
|
||||
String errorMessage;
|
||||
|
|
|
@ -210,7 +210,20 @@ class SimplexAPI {
|
|||
|
||||
BuyResponse<SimplexQuote> _parseQuote(dynamic jsonArray) {
|
||||
try {
|
||||
String cryptoAmount = "${jsonArray['digital_money']['amount']}";
|
||||
// final Map<String, dynamic> lol =
|
||||
// Map<String, dynamic>.from(jsonArray as Map);
|
||||
|
||||
String? cryptoAmount = jsonArray['digital_money']?['amount'] as String?;
|
||||
|
||||
if (cryptoAmount == null) {
|
||||
String error = jsonArray['error'] as String;
|
||||
return BuyResponse(
|
||||
exception: BuyException(
|
||||
error,
|
||||
BuyExceptionType.cryptoAmountOutOfRange,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SimplexQuote quote = jsonArray['quote'] as SimplexQuote;
|
||||
final SimplexQuote _quote = SimplexQuote(
|
||||
|
|
|
@ -251,6 +251,14 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -1314,6 +1322,8 @@ class BitcoinWallet extends CoinServiceAPI
|
|||
checkChangeAddressForTransactions: _checkChangeAddressForTransactions,
|
||||
addDerivation: addDerivation,
|
||||
addDerivations: addDerivations,
|
||||
dustLimitP2PKH: DUST_LIMIT_P2PKH,
|
||||
minConfirms: MINIMUM_CONFIRMATIONS,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -217,6 +217,14 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
|
|
@ -226,6 +226,14 @@ class DogecoinWallet extends CoinServiceAPI
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
|
|
@ -1416,6 +1416,14 @@ class EpicCashWallet extends CoinServiceAPI
|
|||
});
|
||||
|
||||
await updateCachedChainHeight(latestHeight!);
|
||||
if (latestHeight! > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return latestHeight!;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
|
|
@ -4897,6 +4897,14 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
|
|
@ -241,6 +241,14 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
|
|
@ -236,6 +236,14 @@ class NamecoinWallet extends CoinServiceAPI
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
|
|
@ -231,6 +231,14 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
|
|
@ -189,7 +189,8 @@ mixin ElectrumXParsing {
|
|||
|
||||
TransactionSubType txSubType = TransactionSubType.none;
|
||||
if (this is PaynymWalletInterface && outs.length > 1 && ins.isNotEmpty) {
|
||||
List<String>? scriptChunks = outs[1].scriptPubKeyAsm?.split(" ");
|
||||
for (int i = 0; i < outs.length; i++) {
|
||||
List<String>? scriptChunks = outs[i].scriptPubKeyAsm?.split(" ");
|
||||
if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") {
|
||||
final blindedPaymentCode = scriptChunks![1];
|
||||
final bytes = blindedPaymentCode.fromHex;
|
||||
|
@ -200,6 +201,7 @@ mixin ElectrumXParsing {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final tx = Transaction(
|
||||
walletId: walletId,
|
||||
|
|
|
@ -15,7 +15,6 @@ import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
|||
import 'package:stackwallet/exceptions/wallet/insufficient_balance_exception.dart';
|
||||
import 'package:stackwallet/exceptions/wallet/paynym_send_exception.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||
import 'package:stackwallet/utilities/bip32_utils.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
|
@ -35,6 +34,8 @@ mixin PaynymWalletInterface {
|
|||
late final MainDB _db;
|
||||
late final ElectrumX _electrumXClient;
|
||||
late final SecureStorageInterface _secureStorage;
|
||||
late final int _dustLimitP2PKH;
|
||||
late final int _minConfirms;
|
||||
|
||||
// passed in wallet functions
|
||||
late final Future<List<String>> Function() _getMnemonic;
|
||||
|
@ -79,6 +80,8 @@ mixin PaynymWalletInterface {
|
|||
required MainDB db,
|
||||
required ElectrumX electrumXClient,
|
||||
required SecureStorageInterface secureStorage,
|
||||
required int dustLimitP2PKH,
|
||||
required int minConfirms,
|
||||
required Future<List<String>> Function() getMnemonic,
|
||||
required Future<int> Function() getChainHeight,
|
||||
required Future<String> Function() getCurrentChangeAddress,
|
||||
|
@ -125,6 +128,8 @@ mixin PaynymWalletInterface {
|
|||
_db = db;
|
||||
_electrumXClient = electrumXClient;
|
||||
_secureStorage = secureStorage;
|
||||
_dustLimitP2PKH = dustLimitP2PKH;
|
||||
_minConfirms = minConfirms;
|
||||
_getMnemonic = getMnemonic;
|
||||
_getChainHeight = getChainHeight;
|
||||
_getCurrentChangeAddress = getCurrentChangeAddress;
|
||||
|
@ -353,7 +358,7 @@ mixin PaynymWalletInterface {
|
|||
int additionalOutputs = 0,
|
||||
List<UTXO>? utxos,
|
||||
}) async {
|
||||
const amountToSend = DUST_LIMIT;
|
||||
final amountToSend = _dustLimitP2PKH;
|
||||
final List<UTXO> availableOutputs =
|
||||
utxos ?? await _db.getUTXOs(_walletId).findAll();
|
||||
final List<UTXO> spendableOutputs = [];
|
||||
|
@ -362,8 +367,8 @@ mixin PaynymWalletInterface {
|
|||
// 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 _getChainHeight(), MINIMUM_CONFIRMATIONS) ==
|
||||
availableOutputs[i]
|
||||
.isConfirmed(await _getChainHeight(), _minConfirms) ==
|
||||
true) {
|
||||
spendableOutputs.add(availableOutputs[i]);
|
||||
spendableSatoshiValue += availableOutputs[i].value;
|
||||
|
@ -440,13 +445,13 @@ mixin PaynymWalletInterface {
|
|||
feeForWithChange = vSizeForWithChange * 1000;
|
||||
}
|
||||
|
||||
if (satoshisBeingUsed - amountToSend > feeForNoChange + DUST_LIMIT) {
|
||||
if (satoshisBeingUsed - amountToSend > feeForNoChange + _dustLimitP2PKH) {
|
||||
// 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 &&
|
||||
if (changeAmount >= _dustLimitP2PKH &&
|
||||
satoshisBeingUsed - amountToSend - changeAmount == feeForWithChange) {
|
||||
final txn = await _createNotificationTx(
|
||||
targetPaymentCodeString: targetPaymentCodeString,
|
||||
|
@ -572,7 +577,8 @@ mixin PaynymWalletInterface {
|
|||
);
|
||||
|
||||
// todo: modify address once segwit support is in our bip47
|
||||
txb.addOutput(targetPaymentCode.notificationAddressP2PKH(), DUST_LIMIT);
|
||||
txb.addOutput(
|
||||
targetPaymentCode.notificationAddressP2PKH(), _dustLimitP2PKH);
|
||||
txb.addOutput(opReturnScript, 0);
|
||||
|
||||
// TODO: add possible change output and mark output as dangerous
|
||||
|
|
|
@ -6,7 +6,6 @@ import 'package:stackwallet/models/isar/models/isar_models.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/transaction_views/transaction_details_view.dart';
|
||||
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
@ -121,7 +120,8 @@ class _TransactionCardState extends ConsumerState<TransactionCard> {
|
|||
}
|
||||
}
|
||||
|
||||
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
|
||||
final currentHeight = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).currentHeight));
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
|
|
|
@ -80,6 +80,8 @@ void main() {
|
|||
|
||||
when(wallets.getManager("wallet-id"))
|
||||
.thenAnswer((realInvocation) => Manager(wallet));
|
||||
|
||||
when(wallet.storedChainHeight).thenAnswer((_) => 6000000);
|
||||
//
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
|
@ -173,6 +175,7 @@ void main() {
|
|||
.thenAnswer((realInvocation) => Tuple2(Decimal.ten, 0.00));
|
||||
|
||||
when(wallet.coin).thenAnswer((_) => Coin.firo);
|
||||
when(wallet.storedChainHeight).thenAnswer((_) => 6000000);
|
||||
|
||||
when(wallets.getManager("wallet-id"))
|
||||
.thenAnswer((realInvocation) => Manager(wallet));
|
||||
|
@ -271,6 +274,8 @@ void main() {
|
|||
when(wallets.getManager("wallet-id"))
|
||||
.thenAnswer((realInvocation) => Manager(wallet));
|
||||
|
||||
when(wallet.storedChainHeight).thenAnswer((_) => 6000000);
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
|
@ -358,6 +363,8 @@ void main() {
|
|||
when(wallets.getManager("wallet id"))
|
||||
.thenAnswer((realInvocation) => Manager(wallet));
|
||||
|
||||
when(wallet.storedChainHeight).thenAnswer((_) => 6000000);
|
||||
|
||||
mockingjay
|
||||
.when(() => navigator.pushNamed("/transactionDetails",
|
||||
arguments: Tuple3(tx, Coin.firo, "wallet id")))
|
||||
|
@ -395,6 +402,7 @@ void main() {
|
|||
verify(mockPrefs.currency).called(2);
|
||||
verify(mockLocaleService.locale).called(4);
|
||||
verify(wallet.coin.ticker).called(2);
|
||||
verify(wallet.storedChainHeight).called(2);
|
||||
|
||||
verifyNoMoreInteractions(wallet);
|
||||
verifyNoMoreInteractions(mockLocaleService);
|
||||
|
|
Loading…
Reference in a new issue