dev
dart 453 lines 16.5 KB
Raw
1 import 'dart:io';
2
3 import 'package:cake_wallet/generated/i18n.dart';
4 import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
5 import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
6 import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
7 import 'package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart';
8 import 'package:flutter/material.dart';
9 import 'package:flutter_mobx/flutter_mobx.dart';
10 import 'package:flutter_svg/svg.dart';
11
12 class PickerItem<Item> {
13 final String title;
14 final String? subtitle;
15 final String? hint;
16 final Item value;
17 final bool isSliderItem;
18
19 PickerItem(
20 {required this.title,
21 this.subtitle,
22 this.hint,
23 required this.value,
24 this.isSliderItem = false});
25 }
26
27 class NewPicker<Item> extends StatefulWidget {
28 const NewPicker({
29 super.key,
30 this.title = "Please select",
31 this.sliderPageTitle = "Custom value",
32 required this.items,
33 this.description,
34 required this.onItemSelected,
35 this.sliderInitialValue,
36 this.sliderMaxValue,
37 this.sliderValueDescription,
38 this.onSliderChanged,
39 required this.selectedIndex,
40 this.closeOnSelection = false,
41 });
42
43 final String title;
44 final String sliderPageTitle;
45 final String? description;
46 final String? sliderValueDescription;
47 final List<PickerItem<Item>> items;
48 final int selectedIndex;
49 final Function(Item) onItemSelected;
50 final double? sliderInitialValue;
51 final double? sliderMaxValue;
52 final Function(double)? onSliderChanged;
53 final bool closeOnSelection;
54
55 @override
56 State<NewPicker<Item>> createState() => _NewPickerState();
57 }
58
59 class _NewPickerState<Item> extends State<NewPicker<Item>> {
60 late double? sliderCurrentValue;
61
62 @override
63 void initState() {
64 super.initState();
65 sliderCurrentValue = widget.sliderInitialValue;
66 }
67
68 @override
69 Widget build(BuildContext context) {
70 return Observer(
71 builder: (_) => Container(
72 decoration: BoxDecoration(
73 color: Theme.of(context).colorScheme.surface,
74 borderRadius: BorderRadius.vertical(top: Radius.circular(24))),
75 child: Column(
76 mainAxisSize: MainAxisSize.min,
77 children: [
78 ModalTopBar(
79 title: widget.title,
80 leadingIcon: Icon(Icons.arrow_back_ios_new),
81 leadingSemanticLabel: S.of(context).seed_alert_back,
82 onLeadingPressed: Navigator.of(context).maybePop,
83 ),
84 Padding(
85 padding: const EdgeInsets.symmetric(horizontal: 12.0),
86 child: SafeArea(
87 child: Column(
88 mainAxisSize: MainAxisSize.min,
89 children: [
90 if (widget.description != null && widget.description!.isNotEmpty)
91 Text(
92 widget.description!,
93 textAlign: TextAlign.center,
94 style: TextStyle(
95 fontSize: 14,
96 fontWeight: FontWeight.w400,
97 color: Theme.of(context).colorScheme.onSurfaceVariant),
98 ),
99 SizedBox(height: 16),
100 ...widget.items.map((item) {
101 final isSelected = widget.items.indexOf(item) == widget.selectedIndex;
102 return Column(children: [
103 if (item.isSliderItem)
104 PickerSliderButton(
105 key: ValueKey(isSelected),
106 item: item,
107 isSelected: isSelected,
108 onSelected: (item) {
109 Navigator.of(context).push(MaterialPageRoute(
110 builder: (context) => Material(
111 child: PickerSliderPage(
112 title: widget.sliderPageTitle,
113 sliderInitialValue: sliderCurrentValue!,
114 valueDescription: widget.sliderValueDescription ?? "",
115 sliderMaxValue: widget.sliderMaxValue!,
116 onSubmitted: (value) {
117 setState(() {
118 sliderCurrentValue = value;
119 });
120 widget.onSliderChanged?.call(value);
121 widget.onItemSelected(item);
122 }),
123 )));
124 },
125 isFirst: widget.items.indexOf(item) == 0,
126 customSubtitle:
127 "${sliderCurrentValue?.toInt()} ${widget.sliderValueDescription}",
128 isLast: widget.items.indexOf(item) == widget.items.length - 1,
129 )
130 else
131 PickerRow(
132 item: item,
133 isSelected: isSelected,
134 onSelected: _itemSelected,
135 isFirst: widget.items.indexOf(item) == 0,
136 isLast: widget.items.indexOf(item) == widget.items.length - 1,
137 ),
138 if (widget.items.indexOf(item) != widget.items.length - 1)
139 Container(
140 color: Theme.of(context).colorScheme.surfaceContainer,
141 child: Padding(
142 padding: const EdgeInsets.symmetric(horizontal: 18.0),
143 child: Container(
144 height: 1,
145 color: Theme.of(context).colorScheme.surfaceContainerHigh,
146 ),
147 ),
148 )
149 ]);
150 }),
151 SizedBox(height: 16),
152 NewPrimaryButton(
153 onPressed: Navigator.of(context).maybePop,
154 text: "Continue",
155 color: Theme.of(context).colorScheme.primary,
156 textColor: Theme.of(context).colorScheme.onPrimary),
157 ],
158 ),
159 ),
160 )
161 ],
162 ),
163 ),
164 );
165 }
166
167 void _itemSelected(Item value) {
168 widget.onItemSelected(value);
169 if (widget.closeOnSelection) {
170 Navigator.of(context).pop();
171 }
172 }
173 }
174
175 class PickerSliderButton<Item> extends StatelessWidget {
176 const PickerSliderButton(
177 {super.key,
178 required this.item,
179 required this.isSelected,
180 required this.onSelected,
181 required this.isFirst,
182 required this.isLast,
183 this.customSubtitle});
184
185 final PickerItem<Item> item;
186 final bool isSelected;
187 final bool isFirst;
188 final bool isLast;
189 final String? customSubtitle;
190 final Function(Item) onSelected;
191
192 @override
193 Widget build(BuildContext context) {
194 return GestureDetector(
195 onTap: () {
196 onSelected(item.value);
197 },
198 child: Container(
199 height: 64,
200 width: MediaQuery.of(context).size.height * 0.9,
201 decoration: BoxDecoration(
202 color: Theme.of(context).colorScheme.surfaceContainer,
203 borderRadius: BorderRadius.vertical(
204 top: isFirst ? Radius.circular(16) : Radius.circular(0),
205 bottom: isLast ? Radius.circular(16) : Radius.circular(0))),
206 child: Padding(
207 padding: const EdgeInsets.symmetric(horizontal: 12.0),
208 child: Row(
209 mainAxisAlignment: MainAxisAlignment.spaceBetween,
210 children: [
211 Column(
212 mainAxisSize: MainAxisSize.min,
213 crossAxisAlignment: CrossAxisAlignment.start,
214 children: [
215 Row(
216 spacing: 8,
217 children: [
218 Text(
219 item.title,
220 style: TextStyle(
221 fontSize: 14,
222 color: Theme.of(context).colorScheme.onSurface,
223 fontWeight: FontWeight.w500),
224 ),
225 if (item.hint != null)
226 Text(item.hint!,
227 style: TextStyle(
228 fontSize: 14,
229 color: Theme.of(context).colorScheme.onSurfaceVariant,
230 fontWeight: FontWeight.w400)),
231 if (isSelected)
232 CakeImageWidget(
233 imageUrl: "assets/new-ui/arrow_right.svg",
234 colorFilter: ColorFilter.mode(
235 Theme.of(context).colorScheme.primary, BlendMode.srcIn),
236 )
237 ],
238 ),
239 if (customSubtitle != null || item.subtitle != null)
240 Text(customSubtitle ?? item.subtitle!,
241 style: TextStyle(
242 fontSize: 12,
243 color: Theme.of(context).colorScheme.onSurfaceVariant,
244 fontWeight: FontWeight.w400))
245 ],
246 ),
247 isSelected
248 ? NewSimpleCheckbox(value: true, onChanged: (val) {})
249 : CakeImageWidget(
250 imageUrl: "assets/new-ui/arrow_right.svg",
251 colorFilter:
252 ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
253 )
254 ],
255 ),
256 ),
257 ),
258 );
259 }
260 }
261
262 class PickerRow<Item> extends StatelessWidget {
263 const PickerRow(
264 {super.key,
265 required this.item,
266 required this.isSelected,
267 required this.onSelected,
268 required this.isFirst,
269 required this.isLast});
270
271 final PickerItem<Item> item;
272 final bool isSelected;
273 final bool isFirst;
274 final bool isLast;
275 final Function(Item) onSelected;
276
277 @override
278 Widget build(BuildContext context) {
279 return GestureDetector(
280 onTap: () {
281 onSelected(item.value);
282 },
283 child: Container(
284 height: 64,
285 width: MediaQuery.of(context).size.height * 0.9,
286 decoration: BoxDecoration(
287 color: Theme.of(context).colorScheme.surfaceContainer,
288 borderRadius: BorderRadius.vertical(
289 top: isFirst ? Radius.circular(16) : Radius.circular(0),
290 bottom: isLast ? Radius.circular(16) : Radius.circular(0))),
291 child: Padding(
292 padding: const EdgeInsets.symmetric(horizontal: 12.0),
293 child: Row(
294 mainAxisAlignment: MainAxisAlignment.spaceBetween,
295 crossAxisAlignment: CrossAxisAlignment.center,
296 children: [
297 Column(
298 crossAxisAlignment: CrossAxisAlignment.start,
299 mainAxisSize: MainAxisSize.max,
300 mainAxisAlignment: MainAxisAlignment.center,
301 children: [
302 Row(
303 spacing: 4,
304 children: [
305 Text(item.title,
306 style: TextStyle(
307 fontSize: 14,
308 color: Theme.of(context).colorScheme.onSurface,
309 fontWeight: FontWeight.w500)),
310 if (item.hint != null)
311 Text(item.hint!,
312 style: TextStyle(
313 fontSize: 14,
314 color: Theme.of(context).colorScheme.onSurfaceVariant,
315 fontWeight: FontWeight.w400))
316 ],
317 ),
318 item.subtitle != null
319 ? Text(item.subtitle!,
320 style: TextStyle(
321 fontSize: 12,
322 color: Theme.of(context).colorScheme.onSurfaceVariant,
323 fontWeight: FontWeight.w400))
324 : SizedBox.shrink()
325 ],
326 ),
327 AnimatedSwitcher(
328 duration: Duration(milliseconds: 100),
329 child: isSelected
330 ? NewSimpleCheckbox(
331 key: ValueKey(1),
332 value: true,
333 onChanged: (val) {
334 onSelected(item.value);
335 })
336 : NewSimpleCheckbox(
337 key: ValueKey(0),
338 value: false,
339 onChanged: (val) {
340 onSelected(item.value);
341 }))
342 ],
343 ),
344 ),
345 ),
346 );
347 }
348 }
349
350 class PickerSliderPage<Item> extends StatefulWidget {
351 const PickerSliderPage(
352 {super.key,
353 required this.sliderInitialValue,
354 required this.sliderMaxValue,
355 required this.onSubmitted,
356 required this.title,
357 this.valueDescription = ""});
358
359 final double sliderInitialValue;
360 final double sliderMaxValue;
361 final Function(double) onSubmitted;
362 final String title;
363 final String valueDescription;
364
365 @override
366 State<PickerSliderPage<Item>> createState() => _PickerSliderPageState();
367 }
368
369 class _PickerSliderPageState<Item> extends State<PickerSliderPage<Item>> {
370 late double sliderValue;
371
372 @override
373 void initState() {
374 super.initState();
375 sliderValue = widget.sliderInitialValue;
376 }
377
378 @override
379 Widget build(BuildContext context) {
380 return Container(
381 decoration: BoxDecoration(
382 color: Theme.of(context).colorScheme.surface,
383 borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
384 child: SafeArea(
385 child: Column(
386 spacing: 36,
387 mainAxisSize: MainAxisSize.max,
388 children: [
389 ModalTopBar(
390 title: widget.title,
391 leadingIcon: Icon(Icons.arrow_back_ios_new),
392 leadingSemanticLabel: S.of(context).seed_alert_back,
393 onLeadingPressed: Navigator.of(context).pop,
394 ),
395 Expanded(
396 child: Column(
397 mainAxisAlignment: MainAxisAlignment.center,
398 mainAxisSize: MainAxisSize.max,
399 children: [
400 Row(
401 mainAxisAlignment: MainAxisAlignment.center,
402 spacing: 4,
403 children: [
404 Text(
405 sliderValue.toStringAsFixed(0),
406 style: TextStyle(
407 fontSize: 18,
408 fontWeight: FontWeight.w600,
409 color: Theme.of(context).colorScheme.primary),
410 ),
411 Text(widget.valueDescription,
412 style: TextStyle(
413 fontSize: 18,
414 fontWeight: FontWeight.w600,
415 color: Theme.of(context).colorScheme.onSurface)),
416 ],
417 ),
418 Padding(
419 padding: const EdgeInsets.symmetric(horizontal: 16.0),
420 child: Slider(
421 activeColor: Theme.of(context).colorScheme.primary,
422 inactiveColor: Theme.of(context).colorScheme.surfaceContainer,
423 thumbColor: Theme.of(context).colorScheme.onSurface,
424 value: sliderValue,
425 onChanged: (value) {
426 setState(() {
427 sliderValue = value.roundToDouble();
428 });
429 },
430 min: 1,
431 max: widget.sliderMaxValue,
432 ),
433 ),
434 ],
435 ),
436 ),
437 Padding(
438 padding: const EdgeInsets.all(16.0),
439 child: NewPrimaryButton(
440 onPressed: () {
441 widget.onSubmitted(sliderValue);
442 Navigator.of(context).pop();
443 },
444 text: "Continue",
445 color: Theme.of(context).colorScheme.primary,
446 textColor: Theme.of(context).colorScheme.onPrimary),
447 )
448 ],
449 ),
450 ),
451 );
452 }
453 }