swap rate refresh fixes and error propagation

This commit is contained in:
julian 2023-05-04 12:19:50 -06:00
parent a1b7c9248b
commit 1c33dd5062
3 changed files with 105 additions and 118 deletions

View file

@ -10,6 +10,7 @@ import 'package:isar/isar.dart';
import 'package:stackwallet/models/exchange/aggregate_currency.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/range.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
@ -28,7 +29,6 @@ import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
@ -43,6 +43,9 @@ import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:stackwallet/widgets/textfields/exchange_textfield.dart';
import 'package:tuple/tuple.dart';
import 'package:uuid/uuid.dart';
import '../../services/exchange/exchange_response.dart';
class ExchangeForm extends ConsumerStatefulWidget {
const ExchangeForm({
@ -604,7 +607,9 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
}
Future<void> update() async {
_addUpdate();
final uuid = const Uuid().v1();
_latestUuid = uuid;
_addUpdate(uuid);
for (final exchange in exchanges) {
ref.read(efEstimatesListProvider(exchange.name).notifier).state = null;
}
@ -619,10 +624,12 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
amount <= Decimal.zero ||
pair.send == null ||
pair.receive == null) {
_removeUpdate();
_removeUpdate(uuid);
return;
}
final rateType = ref.read(efRateTypeProvider);
final Map<String, Tuple2<ExchangeResponse<List<Estimate>>, Range?>>
results = {};
for (final exchange in exchanges) {
final sendCurrency = pair.send?.forExchange(exchange.name);
@ -635,14 +642,6 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
rateType == ExchangeRateType.fixed,
);
if (rangeResponse.value == null) {
Logging.instance.log(
"Tried to $runtimeType.update Range for:"
" $exchange where response: $rangeResponse",
level: LogLevel.Info,
);
}
final estimateResponse = await exchange.getEstimates(
sendCurrency.ticker,
receiveCurrency.ticker,
@ -651,43 +650,41 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
reversed,
);
if (estimateResponse.value == null) {
Logging.instance.log(
"Tried to $runtimeType._fetchEstimateAndRange Estimate for:"
" $exchange where response: $estimateResponse",
level: LogLevel.Info,
);
}
if (mounted) {
if (estimateResponse.value != null && rangeResponse.value != null) {
ref.read(efEstimatesListProvider(exchange.name).notifier).state =
Tuple2(estimateResponse.value!, rangeResponse.value!);
} else {
ref.read(efEstimatesListProvider(exchange.name).notifier).state =
null;
}
}
results.addAll(
{
exchange.name: Tuple2(
estimateResponse,
rangeResponse.value,
),
},
);
}
}
// WidgetsBinding.instance.addPostFrameCallback((_) {
_removeUpdate();
// });
for (final exchange in exchanges) {
if (uuid == _latestUuid) {
ref.read(efEstimatesListProvider(exchange.name).notifier).state =
results[exchange.name];
}
}
_removeUpdate(uuid);
}
int _updateCount = 0;
String? _latestUuid;
final Set<String> _uuids = {};
void _addUpdate() {
_updateCount++;
void _addUpdate(String uuid) {
_uuids.add(uuid);
ref.read(efRefreshingProvider.notifier).state = true;
}
void _removeUpdate() {
_updateCount--;
if (_updateCount <= 0) {
_updateCount = 0;
ref.read(efRefreshingProvider.notifier).state = false;
void _removeUpdate(String uuid) {
_uuids.remove(uuid);
if (_uuids.isEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(efRefreshingProvider.notifier).state = false;
});
}
}
@ -711,16 +708,24 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
_sendFocusNode.addListener(() {
if (_sendFocusNode.hasFocus) {
final reversed = ref.read(efReversedProvider);
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(efReversedProvider.notifier).state = false;
if (reversed == true) {
update();
}
});
}
});
_receiveFocusNode.addListener(() {
if (_receiveFocusNode.hasFocus &&
ref.read(efExchangeProvider).name != ChangeNowExchange.exchangeName) {
final reversed = ref.read(efReversedProvider);
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(efReversedProvider.notifier).state = true;
if (reversed != true) {
update();
}
});
}
});
@ -856,12 +861,6 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
SizedBox(
height: isDesktop ? 10 : 4,
),
// if (ref.watch(efWarningProvider).isNotEmpty &&
// !ref.watch(efReversedProvider))
// Text(
// ref.watch(efWarningProvider),
// style: STextStyles.errorSmall(context),
// ),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -926,27 +925,24 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
borderRadius: Constants.size.circularBorderRadius,
background:
Theme.of(context).extension<StackColors>()!.textFieldDefaultBG,
onTap: () {
if (!(ref.read(efRateTypeProvider) == ExchangeRateType.estimated) &&
_receiveController.text == "-") {
_receiveController.text = "";
}
},
onTap: rateType == ExchangeRateType.estimated &&
ref.watch(efExchangeProvider).name ==
ChangeNowExchange.exchangeName
? null
: () {
if (_sendController.text == "-") {
_sendController.text = "";
}
},
onChanged: receiveFieldOnChanged,
onButtonTap: selectReceiveCurrency,
isWalletCoin: isWalletCoin(coin, true),
currency: ref
.watch(efCurrencyPairProvider.select((value) => value.receive)),
readOnly: (rateType) == ExchangeRateType.estimated &&
readOnly: rateType == ExchangeRateType.estimated &&
ref.watch(efExchangeProvider).name ==
ChangeNowExchange.exchangeName,
),
// if (ref.watch(efWarningProvider).isNotEmpty &&
// ref.watch(efReversedProvider))
// Text(
// ref.watch(efWarningProvider),
// style: STextStyles.errorSmall(context),
// ),
SizedBox(
height: isDesktop ? 20 : 12,
),
@ -957,21 +953,12 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
onChanged: onRateTypeChanged,
),
),
// AnimatedSize(
// duration: const Duration(milliseconds: 250),
// curve: Curves.easeInOutCirc,
// child: ref.watch(efSendAmountProvider).sendAmount != null &&
// ref.watch(exchangeFormStateProvider).sendAmount != Decimal.zero
// ?
Padding(
padding: EdgeInsets.only(top: isDesktop ? 20 : 12),
child: ExchangeProviderOptions(
fixedRate: rateType == ExchangeRateType.fixed,
reversed: ref.watch(efReversedProvider),
),
// : const SizedBox(
// height: 0,
// ),
),
SizedBox(
height: isDesktop ? 20 : 12,

View file

@ -9,6 +9,7 @@ import 'package:stackwallet/services/exchange/exchange.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
@ -31,11 +32,10 @@ class ExchangeOption extends ConsumerStatefulWidget {
final bool reversed;
@override
ConsumerState<ExchangeOption> createState() =>
_ExchangeMultiProviderOptionState();
ConsumerState<ExchangeOption> createState() => _ExchangeOptionState();
}
class _ExchangeMultiProviderOptionState extends ConsumerState<ExchangeOption> {
class _ExchangeOptionState extends ConsumerState<ExchangeOption> {
final isDesktop = Util.isDesktop;
@override
@ -49,7 +49,8 @@ class _ExchangeMultiProviderOptionState extends ConsumerState<ExchangeOption> {
? ref.watch(efReceiveAmountProvider)
: ref.watch(efSendAmountProvider);
final estimates = ref.watch(efEstimatesListProvider(widget.exchange.name));
final data = ref.watch(efEstimatesListProvider(widget.exchange.name));
final estimates = data?.item1.value;
return AnimatedSize(
duration: const Duration(milliseconds: 500),
@ -68,14 +69,14 @@ class _ExchangeMultiProviderOptionState extends ConsumerState<ExchangeOption> {
receivingCurrency != null &&
amount != null &&
amount > Decimal.zero) {
if (estimates != null && estimates.item1.isNotEmpty) {
if (estimates != null && estimates.isNotEmpty) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
for (int i = 0; i < estimates.item1.length; i++)
for (int i = 0; i < estimates.length; i++)
Builder(
builder: (context) {
final e = estimates.item1[i];
final e = estimates[i];
int decimals;
try {
@ -136,14 +137,37 @@ class _ExchangeMultiProviderOptionState extends ConsumerState<ExchangeOption> {
);
} else {
Logging.instance.log(
"$runtimeType failed to fetch rate for ${widget.exchange.name}: $estimates",
"$runtimeType rate unavailable for ${widget.exchange.name}: $data",
level: LogLevel.Warning,
);
return _ProviderOption(
exchange: widget.exchange,
estimate: null,
rateString: "Failed to fetch rate",
return Consumer(
builder: (_, ref, __) {
String? message;
final range = data?.item2;
if (range != null) {
if (range.min != null && amount < range.min!) {
message ??= "Amount too small";
} else if (range.max != null && amount > range.max!) {
message ??= "Amount too large";
}
} else if (data?.item1.value == null) {
final rateType = ref.watch(efRateTypeProvider) ==
ExchangeRateType.estimated
? "estimated"
: "fixed";
message ??= "Pair unavailable on $rateType rate flow";
}
return _ProviderOption(
exchange: widget.exchange,
estimate: null,
rateString: message ?? "Failed to fetch rate",
rateColor:
Theme.of(context).extension<StackColors>()!.textError,
);
},
);
}
} else {
@ -168,6 +192,7 @@ class _ProviderOption extends ConsumerStatefulWidget {
required this.rateString,
this.kycRating,
this.loadingString = false,
this.rateColor,
}) : super(key: key);
final Exchange exchange;
@ -175,6 +200,7 @@ class _ProviderOption extends ConsumerStatefulWidget {
final String rateString;
final String? kycRating;
final bool loadingString;
final Color? rateColor;
@override
ConsumerState<_ProviderOption> createState() => _ProviderOptionState();
@ -201,8 +227,6 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> {
groupValue = _id;
}
// bool selected = groupValue == _id;
return ConditionalParent(
condition: isDesktop,
builder: (child) => MouseRegion(
@ -214,17 +238,6 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> {
ref.read(efExchangeProvider.notifier).state = widget.exchange;
ref.read(efExchangeProviderNameProvider.notifier).state =
widget.estimate?.exchangeProvider ?? widget.exchange.name;
// if (!selected) {
// ref.read(exchangeFormStateProvider).updateExchange(
// exchange: widget.exchange,
// shouldUpdateData: false,
// shouldNotifyListeners: false,
// providerName:
// widget.estimate?.exchangeProvider ?? widget.exchange.name,
// shouldAwait: false,
// );
// }
},
child: Container(
color: Colors.transparent,
@ -253,19 +266,6 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> {
.state =
widget.estimate?.exchangeProvider ??
widget.exchange.name;
// if (!selected) {
//
//
// ref.read(exchangeFormStateProvider).updateExchange(
// exchange: widget.exchange,
// shouldUpdateData: false,
// shouldNotifyListeners: false,
// providerName:
// widget.estimate?.exchangeProvider ??
// widget.exchange.name,
// shouldAwait: false,
// );
// }
},
),
),
@ -324,9 +324,10 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> {
widget.rateString,
style:
STextStyles.itemSubtitle12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
color: widget.rateColor ??
Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],

View file

@ -4,12 +4,13 @@ import 'package:stackwallet/models/exchange/active_pair.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/range.dart';
import 'package:stackwallet/services/exchange/exchange.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
import 'package:tuple/tuple.dart';
final efEstimatesListProvider =
StateProvider.family<Tuple2<List<Estimate>, Range>?, String>(
(ref, exchangeName) => null);
final efEstimatesListProvider = StateProvider.family<
Tuple2<ExchangeResponse<List<Estimate>>, Range?>?,
String>((ref, exchangeName) => null);
final efRateTypeProvider =
StateProvider<ExchangeRateType>((ref) => ExchangeRateType.estimated);
@ -53,19 +54,17 @@ final efCurrencyPairProvider = ChangeNotifierProvider<ActivePair>(
(ref) => ActivePair(),
);
final efRangeProvider = StateProvider<Range?>((ref) {
final exchange = ref.watch(efExchangeProvider);
return ref.watch(efEstimatesListProvider(exchange.name))?.item2;
});
final efEstimateProvider = StateProvider<Estimate?>((ref) {
final exchange = ref.watch(efExchangeProvider);
final provider = ref.watch(efExchangeProviderNameProvider);
final reversed = ref.watch(efReversedProvider);
final fixedRate = ref.watch(efRateTypeProvider) == ExchangeRateType.fixed;
final matches =
ref.watch(efEstimatesListProvider(exchange.name))?.item1.where((e) {
final matches = ref
.watch(efEstimatesListProvider(exchange.name))
?.item1
.value
?.where((e) {
return e.exchangeProvider == provider &&
e.fixedRate == fixedRate &&
e.reversed == reversed;