dev
dart 350 lines 14.5 KB
Raw
1 import 'package:cake_wallet/generated/i18n.dart';
2 import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
3 import 'package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart';
4 import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
5 import 'package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart';
6 import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
7 import 'package:cw_core/currency_for_wallet_type.dart';
8 import 'package:cw_core/wallet_info.dart';
9 import 'package:cw_core/wallet_type.dart' show WalletType;
10 import 'package:flutter/material.dart';
11 import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
12
13 class SwapAddressSelectionResult {
14 String? address;
15 WalletInfo? walletInfo;
16 String? accountName;
17
18 SwapAddressSelectionResult({this.address, this.walletInfo, this.accountName});
19 }
20
21 class SwapAddressSelectionModal extends StatefulWidget {
22 const SwapAddressSelectionModal(
23 {super.key, required this.isSelectingReceiver, required this.exchangeViewModel});
24
25 final bool isSelectingReceiver;
26 final ExchangeViewModel exchangeViewModel;
27
28 @override
29 State<SwapAddressSelectionModal> createState() => _SwapAddressSelectionModalState();
30 }
31
32 class _SwapAddressSelectionModalState extends State<SwapAddressSelectionModal> {
33 List<WalletInfo> items = [];
34 Map<String, List<WalletInfoAddressInfo>> accounts = {};
35 final addressController = TextEditingController();
36 bool textEntered = false;
37
38 bool _itemsLoaded = false;
39
40 @override
41 void initState() {
42 super.initState();
43
44 addressController.addListener(() {
45 setState(() {
46 textEntered = addressController.text.isNotEmpty;
47 });
48 });
49
50 () async {
51 items = widget.isSelectingReceiver
52 ? await widget.exchangeViewModel.receiveWallets
53 : await widget.exchangeViewModel.depositWallets;
54 for (final item in items) {
55 if (item.type == WalletType.monero) {
56 accounts[item.id] = await widget.exchangeViewModel.addressesForAccountsWallet(item);
57 }
58 }
59 setState(() {
60 _itemsLoaded = true;
61 });
62 }.call();
63 }
64
65 @override
66 Widget build(BuildContext context) {
67 final currency = widget.isSelectingReceiver
68 ? widget.exchangeViewModel.receiveCurrency
69 : widget.exchangeViewModel.depositCurrency;
70
71 if (!_itemsLoaded) return SizedBox.shrink();
72 return Container(
73 decoration: BoxDecoration(
74 color: Theme.of(context).colorScheme.surface,
75 borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
76 child: SafeArea(
77 child: Column(
78 mainAxisSize: MainAxisSize.min,
79 children: [
80 ModalTopBar(
81 title: widget.isSelectingReceiver
82 ? "${S.of(context).receive_to}..."
83 : "${S.of(context).send_from}...",
84 leadingIcon: Icon(Icons.close),
85 leadingSemanticLabel: S.of(context).close,
86 onLeadingPressed: Navigator.of(context).pop,
87 ),
88 Flexible(
89 child: Padding(
90 padding: const EdgeInsets.symmetric(horizontal: 18.0),
91 child: items.isEmpty
92 ? Center(
93 child: Text(
94 "${S.of(context).no_wallets_for} ${widget.isSelectingReceiver ? currency.fullName : currency.fullName}.",
95 style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
96 ))
97 : ListView.builder(
98 padding: EdgeInsets.only(bottom: 12),
99 shrinkWrap: true,
100 controller: ModalScrollController.of(context),
101 itemCount: items.length,
102 itemBuilder: (context, index) {
103 final item = items[index];
104
105 late final bool selected;
106 if (widget.isSelectingReceiver) {
107 selected = widget.exchangeViewModel.receiveAddress == item.address;
108 } else {
109 selected = widget.exchangeViewModel.wallet.name == item.name &&
110 !widget.exchangeViewModel.isSendFromExternal;
111 }
112
113 final String currencyIconPath =
114 getCryptoCurrencyIconForWalletListItem(item.type);
115
116 final bool hasAccounts =
117 item.type == WalletType.monero && widget.isSelectingReceiver;
118
119 List<WalletInfoAddressInfo>? accounts =
120 hasAccounts ? this.accounts[item.id] : null;
121
122 return Padding(
123 padding: const EdgeInsets.symmetric(vertical: 6.0),
124 child: SwapAddressSelectionModalRow(
125 wallet: item,
126 iconPath: currencyIconPath,
127 isSelected: selected,
128 accounts: accounts,
129 onAddressChosen: (address, accountName) {
130 Navigator.of(context).pop(SwapAddressSelectionResult(
131 address: address,
132 walletInfo: item,
133 accountName: accountName));
134 },
135 ),
136 );
137 },
138 ),
139 ),
140 ),
141 Container(
142 height: 1,
143 color: Theme.of(context).colorScheme.surfaceContainerHigh,
144 ),
145 Padding(
146 padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 18),
147 child: !widget.isSelectingReceiver
148 ? GestureDetector(
149 behavior: HitTestBehavior.opaque,
150 onTap: () {
151 Navigator.of(context).pop(SwapAddressSelectionResult());
152 },
153 child: Container(
154 height: 64,
155 decoration: BoxDecoration(
156 color: Theme.of(context).colorScheme.surfaceContainer,
157 borderRadius: BorderRadius.circular(16)),
158 child: Row(
159 mainAxisAlignment: MainAxisAlignment.center,
160 spacing: 10,
161 children: [
162 CakeImageWidget(
163 imageUrl: "assets/new-ui/send_from_external.svg",
164 colorFilter: ColorFilter.mode(
165 Theme.of(context).colorScheme.primary, BlendMode.srcIn)),
166 Text(S.of(context).send_from_external,
167 style: TextStyle(
168 fontSize: 15,
169 fontWeight: FontWeight.w500,
170 color: Theme.of(context).colorScheme.primary))
171 ],
172 ),
173 ),
174 )
175 : Row(
176 children: [
177 Flexible(
178 child: NewSendAddressInput(
179 addressController: addressController,
180 selectedCurrency: widget.exchangeViewModel.receiveCurrency,
181 onEditingComplete: () {},
182 bottomPadding: true,
183 ),
184 ),
185 AnimatedScale(
186 alignment: Alignment.centerLeft,
187 scale: textEntered ? 1.0 : 0.0,
188 duration: const Duration(milliseconds: 200),
189 curve: Curves.easeOutCubic,
190 child: Row(
191 children: [
192 SizedBox(width: textEntered ? 8 : 0),
193 GestureDetector(
194 onTap: () {
195 Navigator.of(context).pop(SwapAddressSelectionResult(
196 address: addressController.text));
197 },
198 child: AnimatedContainer(
199 duration: const Duration(milliseconds: 200),
200 curve: Curves.easeOutCubic,
201 width: textEntered ? 48 : 0,
202 height: 48,
203 decoration: BoxDecoration(
204 color: Theme.of(context).colorScheme.primary,
205 shape: BoxShape.circle,
206 ),
207 child: Icon(
208 Icons.arrow_forward_ios,
209 color: Theme.of(context).colorScheme.onPrimary,
210 ),
211 ),
212 ),
213 ],
214 ),
215 )
216 ],
217 ),
218 )
219 ],
220 ),
221 ));
222 }
223 }
224
225 class SwapAddressSelectionModalRow extends StatefulWidget {
226 const SwapAddressSelectionModalRow(
227 {super.key,
228 required this.wallet,
229 required this.iconPath,
230 required this.isSelected,
231 required this.onAddressChosen,
232 this.accounts});
233
234 final WalletInfo wallet;
235 final String iconPath;
236 final bool isSelected;
237 final Function(String, String?) onAddressChosen;
238 final List<WalletInfoAddressInfo>? accounts;
239
240 @override
241 State<SwapAddressSelectionModalRow> createState() => _SwapAddressSelectionModalRowState();
242 }
243
244 class _SwapAddressSelectionModalRowState extends State<SwapAddressSelectionModalRow> {
245 bool _isExpanded = false;
246
247 @override
248 Widget build(BuildContext context) {
249 return Container(
250 decoration: BoxDecoration(
251 color: Theme.of(context).colorScheme.surfaceContainer,
252 borderRadius: BorderRadius.circular(16)),
253 child: Padding(
254 padding: const EdgeInsets.symmetric(horizontal: 12.0),
255 child: Column(
256 children: [
257 GestureDetector(
258 behavior: HitTestBehavior.opaque,
259 onTap: () {
260 if (widget.accounts != null) {
261 setState(() {
262 _isExpanded = !_isExpanded;
263 });
264 } else {
265 widget.onAddressChosen(widget.wallet.address, null);
266 }
267 },
268 child: Container(
269 height: 64,
270 child: Row(
271 mainAxisAlignment: MainAxisAlignment.spaceBetween,
272 children: [
273 Row(
274 spacing: 12,
275 children: [
276 CakeImageWidget(imageUrl: widget.iconPath, width: 24, height: 24),
277 Text(widget.wallet.name)
278 ],
279 ),
280 widget.accounts != null
281 ? AnimatedRotation(
282 duration: Duration(milliseconds: 200),
283 curve: Curves.easeOutCubic,
284 turns: _isExpanded ? 0 : 0.5,
285 child: CakeImageWidget(
286 imageUrl: "assets/new-ui/dropdown_arrow.svg",
287 colorFilter: ColorFilter.mode(
288 Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
289 ))
290 : NewSimpleCheckbox(value: widget.isSelected, onChanged: (value) {}),
291 ],
292 ),
293 ),
294 ),
295 if (widget.accounts != null)
296 AnimatedSwitcher(
297 duration: const Duration(milliseconds: 200),
298 switchInCurve: Curves.easeOutCubic,
299 switchOutCurve: Curves.easeInCubic,
300 transitionBuilder: (Widget child, Animation<double> animation) {
301 return SizeTransition(
302 sizeFactor: animation,
303 axisAlignment: -1.0,
304 child: child,
305 );
306 },
307 child: Container(
308 key: ValueKey(_isExpanded),
309 height: _isExpanded ? null : 0,
310 width: double.infinity,
311 child: Column(
312 children: widget.accounts!.map((item) {
313 return Column(
314 children: [
315 Container(
316 height: 1,
317 color: Theme.of(context).colorScheme.surfaceContainerHigh,
318 ),
319 GestureDetector(
320 behavior: HitTestBehavior.opaque,
321 onTap: () {
322 widget.onAddressChosen(item.address, item.label);
323 },
324 child: SizedBox(
325 height: 48,
326 child: Row(
327 spacing: 12,
328 children: [
329 CakeImageWidget(
330 imageUrl: "assets/new-ui/account.svg",
331 colorFilter: ColorFilter.mode(
332 Theme.of(context).colorScheme.onSurfaceVariant,
333 BlendMode.srcIn),
334 ),
335 Text(item.label)
336 ],
337 ),
338 ),
339 ),
340 ],
341 );
342 }).toList(),
343 ),
344 ))
345 ],
346 ),
347 ),
348 );
349 }
350 }