dev
dart 360 lines 20.3 KB
Raw
1 import 'dart:async';
2 import 'dart:math';
3
4 import 'package:cake_wallet/generated/i18n.dart';
5 import 'package:cake_wallet/new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
6 import 'package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.dart';
7 import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
8 import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
9 import 'package:cw_core/card_design.dart';
10 import 'package:flutter/material.dart';
11 import 'package:flutter_bloc/flutter_bloc.dart';
12
13 class CardCustomizer extends StatefulWidget {
14 const CardCustomizer({super.key, required this.cryptoTitle, required this.cryptoName});
15
16 final String cryptoTitle;
17 final String cryptoName;
18
19 @override
20 State<CardCustomizer> createState() => _CardCustomizerState();
21 }
22
23 class _CardCustomizerState extends State<CardCustomizer> {
24 final accountNameController = TextEditingController();
25 bool editEnabled = false;
26 late final CardCustomizerBloc bloc;
27
28 @override
29 void initState() {
30 super.initState();
31
32 bloc = context.read<CardCustomizerBloc>();
33
34 if (bloc.state is! CardCustomizerNotLoaded) {
35 editEnabled = bloc.state.accountName.isNotEmpty;
36 accountNameController.text = bloc.state.accountName;
37 } else {
38 late final StreamSubscription sub;
39 sub = bloc.stream.listen((state) {
40 if (state is! CardCustomizerNotLoaded) {
41 setState(() {
42 editEnabled = state.accountName.isNotEmpty;
43 });
44 sub.cancel();
45 }
46 });
47 }
48 }
49
50 bool _showIconStylePanel(CardCustomizerState state) {
51 final design = state.availableDesigns[state.selectedDesignIndex];
52 return design.backgroundType == CardDesignBackgroundTypes.svgIcon &&
53 state.availableIconPaths.isNotEmpty;
54 }
55
56 CardDesign _cardStylePreviewDesign(CardCustomizerState state, int index) {
57 var design = state.availableDesigns[index].withGradient(state.selectedColor);
58 if (design.backgroundType == CardDesignBackgroundTypes.svgIcon &&
59 state.availableIconPaths.isNotEmpty) {
60 design = design.withIcon(state.availableIconPaths[state.selectedIconIndex]);
61 }
62 return design;
63 }
64
65 @override
66 Widget build(BuildContext context) {
67 return BlocListener<CardCustomizerBloc, CardCustomizerState>(
68 listenWhen: (previous, current) => previous.accountName != current.accountName,
69 listener: (context, state) {
70 if (accountNameController.text != state.accountName) {
71 accountNameController.text = state.accountName;
72 }
73 },
74 child: BlocBuilder<CardCustomizerBloc, CardCustomizerState>(
75 builder: (context, state) {
76 if (state is CardCustomizerNotLoaded) return SizedBox.shrink();
77 return PopScope(
78 child: SingleChildScrollView(
79 child: SafeArea(
80 child: Column(
81 spacing: 25.0,
82 mainAxisSize: MainAxisSize.min,
83 children: [
84 ModalTopBar(
85 title: editEnabled ? S.of(context).edit_account : S.of(context).edit_card,
86 leadingIcon: Icon(Icons.close),
87 leadingSemanticLabel: S.of(context).close,
88 // trailingIcon: editEnabled ? Icon(Icons.delete_forever) : null,
89 onLeadingPressed: () => Navigator.of(context).maybePop(),
90 // onTrailingPressed: () {},
91 ),
92 if (editEnabled)
93 Padding(
94 padding: const EdgeInsets.symmetric(horizontal: 18.0),
95 child: Column(
96 spacing: 8.0,
97 crossAxisAlignment: CrossAxisAlignment.start,
98 children: [
99 Text(S.of(context).account_name),
100 TextField(
101 maxLength: 32,
102 decoration: InputDecoration(counterText: ""),
103 onChanged: (value) {
104 context.read<CardCustomizerBloc>().add(AccountNameChanged(value));
105 },
106 controller: accountNameController,
107 )
108 ],
109 ),
110 ),
111 BalanceCard(
112 width: min(MediaQuery.of(context).size.width * 0.87, 768),
113 selected: true,
114 designSwitchDuration: Duration(milliseconds: 300),
115 accountName: editEnabled ? state.accountName : "",
116 balance: "0.00",
117 assetName: state.displaySats ? "sats" : widget.cryptoName,
118 capitalizeAssetName: !state.displaySats,
119 design: state.selectedDesign,
120 ),
121 Padding(
122 padding: const EdgeInsets.symmetric(horizontal: 18.0),
123 child: Container(
124 decoration: BoxDecoration(
125 color: Theme.of(context).colorScheme.surfaceContainer,
126 borderRadius: BorderRadius.circular(16),
127 ),
128 child: Column(
129 crossAxisAlignment: CrossAxisAlignment.start,
130 children: [
131 Padding(
132 padding: const EdgeInsets.all(12.0),
133 child: Column(
134 spacing: 8.0,
135 crossAxisAlignment: CrossAxisAlignment.start,
136 children: [
137 Text(S.of(context).card_style),
138 Container(
139 height: 63,
140 child: ListView.separated(
141 scrollDirection: Axis.horizontal,
142 itemCount: state.availableDesigns.length,
143 separatorBuilder: (context, index) {
144 return SizedBox(width: 8.0);
145 },
146 itemBuilder: (context, index) {
147 return GestureDetector(
148 onTap: () {
149 context
150 .read<CardCustomizerBloc>()
151 .add(CardDesignSelected(index));
152 },
153 child: AnimatedContainer(
154 duration: Duration(milliseconds: 300),
155 decoration: ShapeDecoration(
156 shape: RoundedSuperellipseBorder(
157 side: BorderSide(
158 color:
159 ((index == state.selectedDesignIndex)
160 ? Theme.of(context)
161 .colorScheme
162 .onSurface
163 : Colors.transparent),
164 width: 1),
165 borderRadius:
166 BorderRadiusGeometry.circular(12),
167 ),
168 ),
169 child: AnimatedScale(
170 duration: Duration(milliseconds: 200),
171 scale: index == state.selectedDesignIndex
172 ? 0.94
173 : 1,
174 child: BalanceCard(
175 width: 96,
176 borderRadius: 10,
177 selected: false,
178 designSwitchDuration:
179 Duration(milliseconds: 300),
180 design: _cardStylePreviewDesign(state, index),
181 ),
182 ),
183 ),
184 );
185 }),
186 ),
187 ],
188 )),
189 AnimatedSwitcher(
190 duration: const Duration(milliseconds: 350),
191 switchInCurve: Curves.easeOut,
192 switchOutCurve: Curves.easeIn,
193 transitionBuilder: (Widget child, Animation<double> animation) {
194 return FadeTransition(
195 opacity: animation,
196 child: SizeTransition(
197 sizeFactor: animation,
198 axisAlignment: -1,
199 child: child,
200 ),
201 );
202 },
203 child: _showIconStylePanel(state)
204 ? Column(
205 key: const ValueKey('icon_style_panel'),
206 mainAxisSize: MainAxisSize.min,
207 crossAxisAlignment: CrossAxisAlignment.start,
208 children: [
209 const SizedBox(height: 8),
210 Padding(
211 padding: const EdgeInsets.symmetric(horizontal: 12.0),
212 child: Column(
213 spacing: 8.0,
214 crossAxisAlignment: CrossAxisAlignment.start,
215 children: [
216 Text(S.of(context).icon_style),
217 SizedBox(
218 height: 48,
219 child: ListView.separated(
220 scrollDirection: Axis.horizontal,
221 itemCount: state.availableIconPaths.length,
222 separatorBuilder: (context, _) =>
223 const SizedBox(width: 8.0),
224 itemBuilder: (context, index) {
225 final icon = state.availableIconPaths[index];
226 final isSelected =
227 index == state.selectedIconIndex;
228 return GestureDetector(
229 onTap: () {
230 context
231 .read<CardCustomizerBloc>()
232 .add(IconStyleSelected(index));
233 },
234 child: AnimatedContainer(
235 duration:
236 const Duration(milliseconds: 200),
237 width: 48,
238 height: 48,
239 decoration: BoxDecoration(
240 borderRadius: BorderRadius.circular(18),
241 color: Theme.of(context).brightness ==
242 Brightness.light
243 ? Theme.of(context)
244 .colorScheme
245 .onSurfaceVariant
246 .withAlpha(64)
247 : Theme.of(context)
248 .colorScheme
249 .surfaceContainerHigh,
250 border: Border.all(
251 color: isSelected
252 ? Theme.of(context)
253 .colorScheme
254 .onSurface
255 : Colors.transparent,
256 width: 2,
257 ),
258 ),
259 child: Padding(
260 padding: const EdgeInsets.all(10.0),
261 child: CakeImageWidget(
262 imageUrl: icon.path),
263 ),
264 ),
265 );
266 },
267 ),
268 ),
269 ],
270 ),
271 ),
272 ],
273 )
274 : const SizedBox.shrink(key: ValueKey('icon_style_hidden')),
275 ),
276 SizedBox(height: 8),
277 Container(
278 decoration: BoxDecoration(
279 color: Theme.of(context).colorScheme.surfaceContainerHigh,
280 borderRadius: BorderRadius.circular(16)),
281 child: Padding(
282 padding: const EdgeInsets.all(12.0),
283 child: Column(
284 spacing: 8.0,
285 crossAxisAlignment: CrossAxisAlignment.start,
286 children: [
287 Text(S.of(context).color),
288 Container(
289 width: double.infinity,
290 child: Wrap(
291 direction: Axis.horizontal,
292 spacing: 4, // space between items in a row
293 runSpacing: 8,
294 children: List.generate(state.availableColors.length,
295 (index) {
296 return Material(
297 borderRadius: BorderRadius.circular(999999999),
298 child: InkWell(
299 borderRadius: BorderRadius.circular(999999999),
300 onTap: () {
301 context
302 .read<CardCustomizerBloc>()
303 .add(ColorSelected(index));
304 },
305 child: Stack(
306 children: [
307 AnimatedOpacity(
308 duration: Duration(milliseconds: 200),
309 opacity: index == state.selectedColorIndex
310 ? 1
311 : 0,
312 child: Container(
313 width: 32,
314 height: 32,
315 decoration: BoxDecoration(
316 borderRadius:
317 BorderRadius.circular(99999999),
318 border: Border.all(
319 color: Theme.of(context)
320 .colorScheme
321 .onSurface))),
322 ),
323 AnimatedScale(
324 duration: Duration(milliseconds: 200),
325 scale: index == state.selectedColorIndex
326 ? 0.8
327 : 1,
328 child: Container(
329 width: 32,
330 height: 32,
331 decoration: BoxDecoration(
332 borderRadius:
333 BorderRadius.circular(99999999),
334 gradient:
335 state.availableColors[index]),
336 ),
337 ),
338 ],
339 ),
340 ),
341 );
342 }),
343 )),
344 ],
345 ),
346 ),
347 )
348 ],
349 ),
350 )),
351 ],
352 ),
353 ),
354 ),
355 );
356 },
357 ),
358 );
359 }
360 }