Skip to content

Commit 918c5c3

Browse files
Merge pull request #1224 from cypherstack/julian
various
2 parents b19dde9 + 6952baa commit 918c5c3

File tree

24 files changed

+1117
-851
lines changed

24 files changed

+1117
-851
lines changed

lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart

Lines changed: 116 additions & 127 deletions
Large diffs are not rendered by default.

lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import '../../../themes/stack_colors.dart';
2121
import '../../../utilities/prefs.dart';
2222
import '../../../utilities/util.dart';
2323
import '../../../widgets/rounded_white_container.dart';
24-
import 'exchange_provider_option.dart';
24+
import 'sorted_exchange_providers.dart';
2525

2626
class ExchangeProviderOptions extends ConsumerStatefulWidget {
2727
const ExchangeProviderOptions({
@@ -94,46 +94,55 @@ class _ExchangeProviderOptionsState
9494

9595
return RoundedWhiteContainer(
9696
padding: isDesktop ? const EdgeInsets.all(0) : const EdgeInsets.all(12),
97-
borderColor:
98-
isDesktop
99-
? Theme.of(context).extension<StackColors>()!.background
100-
: null,
101-
child: Column(
102-
children: [
103-
if (showChangeNow)
104-
ExchangeOption(
105-
exchange: ChangeNowExchange.instance,
106-
fixedRate: widget.fixedRate,
107-
reversed: widget.reversed,
108-
),
109-
if (showChangeNow && showTrocador)
110-
isDesktop
111-
? Container(
112-
height: 1,
113-
color: Theme.of(context).extension<StackColors>()!.background,
114-
)
115-
: const SizedBox(height: 16),
116-
if (showTrocador)
117-
ExchangeOption(
118-
fixedRate: widget.fixedRate,
119-
reversed: widget.reversed,
120-
exchange: TrocadorExchange.instance,
121-
),
122-
if ((showChangeNow || showTrocador) && showNanswap)
123-
isDesktop
124-
? Container(
125-
height: 1,
126-
color: Theme.of(context).extension<StackColors>()!.background,
127-
)
128-
: const SizedBox(height: 16),
129-
if (showNanswap)
130-
ExchangeOption(
131-
fixedRate: widget.fixedRate,
132-
reversed: widget.reversed,
133-
exchange: NanswapExchange.instance,
134-
),
97+
borderColor: isDesktop
98+
? Theme.of(context).extension<StackColors>()!.background
99+
: null,
100+
child: SortedExchangeProviders(
101+
exchangees: [
102+
if (showChangeNow) ChangeNowExchange.instance,
103+
if (showTrocador) TrocadorExchange.instance,
104+
if (showNanswap) NanswapExchange.instance,
135105
],
106+
fixedRate: widget.fixedRate,
107+
reversed: widget.reversed,
136108
),
109+
110+
// Column(
111+
// children: [
112+
// if (showChangeNow)
113+
// ExchangeOption(
114+
// exchange: ChangeNowExchange.instance,
115+
// fixedRate: widget.fixedRate,
116+
// reversed: widget.reversed,
117+
// ),
118+
// if (showChangeNow && showTrocador)
119+
// isDesktop
120+
// ? Container(
121+
// height: 1,
122+
// color: Theme.of(context).extension<StackColors>()!.background,
123+
// )
124+
// : const SizedBox(height: 16),
125+
// if (showTrocador)
126+
// ExchangeOption(
127+
// fixedRate: widget.fixedRate,
128+
// reversed: widget.reversed,
129+
// exchange: TrocadorExchange.instance,
130+
// ),
131+
// if ((showChangeNow || showTrocador) && showNanswap)
132+
// isDesktop
133+
// ? Container(
134+
// height: 1,
135+
// color: Theme.of(context).extension<StackColors>()!.background,
136+
// )
137+
// : const SizedBox(height: 16),
138+
// if (showNanswap)
139+
// ExchangeOption(
140+
// fixedRate: widget.fixedRate,
141+
// reversed: widget.reversed,
142+
// exchange: NanswapExchange.instance,
143+
// ),
144+
// ],
145+
// ),
137146
);
138147
}
139148
}
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import 'package:decimal/decimal.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_riverpod/flutter_riverpod.dart';
4+
import 'package:tuple/tuple.dart';
5+
6+
import '../../../app_config.dart';
7+
import '../../../models/exchange/response_objects/estimate.dart';
8+
import '../../../models/exchange/response_objects/range.dart';
9+
import '../../../providers/exchange/exchange_form_state_provider.dart';
10+
import '../../../providers/global/locale_provider.dart';
11+
import '../../../services/exchange/exchange.dart';
12+
import '../../../services/exchange/exchange_response.dart';
13+
import '../../../themes/stack_colors.dart';
14+
import '../../../utilities/amount/amount.dart';
15+
import '../../../utilities/amount/amount_formatter.dart';
16+
import '../../../utilities/amount/amount_unit.dart';
17+
import '../../../utilities/enums/exchange_rate_type_enum.dart';
18+
import '../../../utilities/util.dart';
19+
import '../../../wallets/crypto_currency/crypto_currency.dart';
20+
import '../../../widgets/conditional_parent.dart';
21+
import '../../../widgets/loading_indicator.dart';
22+
import 'exchange_provider_option.dart';
23+
24+
class SortedExchangeProviders extends ConsumerStatefulWidget {
25+
const SortedExchangeProviders({
26+
super.key,
27+
required this.exchangees,
28+
required this.fixedRate,
29+
required this.reversed,
30+
});
31+
32+
final List<Exchange> exchangees;
33+
final bool fixedRate;
34+
final bool reversed;
35+
36+
@override
37+
ConsumerState<SortedExchangeProviders> createState() =>
38+
_SortedExchangeProvidersState();
39+
}
40+
41+
class _SortedExchangeProvidersState
42+
extends ConsumerState<SortedExchangeProviders> {
43+
final List<(Exchange, Tuple2<ExchangeResponse<List<Estimate>>, Range?>?)>
44+
dataList = [];
45+
final List<(Exchange, List<Estimate>?)> estimates = [];
46+
47+
List<(Exchange, Estimate?)> transform(Decimal amount, String rcvTicker) {
48+
final List<(Exchange, Estimate?)> flattened = [];
49+
50+
for (final s in estimates) {
51+
if (s.$2 != null && s.$2!.isNotEmpty) {
52+
for (final e in s.$2!) {
53+
flattened.add((s.$1, e));
54+
}
55+
} else {
56+
flattened.add((s.$1, null));
57+
}
58+
}
59+
60+
flattened.sort((a, b) {
61+
if (a.$2 == null && b.$2 == null) return 1;
62+
if (a.$2 != null && b.$2 == null) return 0;
63+
if (a.$2 == null && b.$2 != null) return 0;
64+
65+
// or we get problems!!!
66+
assert(a.$2!.reversed == b.$2!.reversed);
67+
68+
return _getRate(a.$2!, amount, rcvTicker) >
69+
_getRate(b.$2!, amount, rcvTicker)
70+
? 0
71+
: 1;
72+
});
73+
74+
return flattened;
75+
}
76+
77+
Amount _getRate(Estimate e, Decimal amount, String rcvTicker) {
78+
int decimals;
79+
try {
80+
decimals = AppConfig.getCryptoCurrencyForTicker(
81+
rcvTicker,
82+
)!.fractionDigits;
83+
} catch (_) {
84+
decimals = 8; // some reasonable alternative
85+
}
86+
Amount rate;
87+
if (e.reversed) {
88+
rate = (amount / e.estimatedAmount)
89+
.toDecimal(scaleOnInfinitePrecision: 18)
90+
.toAmount(fractionDigits: decimals);
91+
} else {
92+
rate = (e.estimatedAmount / amount)
93+
.toDecimal(scaleOnInfinitePrecision: 18)
94+
.toAmount(fractionDigits: decimals);
95+
}
96+
return rate;
97+
}
98+
99+
@override
100+
Widget build(BuildContext context) {
101+
final sendCurrency = ref.watch(
102+
efCurrencyPairProvider.select((value) => value.send),
103+
);
104+
final receivingCurrency = ref.watch(
105+
efCurrencyPairProvider.select((value) => value.receive),
106+
);
107+
final reversed = ref.watch(efReversedProvider);
108+
final amount = reversed
109+
? ref.watch(efReceiveAmountProvider)
110+
: ref.watch(efSendAmountProvider);
111+
112+
dataList.clear();
113+
estimates.clear();
114+
for (final exchange in widget.exchangees) {
115+
final data = ref.watch(efEstimatesListProvider(exchange.name));
116+
dataList.add((exchange, data));
117+
estimates.add((exchange, data?.item1.value));
118+
}
119+
120+
// final data = ref.watch(efEstimatesListProvider(widget.exchange.name));
121+
// final estimates = data?.item1.value;
122+
123+
final pair = sendCurrency != null && receivingCurrency != null
124+
? (from: sendCurrency, to: receivingCurrency)
125+
: null;
126+
127+
if (ref.watch(efRefreshingProvider)) {
128+
return const LoadingIndicator(width: 48, height: 48);
129+
}
130+
131+
if (sendCurrency != null &&
132+
receivingCurrency != null &&
133+
amount != null &&
134+
amount > Decimal.zero) {
135+
final estimates = transform(amount, receivingCurrency.ticker);
136+
137+
if (estimates.isNotEmpty) {
138+
return Column(
139+
mainAxisSize: .min,
140+
children: [
141+
for (int i = 0; i < estimates.length; i++)
142+
Builder(
143+
builder: (context) {
144+
final e = estimates[i].$2;
145+
146+
if (e == null) {
147+
return Consumer(
148+
builder: (_, ref, __) {
149+
String? message;
150+
151+
final data = dataList
152+
.firstWhere((e) => identical(e.$1, estimates[i].$1))
153+
.$2;
154+
155+
final range = data?.item2;
156+
if (range != null) {
157+
if (range.min != null && amount < range.min!) {
158+
message ??= "Amount too small";
159+
} else if (range.max != null && amount > range.max!) {
160+
message ??= "Amount too large";
161+
}
162+
} else if (data?.item1.value == null) {
163+
final rateType =
164+
ref.watch(efRateTypeProvider) ==
165+
ExchangeRateType.estimated
166+
? "estimated"
167+
: "fixed";
168+
message ??= "Pair unavailable on $rateType rate flow";
169+
}
170+
171+
return ExchProviderOption(
172+
exchange: estimates[i].$1,
173+
estimate: null,
174+
pair: pair,
175+
rateString: message ?? "Failed to fetch rate",
176+
rateColor: Theme.of(
177+
context,
178+
).extension<StackColors>()!.textError,
179+
);
180+
},
181+
);
182+
}
183+
184+
final rate = _getRate(e, amount, receivingCurrency.ticker);
185+
186+
CryptoCurrency? coin;
187+
try {
188+
coin = AppConfig.getCryptoCurrencyForTicker(
189+
receivingCurrency.ticker,
190+
);
191+
} catch (_) {
192+
coin = null;
193+
}
194+
195+
final String rateString;
196+
if (coin != null) {
197+
rateString =
198+
"1 ${sendCurrency.ticker.toUpperCase()} "
199+
"~ ${ref.watch(pAmountFormatter(coin)).format(rate)}";
200+
} else {
201+
final formatter = AmountFormatter(
202+
unit: AmountUnit.normal,
203+
locale: ref.watch(
204+
localeServiceChangeNotifierProvider.select(
205+
(value) => value.locale,
206+
),
207+
),
208+
coin: Bitcoin(
209+
CryptoCurrencyNetwork.main,
210+
), // some sane default
211+
maxDecimals: 8, // some sane default
212+
);
213+
rateString =
214+
"1 ${sendCurrency.ticker.toUpperCase()} "
215+
"~ ${formatter.format(rate, withUnitName: false)}"
216+
" ${receivingCurrency.ticker.toUpperCase()}";
217+
}
218+
219+
return ConditionalParent(
220+
condition: i > 0,
221+
builder: (child) => Column(
222+
mainAxisSize: MainAxisSize.min,
223+
children: [
224+
Util.isDesktop
225+
? Container(
226+
height: 1,
227+
color: Theme.of(
228+
context,
229+
).extension<StackColors>()!.background,
230+
)
231+
: const SizedBox(height: 16),
232+
child,
233+
],
234+
),
235+
child: ExchProviderOption(
236+
key: Key(estimates[i].$1.name + e.exchangeProvider),
237+
exchange: estimates[i].$1,
238+
pair: pair,
239+
estimate: e,
240+
rateString: rateString,
241+
kycRating: e.kycRating,
242+
),
243+
);
244+
},
245+
),
246+
],
247+
);
248+
}
249+
}
250+
251+
return Column(
252+
mainAxisSize: .min,
253+
children: [
254+
...widget.exchangees.map(
255+
(e) => ExchProviderOption(
256+
exchange: e,
257+
estimate: null,
258+
pair: pair,
259+
rateString: "n/a",
260+
),
261+
),
262+
],
263+
);
264+
}
265+
}

0 commit comments

Comments
 (0)