dev
dart 271 lines 8.81 KB
Raw
1 import 'dart:async';
2
3 import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
4 import 'package:cake_wallet/src/screens/ur/widgets/qr_format_info_bottom_sheet.dart';
5 import 'package:cake_wallet/src/screens/ur/widgets/qr_selection_dialog.dart';
6 import 'package:cake_wallet/src/widgets/primary_button.dart';
7 import 'package:cake_wallet/utils/feature_flag.dart';
8 import 'package:cake_wallet/utils/show_pop_up.dart';
9 import 'package:cw_core/wallet_info.dart';
10 import 'package:cw_core/wallet_type.dart';
11 import 'package:flutter/material.dart';
12 import 'package:flutter/services.dart';
13 import 'package:flutter_svg/svg.dart';
14
15 class URQR extends StatefulWidget {
16 const URQR({super.key, required this.urqr, required this.walletType, this.hardwareWalletType});
17
18 final Map<String, String> urqr;
19 final WalletType walletType;
20 final HardwareWalletType? hardwareWalletType;
21
22 @override
23 _URQRState createState() => _URQRState();
24 }
25
26 const urFrameTime = 1000 ~/ 5;
27
28 class _URQRState extends State<URQR> {
29 Timer? _timer;
30 int frame = 0;
31 int selectedInt = 0;
32
33 @override
34 void initState() {
35 final keys = widget.urqr.keys.toList();
36 if (widget.hardwareWalletType == HardwareWalletType.coldcard) {
37 selectedInt = 1;
38 selected = keys[(selectedInt) % keys.length];
39 } else if (widget.hardwareWalletType == HardwareWalletType.seedsigner) {
40 selectedInt = 2;
41 selected = keys[(selectedInt) % keys.length];
42 }
43
44 _timer = Timer.periodic(const Duration(milliseconds: urFrameTime), (_) => _nextFrame());
45 super.initState();
46 }
47
48 @override
49 void dispose() {
50 _timer?.cancel();
51 super.dispose();
52 }
53
54 String get nextLabel => widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length];
55 void next() => setState(() {
56 final keys = widget.urqr.keys.toList();
57
58 selectedInt++;
59 selected = keys[(selectedInt) % keys.length];
60 });
61
62 late String selected = (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first;
63
64 List<String> get frames => widget.urqr[selected]?.split("\n") ?? [];
65 void _nextFrame() => setState(() => frame++);
66
67 @override
68 Widget build(BuildContext context) {
69 return Column(
70 mainAxisSize: MainAxisSize.min,
71 mainAxisAlignment: MainAxisAlignment.center,
72 children: [
73 Center(
74 child: Padding(
75 padding: const EdgeInsets.all(24.0),
76 child: Container(
77 child: QrImage(
78 data: frames[frame % frames.length],
79 version: -1,
80 size: MediaQuery.of(context).size.width * 0.7,
81 ),
82 ),
83 ),
84 ),
85 if (widget.urqr.values.length > 1)
86 widget.walletType == WalletType.monero ? _legacySwitch(context) : _newSwitch(context),
87 if (FeatureFlag.hasDevOptions) ...{
88 TextButton(
89 onPressed: () {
90 Clipboard.setData(ClipboardData(
91 text:
92 """Current frame (${frame % frames.length}): ${frames[frame % frames.length]},
93 All frames:
94 - ${frames.join("\n - ")}"""));
95 },
96 child: Text("[dev] copy debug info"),
97 ),
98 }
99 ],
100 );
101 }
102
103 Widget _legacySwitch(BuildContext context) => SizedBox(
104 width: double.maxFinite,
105 child: Padding(
106 padding: const EdgeInsets.symmetric(horizontal: 16.0),
107 child: SizedBox(
108 width: double.maxFinite,
109 child: PrimaryButton(
110 onPressed: next,
111 text: nextLabel,
112 color: Theme.of(context).colorScheme.primary,
113 textColor: Theme.of(context).colorScheme.onPrimary,
114 ),
115 ),
116 ),
117 );
118
119 Widget _newSwitch(BuildContext context) {
120 return Column(
121 children: [
122 const SizedBox(height: 32),
123 Row(
124 mainAxisAlignment: MainAxisAlignment.center,
125 children: [
126 Icon(Icons.qr_code, color: Theme.of(context).colorScheme.primary, size: 24),
127 const SizedBox(width: 4),
128 Text(
129 "QR Code Format",
130 style: TextStyle(
131 fontWeight: FontWeight.bold,
132 fontSize: 16,
133 color: Theme.of(context).colorScheme.onSurface,
134 ),
135 ),
136 ],
137 ),
138 const SizedBox(height: 16),
139 Container(
140 margin: const EdgeInsets.symmetric(horizontal: 16),
141 decoration: BoxDecoration(
142 color: Theme.of(context).colorScheme.surfaceContainer,
143 borderRadius: BorderRadius.circular(12),
144 ),
145 child: Material(
146 color: Colors.transparent,
147 child: InkWell(
148 borderRadius: BorderRadius.circular(12),
149 onTap: () => _showFormatSelectionDialog(context),
150 child: Padding(
151 padding: EdgeInsets.all(16),
152 child: Row(
153 children: [
154 if (_getCurrentFormatName().startsWith('Cupcake'))
155 Container(
156 width: 40,
157 height: 40,
158 decoration: BoxDecoration(
159 borderRadius: BorderRadius.circular(8),
160 ),
161 child: ClipRRect(
162 borderRadius: BorderRadius.circular(8),
163 child: SvgPicture.asset(
164 'assets/images/cupcake.svg',
165 width: 40,
166 height: 40,
167 fit: BoxFit.cover,
168 ),
169 ),
170 ),
171 const SizedBox(width: 16),
172 Expanded(
173 child: Column(
174 crossAxisAlignment: CrossAxisAlignment.start,
175 children: [
176 Text(
177 _getCurrentFormatName(),
178 style: TextStyle(
179 fontSize: 16,
180 fontWeight: FontWeight.w600,
181 color: Theme.of(context).colorScheme.onSurface,
182 ),
183 ),
184 Text(
185 _getCurrentFormatSubtitle(),
186 style: TextStyle(
187 fontSize: 14,
188 color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
189 ),
190 ),
191 ],
192 ),
193 ),
194 Icon(
195 Icons.chevron_right,
196 color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
197 size: 24,
198 ),
199 ],
200 ),
201 ),
202 ),
203 ),
204 ),
205 const SizedBox(height: 16),
206 GestureDetector(
207 onTap: () => _showInfoDialog(context),
208 child: Row(
209 mainAxisAlignment: MainAxisAlignment.center,
210 children: [
211 Text(
212 "What's this for?",
213 style: TextStyle(
214 fontSize: 14,
215 color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
216 ),
217 ),
218 const SizedBox(width: 4),
219 Icon(
220 Icons.info_outline,
221 size: 16,
222 color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
223 ),
224 ],
225 ),
226 ),
227 const SizedBox(height: 32),
228 ],
229 );
230 }
231
232 String _getCurrentFormatName() {
233 if (widget.urqr.isEmpty) return "Unknown";
234 final currentKey = widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
235 return currentKey.split(' ')[0];
236 }
237
238 String _getCurrentFormatSubtitle() {
239 if (widget.urqr.isEmpty) return "";
240 final currentKey = widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
241 return currentKey.substring(currentKey.indexOf(' ') + 1);
242 }
243
244 Future<void> _showFormatSelectionDialog(BuildContext context) async {
245 if (widget.urqr.length <= 1) return;
246
247 final keys = widget.urqr.keys.toList();
248
249 await showPopUp<void>(
250 context: context,
251 builder: (context) => QRFormatSelectionDialog(
252 formats: keys,
253 currentIndex: selectedInt,
254 onFormatSelected: (index) {
255 Navigator.pop(context);
256 setState(() {
257 selectedInt = index;
258 selected = keys[index];
259 });
260 },
261 ),
262 );
263 }
264
265 Future<void> _showInfoDialog(BuildContext context) => showModalBottomSheet(
266 context: context,
267 isScrollControlled: true,
268 backgroundColor: Colors.transparent,
269 builder: (_) => QRFormatInfoBottomSheet(),
270 );
271 }