dev
dart 312 lines 13.5 KB
Raw
1 import 'package:cake_wallet/core/utilities.dart';
2 import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
3 import 'package:cake_wallet/exchange/exchange_provider_description.dart';
4 import 'package:cake_wallet/generated/i18n.dart';
5 import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
6 import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
7 import 'package:cake_wallet/new-ui/widgets/send_page/send_confirm_bottom_widget.dart';
8 import 'package:cake_wallet/new-ui/widgets/send_page/send_confirm_sheet.dart';
9 import 'package:cake_wallet/new-ui/widgets/swap_page/swap_modal_header.dart';
10 import 'package:cake_wallet/new-ui/widgets/swap_page/swap_send_external_modal.dart';
11 import 'package:cake_wallet/routes.dart';
12 import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
13 import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
14 import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
15 import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
16 import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
17 import 'package:cw_core/amount/money.dart';
18 import 'package:cw_core/crypto_amount_format.dart';
19 import 'package:cw_core/crypto_currency.dart';
20 import 'package:cw_core/currencies_with_memo.dart';
21 import 'package:cw_core/currency_for_wallet_type.dart';
22 import 'package:cw_core/wallet_type.dart';
23 import 'package:flutter/material.dart';
24 import 'package:flutter/services.dart';
25 import 'package:flutter_mobx/flutter_mobx.dart';
26 import 'package:mobx/mobx.dart';
27 import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
28
29 class SwapConfirmSheet extends StatefulWidget {
30 const SwapConfirmSheet(
31 {super.key,
32 required this.exchangeViewModel,
33 required this.exchangeTradeViewModel,
34 required this.receiveAmount});
35
36 final ExchangeViewModel exchangeViewModel;
37 final ExchangeTradeViewModel exchangeTradeViewModel;
38 final String receiveAmount;
39
40 @override
41 State<SwapConfirmSheet> createState() => _SwapConfirmSheetState();
42 }
43
44 class _SwapConfirmSheetState extends State<SwapConfirmSheet> {
45 void beginSend() async {
46 final sendVM = widget.exchangeTradeViewModel.sendViewModel;
47
48 if (sendVM.wallet.isHardwareWallet) {
49 if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) {
50 await Navigator.of(context).pushNamed(
51 Routes.connectDevices,
52 arguments: ConnectDevicePageParams(
53 walletType: sendVM.walletType,
54 hardwareWalletType: sendVM.wallet.walletInfo.hardwareWalletType!,
55 onConnectDevice: (context, _) {
56 sendVM.hardwareWalletViewModel!.initWallet(sendVM.wallet);
57 Navigator.of(context).pop();
58 },
59 isReconnect: false,
60 ),
61 );
62
63 // Recheck to handle tap-backs
64 if (!sendVM.hardwareWalletViewModel!.isConnected(sendVM.walletType)) {
65 return;
66 }
67 } else {
68 sendVM.hardwareWalletViewModel!.initWallet(sendVM.wallet);
69 }
70 }
71
72 widget.exchangeTradeViewModel.confirmSending();
73 }
74
75 @override
76 void initState() {
77 super.initState();
78
79 if (!widget.exchangeViewModel.isSendFromExternal) {
80 beginSend();
81 }
82
83 reaction((context) => widget.exchangeTradeViewModel.sendViewModel.state, (state) {
84 if (state is TransactionCommitted) {
85 Future.delayed(const Duration(seconds: 2), () {
86 if (mounted) Navigator.of(context).pop();
87 });
88 }
89 });
90 }
91
92 @override
93 Widget build(BuildContext context) {
94 return PopScope(
95 onPopInvokedWithResult: (didPop, result) {
96 Navigator.of(context, rootNavigator: true).pop();
97 },
98 child: Container(
99 decoration: BoxDecoration(
100 color: Theme.of(context).colorScheme.surface,
101 borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
102 ),
103 child: SafeArea(
104 child: Observer(
105 builder: (_) {
106 final commited =
107 widget.exchangeTradeViewModel.sendViewModel.state is TransactionCommitted;
108 return Stack(
109 fit: StackFit.loose,
110 children: [
111 Positioned.fill(
112 child: AnimatedSlide(
113 offset: commited ? Offset.zero : const Offset(1, 0),
114 duration: const Duration(milliseconds: 300),
115 curve: Curves.easeOutCubic,
116 child: const TransactionCommitedScreen(),
117 )),
118 AnimatedSlide(
119 offset: commited ? const Offset(-1, 0) : Offset.zero,
120 duration: const Duration(milliseconds: 300),
121 curve: Curves.easeOutCubic,
122 child: SwapTransactionDetails(
123 exchangeViewModel: widget.exchangeViewModel,
124 exchangeTradeViewModel: widget.exchangeTradeViewModel,
125 receiveAmount: widget.receiveAmount,
126 ),
127 ),
128 ],
129 );
130 },
131 ),
132 ),
133 ));
134 }
135 }
136
137 class SwapTransactionDetails extends StatelessWidget {
138 const SwapTransactionDetails(
139 {super.key,
140 required this.exchangeViewModel,
141 required this.exchangeTradeViewModel,
142 required this.receiveAmount});
143
144 final ExchangeViewModel exchangeViewModel;
145 final ExchangeTradeViewModel exchangeTradeViewModel;
146 final String receiveAmount;
147
148 @override
149 Widget build(BuildContext context) {
150 return Column(
151 mainAxisSize: MainAxisSize.min,
152 children: [
153 ModalTopBar(
154 title: "",
155 leadingWidget: SwapModalHeader(
156 fromIconPath: exchangeViewModel.depositCurrency.iconPath ?? "",
157 toIconPath: exchangeViewModel.receiveCurrency.iconPath ?? ""),
158 trailingIcon: Icon(Icons.close),
159 trailingSemanticLabel: S.of(context).close,
160 onTrailingPressed: Navigator.of(context).maybePop,
161 ),
162 SafeArea(
163 top: false,
164 child: Padding(
165 padding: const EdgeInsets.symmetric(horizontal: 16.0),
166 child: Column(
167 spacing: 24,
168 children: [
169 Observer(
170 builder: (_) => NewListSections(showHeader: true, sections: {
171 S.of(context).send: [
172 ListItemRegularRow(
173 showArrow: false,
174 keyValue: "send value",
175 label: exchangeViewModel.depositCurrency.fullName ?? "",
176 iconPath: exchangeViewModel.depositCurrency.iconPath ?? "",
177 badgeIconPath: _resolveChainBadgePath(exchangeViewModel.depositCurrency),
178 trailingText: exchangeViewModel.amountParsingProxy
179 .asDisplayStringWithSymbol(exchangeViewModel.depositCurrency
180 .tryParseAmount(exchangeTradeViewModel.trade.amount) ??
181 Money.zero(exchangeViewModel.depositCurrency)),
182 ),
183 if (exchangeTradeViewModel.sendViewModel.pendingTransaction != null)
184 ListItemRegularRow(
185 showArrow: false,
186 keyValue: "fee",
187 label: S.of(context).fee,
188 trailingText:
189 "${exchangeTradeViewModel.sendViewModel.pendingTransaction?.feeFormatted} (${exchangeTradeViewModel.pendingTransactionFeeFiatAmountFormatted})"),
190 ListItemRegularRow(
191 keyValue: "sender",
192 label: S.of(context).from,
193 trailingText: exchangeViewModel.isSendFromExternal
194 ? S.of(context).external_wallet
195 : exchangeViewModel.wallet.name,
196 showArrow: false)
197 ],
198 S.of(context).receive: [
199 ListItemRegularRow(
200 showArrow: false,
201 keyValue: "receive value",
202 label: exchangeViewModel.receiveCurrency.fullName ?? "",
203 iconPath: exchangeViewModel.receiveCurrency.iconPath ?? "",
204 badgeIconPath: _resolveChainBadgePath(exchangeViewModel.receiveCurrency),
205 trailingText: (receiveAmount.withMaxDecimals(8)) +
206 " " +
207 (exchangeViewModel.receiveCurrency.title),
208 ),
209 ListItemRegularRow(
210 keyValue: "receiver",
211 label: S.of(context).to,
212 showArrow: false,
213 trailingText: exchangeViewModel.receiveAddressDisplayName ??
214 middleTruncate(
215 exchangeTradeViewModel.trade.payoutAddress ?? "", 8, 8)),
216 if ((exchangeTradeViewModel.trade.toAddressExtraId ?? '').isNotEmpty)
217 ListItemRegularRow(
218 keyValue: "receive memo",
219 showArrow: false,
220 label: memoLabelTypeFor(exchangeViewModel.receiveCurrency) ==
221 MemoLabelType.destinationTag
222 ? S.of(context).destination_tag
223 : S.of(context).memo,
224 trailingText: middleTruncate(
225 exchangeTradeViewModel.trade.toAddressExtraId ?? "", 8, 8),
226 copyableText: exchangeTradeViewModel.trade.toAddressExtraId,
227 ),
228 ],
229 "${S.of(context).swap_id} (${S.of(context).tap_to_copy})": [
230 ListItemRegularRow(
231 showArrow: false,
232 keyValue: "provider",
233 onTap: () => Clipboard.setData(
234 ClipboardData(text: exchangeTradeViewModel.trade.id)),
235 label: exchangeTradeViewModel.trade.provider.title,
236 iconPath: exchangeTradeViewModel.trade.provider.image,
237 trailingIconPath: "assets/new-ui/copy.svg",
238 trailingText: exchangeTradeViewModel.trade.id.toString().length > 18
239 ? null
240 : exchangeTradeViewModel.trade.id,
241 bottomWidget: exchangeTradeViewModel.trade.id.toString().length <= 18
242 ? null
243 : Padding(
244 padding: const EdgeInsets.only(top: 12.0),
245 child: Text(
246 exchangeTradeViewModel.trade.id,
247 style: TextStyle(
248 color: Theme.of(context).colorScheme.onSurfaceVariant),
249 ),
250 )),
251 if (exchangeTradeViewModel.trade.provider ==
252 ExchangeProviderDescription.trocador)
253 ListItemRegularRow(
254 showArrow: false,
255 keyValue: "trocador provider name",
256 label: "Trocador ${S.of(context).provider}",
257 trailingText: exchangeTradeViewModel.trade.providerName ?? "")
258 ]
259 }),
260 ),
261 Padding(
262 padding: const EdgeInsets.only(bottom: 8.0),
263 child: exchangeViewModel.isSendFromExternal
264 ? NewPrimaryButton(
265 onPressed: () => _showExternalSendModal(context),
266 text: S.of(context).continue_text,
267 color: Theme.of(context).colorScheme.primary,
268 textColor: Theme.of(context).colorScheme.onPrimary)
269 : SendConfirmBottomWidget(
270 sendViewModel: exchangeTradeViewModel.sendViewModel),
271 ),
272 ],
273 ),
274 ),
275 )
276 ],
277 );
278 }
279
280 void _showExternalSendModal(BuildContext context) {
281 if (context.mounted) {
282 showMaterialModalBottomSheet(
283 context: context,
284 builder: (context) {
285 return SwapSendExternalModal(
286 amount: exchangeTradeViewModel.trade.amount,
287 exchangeTradeViewModel: exchangeTradeViewModel,
288 from: exchangeTradeViewModel.trade.from ?? exchangeViewModel.depositCurrency,
289 to: exchangeTradeViewModel.trade.to ?? exchangeViewModel.receiveCurrency,
290 address: exchangeTradeViewModel.trade.inputAddress ?? "");
291 });
292 }
293 }
294 }
295
296 String? _resolveChainBadgePath(CryptoCurrency currency) {
297 try {
298 if (currency.chainIconPath != null) return currency.chainIconPath;
299
300 final tag = currency.tag;
301 if (tag != null && tag.isNotEmpty) {
302 final byTag = CryptoCurrency.fromString(tag);
303 if (byTag.chainIconPath != null) return byTag.chainIconPath;
304 }
305
306 final walletType = cryptoCurrencyOrTokenToWalletType(currency);
307 if (walletType != null) {
308 return walletTypeToCryptoCurrency(walletType).chainIconPath;
309 }
310 } catch (_) {}
311 return null;
312 }