1
-import 'package:barcode_scan2/barcode_scan2.dart';
1
+import 'dart:math';
2
+
3
+import 'package:cake_wallet/generated/i18n.dart';
4
+import 'package:cake_wallet/main.dart';
5
+import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
6
+import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
7
+import 'package:cake_wallet/utils/show_pop_up.dart';
8
+import 'package:fast_scanner/fast_scanner.dart';
9
+import 'package:flutter/material.dart';
10
+import 'package:flutter/scheduler.dart';
11
12
var isQrScannerShown = false;
13
5
-Future<String> presentQRScanner() async {
14
+Future<String> presentQRScanner(BuildContext context) async {
15
isQrScannerShown = true;
16
try {
8
- final result = await BarcodeScanner.scan();
17
+ final result = await Navigator.of(context).push<String>(
18
+ MaterialPageRoute(
19
+ builder:(context) {
20
+ return BarcodeScannerSimple();
21
+ },
22
+ ),
23
+ );
24
isQrScannerShown = false;
10
- return result.rawContent.trim();
25
+ return result??'';
26
} catch (e) {
27
isQrScannerShown = false;
28
rethrow;
29
}
30
}
31
+
32
+// https://github.com/MrCyjaneK/fast_scanner/blob/master/example/lib/barcode_scanner_simple.dart
33
+class BarcodeScannerSimple extends StatefulWidget {
34
+ const BarcodeScannerSimple({super.key});
35
+
36
+ @override
37
+ State<BarcodeScannerSimple> createState() => _BarcodeScannerSimpleState();
38
+}
39
+
40
+class _BarcodeScannerSimpleState extends State<BarcodeScannerSimple> {
41
+ Barcode? _barcode;
42
+ bool popped = false;
43
+
44
+ List<String> urCodes = [];
45
+ late var ur = URQRToURQRData(urCodes);
46
+
47
+ void _handleBarcode(BarcodeCapture barcodes) {
48
+ try {
49
+ _handleBarcodeInternal(barcodes);
50
+ } catch (e) {
51
+ showPopUp<void>(
52
+ context: context,
53
+ builder: (context) {
54
+ return AlertWithOneAction(
55
+ alertTitle: S.of(context).error,
56
+ alertContent: S.of(context).error_dialog_content,
57
+ buttonText: 'ok',
58
+ buttonAction: () {
59
+ Navigator.of(context).pop();
60
+ },
61
+ );
62
+ },
63
+ );
64
+ print(e);
65
+ }
66
+ }
67
+
68
+ void _handleBarcodeInternal(BarcodeCapture barcodes) {
69
+ for (final barcode in barcodes.barcodes) {
70
+ // don't handle unknown QR codes
71
+ if (barcode.rawValue?.trim().isEmpty??false == false) continue;
72
+ if (barcode.rawValue!.startsWith("ur:")) {
73
+ if (urCodes.contains(barcode.rawValue)) continue;
74
+ setState(() {
75
+ urCodes.add(barcode.rawValue!);
76
+ ur = URQRToURQRData(urCodes);
77
+ });
78
+ if (ur.progress == 1) {
79
+ setState(() {
80
+ popped = true;
81
+ });
82
+ SchedulerBinding.instance.addPostFrameCallback((_) {
83
+ Navigator.of(context).pop(ur.inputs.join("\n"));
84
+ });
85
+ };
86
+ }
87
+ }
88
+ if (urCodes.isNotEmpty) return;
89
+ if (mounted) {
90
+ setState(() {
91
+ _barcode = barcodes.barcodes.firstOrNull;
92
+ });
93
+ if (_barcode != null && popped != true) {
94
+ setState(() {
95
+ popped = true;
96
+ });
97
+ SchedulerBinding.instance.addPostFrameCallback((_) {
98
+ Navigator.of(context).pop(_barcode?.rawValue ?? "");
99
+ });
100
+ }
101
+ }
102
+ }
103
+
104
+ final MobileScannerController ctrl = MobileScannerController();
105
+
106
+ @override
107
+ Widget build(BuildContext context) {
108
+ return Scaffold(
109
+ appBar: AppBar(
110
+ title: const Text('Scan'),
111
+ actions: [
112
+ SwitchCameraButton(controller: ctrl),
113
+ ToggleFlashlightButton(controller: ctrl),
114
+ ],
115
+ ),
116
+ backgroundColor: Colors.black,
117
+ body: Stack(
118
+ children: [
119
+ MobileScanner(
120
+ onDetect: _handleBarcode,
121
+ controller: ctrl,
122
+ ),
123
+ if (ur.inputs.length != 0)
124
+ Center(child:
125
+ Text(
126
+ "${ur.inputs.length}/${ur.count}",
127
+ style: Theme.of(context).textTheme.displayLarge?.copyWith(color: Colors.white)
128
+ ),
129
+ ),
130
+ SizedBox(
131
+ child: Center(
132
+ child: SizedBox(
133
+ width: 250,
134
+ height: 250,
135
+ child: CustomPaint(
136
+ painter: ProgressPainter(
137
+ urQrProgress: URQrProgress(
138
+ expectedPartCount: ur.count - 1,
139
+ processedPartsCount: ur.inputs.length,
140
+ receivedPartIndexes: _urParts(),
141
+ percentage: ur.progress,
142
+ ),
143
+ ),
144
+ ),
145
+ ),
146
+ ),
147
+ ),
148
+ ],
149
+ ),
150
+ );
151
+ }
152
+
153
+ List<int> _urParts() {
154
+ List<int> l = [];
155
+ for (var inp in ur.inputs) {
156
+ try {
157
+ l.add(int.parse(inp.split("/")[1].split("-")[0]));
158
+ } catch (e) {}
159
+ }
160
+ return l;
161
+ }
162
+}
163
+
164
+
165
+class ToggleFlashlightButton extends StatelessWidget {
166
+ const ToggleFlashlightButton({required this.controller, super.key});
167
+
168
+ final MobileScannerController controller;
169
+
170
+ @override
171
+ Widget build(BuildContext context) {
172
+ return ValueListenableBuilder(
173
+ valueListenable: controller,
174
+ builder: (context, state, child) {
175
+ if (!state.isInitialized || !state.isRunning) {
176
+ return const SizedBox.shrink();
177
+ }
178
+
179
+ switch (state.torchState) {
180
+ case TorchState.auto:
181
+ return IconButton(
182
+ iconSize: 32.0,
183
+ icon: const Icon(Icons.flash_auto),
184
+ onPressed: () async {
185
+ await controller.toggleTorch();
186
+ },
187
+ );
188
+ case TorchState.off:
189
+ return IconButton(
190
+ iconSize: 32.0,
191
+ icon: const Icon(Icons.flash_off),
192
+ onPressed: () async {
193
+ await controller.toggleTorch();
194
+ },
195
+ );
196
+ case TorchState.on:
197
+ return IconButton(
198
+ iconSize: 32.0,
199
+ icon: const Icon(Icons.flash_on),
200
+ onPressed: () async {
201
+ await controller.toggleTorch();
202
+ },
203
+ );
204
+ case TorchState.unavailable:
205
+ return const Icon(
206
+ Icons.no_flash,
207
+ color: Colors.grey,
208
+ );
209
+ }
210
+ },
211
+ );
212
+ }
213
+}
214
+
215
+class SwitchCameraButton extends StatelessWidget {
216
+ const SwitchCameraButton({required this.controller, super.key});
217
+
218
+ final MobileScannerController controller;
219
+
220
+ @override
221
+ Widget build(BuildContext context) {
222
+ return ValueListenableBuilder(
223
+ valueListenable: controller,
224
+ builder: (context, state, child) {
225
+ if (!state.isInitialized || !state.isRunning) {
226
+ return const SizedBox.shrink();
227
+ }
228
+
229
+ final int? availableCameras = state.availableCameras;
230
+
231
+ if (availableCameras != null && availableCameras < 2) {
232
+ return const SizedBox.shrink();
233
+ }
234
+
235
+ final Widget icon;
236
+
237
+ switch (state.cameraDirection) {
238
+ case CameraFacing.front:
239
+ icon = const Icon(Icons.camera_front);
240
+ case CameraFacing.back:
241
+ icon = const Icon(Icons.camera_rear);
242
+ }
243
+
244
+ return IconButton(
245
+ iconSize: 32.0,
246
+ icon: icon,
247
+ onPressed: () async {
248
+ await controller.switchCamera();
249
+ },
250
+ );
251
+ },
252
+ );
253
+ }
254
+}
255
+
256
+class URQRData {
257
+ URQRData(
258
+ {required this.tag,
259
+ required this.str,
260
+ required this.progress,
261
+ required this.count,
262
+ required this.error,
263
+ required this.inputs});
264
+ final String tag;
265
+ final String str;
266
+ final double progress;
267
+ final int count;
268
+ final String error;
269
+ final List<String> inputs;
270
+ Map<String, dynamic> toJson() {
271
+ return {
272
+ "tag": tag,
273
+ "str": str,
274
+ "progress": progress,
275
+ "count": count,
276
+ "error": error,
277
+ "inputs": inputs,
278
+ };
279
+ }
280
+}
281
+
282
+URQRData URQRToURQRData(List<String> urqr_) {
283
+ final urqr = urqr_.toSet().toList();
284
+ urqr.sort((s1, s2) {
285
+ final s1s = s1.split("/");
286
+ final s1frameStr = s1s[1].split("-");
287
+ final s1curFrame = int.parse(s1frameStr[0]);
288
+ final s2s = s2.split("/");
289
+ final s2frameStr = s2s[1].split("-");
290
+ final s2curFrame = int.parse(s2frameStr[0]);
291
+ return s1curFrame - s2curFrame;
292
+ });
293
+
294
+ String tag = '';
295
+ int count = 0;
296
+ String bw = '';
297
+ for (var elm in urqr) {
298
+ final s = elm.substring(elm.indexOf(":") + 1); // strip down ur: prefix
299
+ final s2 = s.split("/");
300
+ tag = s2[0];
301
+ final frameStr = s2[1].split("-");
302
+ // final curFrame = int.parse(frameStr[0]);
303
+ count = int.parse(frameStr[1]);
304
+ final byteWords = s2[2];
305
+ bw += byteWords;
306
+ }
307
+ String? error;
308
+
309
+ return URQRData(
310
+ tag: tag,
311
+ str: bw,
312
+ progress: count == 0 ? 0 : (urqr.length / count),
313
+ count: count,
314
+ error: error ?? "",
315
+ inputs: urqr,
316
+ );
317
+}
318
+
319
+class ProgressPainter extends CustomPainter {
320
+ final URQrProgress urQrProgress;
321
+
322
+ ProgressPainter({required this.urQrProgress});
323
+
324
+ @override
325
+ void paint(Canvas canvas, Size size) {
326
+ final c = Offset(size.width / 2.0, size.height / 2.0);
327
+ final radius = size.width * 0.9;
328
+ final rect = Rect.fromCenter(center: c, width: radius, height: radius);
329
+ const fullAngle = 360.0;
330
+ var startAngle = 0.0;
331
+ for (int i = 0; i < urQrProgress.expectedPartCount.toInt(); i++) {
332
+ var sweepAngle =
333
+ (1 / urQrProgress.expectedPartCount) * fullAngle * pi / 180.0;
334
+ drawSector(canvas, urQrProgress.receivedPartIndexes.contains(i), rect,
335
+ startAngle, sweepAngle);
336
+ startAngle += sweepAngle;
337
+ }
338
+ }
339
+
340
+ void drawSector(Canvas canvas, bool isActive, Rect rect, double startAngle,
341
+ double sweepAngle) {
342
+ final paint = Paint()
343
+ ..style = PaintingStyle.stroke
344
+ ..strokeWidth = 8
345
+ ..strokeCap = StrokeCap.round
346
+ ..strokeJoin = StrokeJoin.round
347
+ ..color = isActive ? const Color(0xffff6600) : Colors.white70;
348
+ canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
349
+ }
350
+
351
+ @override
352
+ bool shouldRepaint(covariant ProgressPainter oldDelegate) {
353
+ return urQrProgress != oldDelegate.urQrProgress;
354
+ }
355
+}
356
+
357
+class URQrProgress {
358
+ int expectedPartCount;
359
+ int processedPartsCount;
360
+ List<int> receivedPartIndexes;
361
+ double percentage;
362
+
363
+ URQrProgress({
364
+ required this.expectedPartCount,
365
+ required this.processedPartsCount,
366
+ required this.receivedPartIndexes,
367
+ required this.percentage,
368
+ });
369
+
370
+ bool equals(URQrProgress? progress) {
371
+ if (progress == null) {
372
+ return false;
373
+ }
374
+ return processedPartsCount == progress.processedPartsCount;
375
+ }
376
+}