dev
dart 349 lines 13.4 KB
Raw
1 import 'package:cake_wallet/evm/evm.dart';
2 import 'package:flutter/material.dart';
3 import 'package:flutter_svg/flutter_svg.dart';
4
5 import 'evm_switcher_row.dart';
6
7 class EvmSwitcherDataItem {
8 final String name;
9 final String svgPath;
10 final int chainId;
11
12 const EvmSwitcherDataItem({
13 required this.name,
14 required this.svgPath,
15 required this.chainId,
16 });
17 }
18
19 String _getSvgPathForChain(String chainName) {
20 final name = chainName.toLowerCase();
21
22 switch (name) {
23 case 'ethereum':
24 return 'assets/new-ui/crypto_full_icons/ethereum.svg';
25 case 'polygon':
26 return 'assets/new-ui/crypto_full_icons/polygon.svg';
27 case 'arbitrum':
28 return 'assets/new-ui/crypto_full_icons/arbitrum.svg';
29 case 'base':
30 return 'assets/new-ui/crypto_full_icons/base.svg';
31 case 'bnb':
32 case 'bsc':
33 return 'assets/new-ui/crypto_full_icons/bnb.svg';
34 default:
35 return 'assets/new-ui/crypto_full_icons/ethereum.svg';
36 }
37 }
38
39 class EvmSwitcher extends StatefulWidget {
40 const EvmSwitcher({
41 super.key,
42 required this.chains,
43 required this.currentChain,
44 required this.onChainSelected,
45 required this.hiddenChainIds,
46 required this.onHiddenChanged,
47 });
48
49 final List<ChainInfo> chains;
50 final ChainInfo? currentChain;
51 final Future<void> Function(int chainId) onChainSelected;
52 final Set<int> hiddenChainIds;
53 final void Function(Set<int> hiddenChainIds) onHiddenChanged;
54
55 static const editModeAnimDuration = Duration(milliseconds: 200);
56
57 @override
58 State<EvmSwitcher> createState() => _EvmSwitcherState();
59 }
60
61 class _EvmSwitcherState extends State<EvmSwitcher> {
62 bool _editMode = false;
63 var optionsEnabled = <bool>[];
64
65 @override
66 void initState() {
67 super.initState();
68 _syncOptionsEnabled();
69 }
70
71 @override
72 void didUpdateWidget(covariant EvmSwitcher oldWidget) {
73 super.didUpdateWidget(oldWidget);
74 if (oldWidget.chains != widget.chains ||
75 !_hiddenSetsEqual(oldWidget.hiddenChainIds, widget.hiddenChainIds)) {
76 _syncOptionsEnabled();
77 }
78 }
79
80 void _syncOptionsEnabled() {
81 optionsEnabled = widget.chains
82 .map((chain) => !widget.hiddenChainIds.contains(chain.chainId))
83 .toList(growable: false);
84 }
85
86 bool _hiddenSetsEqual(Set<int> a, Set<int> b) => a.length == b.length && a.containsAll(b);
87
88 int get _selectedIndex {
89 if (widget.currentChain == null) return -1;
90 return widget.chains.indexWhere(
91 (chain) => chain.chainId == widget.currentChain!.chainId,
92 );
93 }
94
95 bool shouldBuildSeparator(int index) {
96 final nextEnabled = optionsEnabled.indexWhere(
97 (e) => e,
98 index + 1,
99 );
100
101 return index != _selectedIndex &&
102 optionsEnabled[index] &&
103 nextEnabled != _selectedIndex &&
104 nextEnabled != -1;
105 }
106
107 Set<int> _currentHiddenChainIds() {
108 final hidden = <int>{};
109 for (var i = 0; i < widget.chains.length; i++) {
110 if (i < optionsEnabled.length && !optionsEnabled[i]) {
111 hidden.add(widget.chains[i].chainId);
112 }
113 }
114 return hidden;
115 }
116
117 @override
118 Widget build(BuildContext context) {
119 final double popupWidth = MediaQuery.of(context).size.width * 0.9;
120 return Center(
121 child: Column(
122 spacing: 25.0,
123 mainAxisAlignment: MainAxisAlignment.center,
124 children: [
125 Text(
126 _editMode ? "Customize options" : "Select Network",
127 style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
128 ),
129 ClipRRect(
130 borderRadius: BorderRadius.circular(20),
131 child: Container(
132 decoration: BoxDecoration(
133 borderRadius: BorderRadius.circular(20),
134 color: Theme.of(context).colorScheme.surfaceContainer,
135 ),
136 child: AnimatedContainer(
137 duration: EvmSwitcher.editModeAnimDuration,
138 width: popupWidth,
139 child: Row(
140 mainAxisAlignment: MainAxisAlignment.spaceBetween,
141 children: [
142 AnimatedSize(
143 curve: Curves.easeOutCubic,
144 duration: EvmSwitcher.editModeAnimDuration,
145 child: Container(
146 width: _editMode ? 0 : popupWidth,
147 child: Column(
148 mainAxisSize: MainAxisSize.min,
149 children: [
150 ListView.separated(
151 shrinkWrap: true,
152 physics: const NeverScrollableScrollPhysics(),
153 itemBuilder: (context, index) {
154 if (optionsEnabled[index]) {
155 final chain = widget.chains[index];
156 final data = EvmSwitcherDataItem(
157 name: chain.name,
158 svgPath: _getSvgPathForChain(chain.name),
159 chainId: chain.chainId,
160 );
161 return EvmSwitcherRow(
162 key: ValueKey(chain.chainId),
163 data: data,
164 editMode: false,
165 selected: index == _selectedIndex,
166 onTap: () {
167 widget.onChainSelected(chain.chainId);
168 if (mounted) {
169 Navigator.of(context).pop();
170 }
171 },
172 editSwitchValue: true,
173 animDuration: EvmSwitcher.editModeAnimDuration,
174 );
175 } else {
176 return Container();
177 }
178 },
179 separatorBuilder: (context, index) {
180 if (shouldBuildSeparator(index))
181 return Padding(
182 padding: const EdgeInsets.symmetric(horizontal: 18),
183 child: Container(
184 height: 1,
185 width: double.infinity,
186 color:
187 Theme.of(context).colorScheme.surfaceContainerHigh,
188 ));
189 else
190 return Container(height: 1);
191 },
192 itemCount: widget.chains.length),
193 EvmSwitcherAdditionalOption(
194 title: "Customize options",
195 svgPath: "assets/images/evm_switcher_arrow_right.svg",
196 animDuration: EvmSwitcher.editModeAnimDuration,
197 topSeparator: true,
198 bottomSeparator: false,
199 visible: true,
200 onTap: () {
201 setState(() {
202 _editMode = true;
203 });
204 },
205 iconOnRight: true)
206 ],
207 ),
208 ),
209 ),
210 AnimatedSize(
211 curve: Curves.easeOutCubic,
212 duration: EvmSwitcher.editModeAnimDuration,
213 child: Container(
214 width: _editMode ? popupWidth : 0,
215 height: _editMode ? null : 0,
216 child: Column(
217 mainAxisSize: MainAxisSize.min,
218 children: [
219 EvmSwitcherAdditionalOption(
220 title: "Back",
221 svgPath: "assets/images/evm_switcher_arrow_left.svg",
222 animDuration: EvmSwitcher.editModeAnimDuration,
223 topSeparator: false,
224 bottomSeparator: true,
225 visible: true,
226 onTap: () {
227 setState(() {
228 _editMode = false;
229 });
230 },
231 iconOnRight: false),
232 ListView.separated(
233 shrinkWrap: true,
234 physics: const NeverScrollableScrollPhysics(),
235 itemBuilder: (context, index) {
236 final chain = widget.chains[index];
237 final data = EvmSwitcherDataItem(
238 name: chain.name,
239 svgPath: _getSvgPathForChain(chain.name),
240 chainId: chain.chainId,
241 );
242 return EvmSwitcherRow(
243 key: ValueKey(chain.chainId),
244 data: data,
245 editMode: _editMode,
246 selected: index == _selectedIndex,
247 onTap: () {
248 setState(() {
249 optionsEnabled[index] = !optionsEnabled[index];
250 });
251 widget.onHiddenChanged(
252 _currentHiddenChainIds(),
253 );
254 },
255 editSwitchValue: optionsEnabled[index],
256 animDuration: EvmSwitcher.editModeAnimDuration,
257 );
258 },
259 separatorBuilder: (context, index) {
260 return Padding(
261 padding: const EdgeInsets.symmetric(horizontal: 18),
262 child: Container(
263 height: 1,
264 width: double.infinity,
265 color: Theme.of(context).colorScheme.surfaceContainerHigh,
266 ));
267 },
268 itemCount: widget.chains.length),
269 ],
270 ),
271 ),
272 ),
273 ],
274 )),
275 ),
276 ),
277 ],
278 ),
279 );
280 }
281 }
282
283 class EvmSwitcherAdditionalOption extends StatelessWidget {
284 const EvmSwitcherAdditionalOption(
285 {super.key,
286 required this.title,
287 required this.svgPath,
288 required this.animDuration,
289 required this.topSeparator,
290 required this.bottomSeparator,
291 required this.visible,
292 required this.onTap,
293 required this.iconOnRight});
294
295 final String title;
296 final String svgPath;
297 final Duration animDuration;
298 final bool topSeparator;
299 final bool bottomSeparator;
300 final bool visible;
301 final VoidCallback onTap;
302 final bool iconOnRight;
303
304 @override
305 Widget build(BuildContext context) {
306 return GestureDetector(
307 onTap: onTap,
308 behavior: HitTestBehavior.translucent,
309 child: Container(
310 child: Column(
311 mainAxisSize: MainAxisSize.max,
312 children: [
313 if (topSeparator)
314 Padding(
315 padding: const EdgeInsets.symmetric(horizontal: 18),
316 child: Container(
317 height: 1,
318 width: double.infinity,
319 color: Theme.of(context).colorScheme.surfaceContainerHigh,
320 )),
321 Padding(
322 padding: const EdgeInsets.all(18),
323 child: Row(
324 mainAxisSize: MainAxisSize.max,
325 spacing: 8.0,
326 children: [
327 if (!iconOnRight) SvgPicture.asset(svgPath, width: 16, height: 16),
328 Text(
329 title,
330 style: TextStyle(color: Theme.of(context).colorScheme.primary),
331 ),
332 if (iconOnRight) SvgPicture.asset(svgPath, width: 16, height: 16),
333 ],
334 ),
335 ),
336 if (bottomSeparator)
337 Padding(
338 padding: const EdgeInsets.symmetric(horizontal: 18),
339 child: Container(
340 height: 1,
341 width: double.infinity,
342 color: Theme.of(context).colorScheme.surfaceContainerHigh,
343 )),
344 ],
345 ),
346 ),
347 );
348 }
349 }