improved scan screen [cw-1507] (#3281)
* (wip) new scan screen * improved scan screen * fixes for ui * switch to modal sheet * add urqr (cupcake) progress indicator * fixes * Update lib/new-ui/pages/scan_page.dart --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
malik1004x committed
Jun 3, 2026 at 18:13 UTC
86ea17d5bfcee797751841e0be3352e544e8372a
17 files changed
+788
-26
lib/entities/new_ui_entities/list_item/list_item_regular_row.dart
+2
@@ -19,6 +19,7 @@ class ListItemRegularRow extends ListItem {
19
this.copyableText,
20
this.leadingIconErrorWidget,
21
this.leadingIconSize,
22
+ this.iconColor,
23
});
24
25
final String? subtitle;
@@ -35,4 +36,5 @@ class ListItemRegularRow extends ListItem {
36
final double? trailingIconSize;
37
final Widget? leadingIconErrorWidget;
38
final double? leadingIconSize;
39
+ final Color? iconColor;
40
}
lib/entities/qr_scanner.dart
+21
-8
@@ -1,26 +1,39 @@
1
import 'dart:math';
2
3
import 'package:cake_wallet/generated/i18n.dart';
4
+import 'package:cake_wallet/new-ui/pages/scan_page.dart';
5
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
6
import 'package:cake_wallet/utils/show_pop_up.dart';
7
import 'package:cw_core/utils/print_verbose.dart';
8
import 'package:fast_scanner/fast_scanner.dart';
9
+import 'package:flutter/cupertino.dart';
10
import 'package:flutter/material.dart';
11
import 'package:flutter/scheduler.dart';
12
+import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
13
import 'package:ur/ur_decoder.dart';
14
15
var isQrScannerShown = false;
16
14
-Future<String?> presentQRScanner(BuildContext context) async {
17
+Future<String?> presentQRScanner(BuildContext context, {bool showHelp = false, bool showManualInput = true, bool useModal = false}) async {
18
isQrScannerShown = true;
19
try {
17
- final result = await Navigator.of(context).push<String>(
18
- MaterialPageRoute(
19
- builder: (context) {
20
- return BarcodeScannerSimple();
21
- },
22
- ),
23
- );
20
+ final result = useModal
21
+ ? await CupertinoScaffold.showCupertinoModalBottomSheet<String?>(
22
+ context: context,
23
+ builder: (context) => ScanPage(
24
+ showHelp: showHelp,
25
+ showManualInput: showManualInput,
26
+ ))
27
+ : await Navigator.of(context).push<String>(
28
+ CupertinoPageRoute(
29
+ builder: (context) {
30
+ return ScanPage(
31
+ showHelp: showHelp,
32
+ showManualInput: showManualInput,
33
+ );
34
+ },
35
+ ),
36
+ );
37
isQrScannerShown = false;
38
return result;
39
} catch (e) {
lib/new-ui/pages/scan_page.dart
+587
-3
@@ -1,10 +1,594 @@
1
+import 'dart:math';
2
+import 'dart:ui';
3
+
4
+import 'package:cake_wallet/entities/qr_scanner.dart';
5
+import 'package:cake_wallet/generated/i18n.dart';
6
+import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
7
+import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
8
+import 'package:cake_wallet/new-ui/widgets/scan_page/network_list.dart';
9
+import 'package:cake_wallet/new-ui/widgets/send_page/floating_icon_button.dart';
10
+import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
11
+import 'package:cake_wallet/utils/show_pop_up.dart';
12
+import 'package:cw_core/utils/print_verbose.dart';
13
+import 'package:fast_scanner/fast_scanner.dart';
14
import 'package:flutter/material.dart';
15
+import 'package:flutter/scheduler.dart';
16
+import 'package:flutter/services.dart';
17
+import 'package:ur/ur_decoder.dart';
18
+
19
+class ScanPage extends StatefulWidget {
20
+ const ScanPage({super.key, this.showHelp = false, this.showManualInput = true});
21
+
22
+ final bool showHelp;
23
+ final bool showManualInput;
24
+
25
+ @override
26
+ State<ScanPage> createState() => _ScanPageState();
27
+}
28
+
29
+class _ScanPageState extends State<ScanPage> {
30
+ final MobileScannerController controller = MobileScannerController();
31
+ int? _numCameras;
32
+ bool _frontFlashMode = false;
33
+ bool _textInputMode = false;
34
+ final TextEditingController textController = TextEditingController();
35
+ final FocusNode textFocusNode = FocusNode();
36
+ List<String> urCodes = [];
37
+ late var ur = URQRToURQRData(urCodes);
38
+ final decoder = URDecoder();
39
+ bool popped = false;
40
+ Barcode? _barcode;
41
+
42
+ @override
43
+ void initState() {
44
+ super.initState();
45
+ controller.addListener(() => if (mounted) setState(() {
46
+ _numCameras = controller.value.availableCameras;
47
+ }));
48
+ }
49
+
50
+ @override
51
+ void dispose() {
52
+ controller.dispose();
53
+ textController.dispose();
54
+ textFocusNode.dispose();
55
+ super.dispose();
56
+ }
57
+
58
+ @override
59
+ Widget build(BuildContext context) {
60
+ final double cutoutSize = MediaQuery.of(context).size.width * 0.8;
61
+ const double cutoutRadius = 24.0;
62
+ const Duration textModeSwitchDuration = Duration(milliseconds: 300);
63
+ final buttonColor = _frontFlashMode ? Colors.black.withAlpha(40) : Colors.white.withAlpha(40);
64
+ final buttonIconColor = _frontFlashMode ? Colors.black : Colors.white;
65
+ final isScanningURQR = decoder.processedPartsCount() > 0;
66
+ final double targetRadius = isScanningURQR ? (cutoutSize / 2) : cutoutRadius;
67
+
68
+ return Material(
69
+ child: Stack(
70
+ children: [
71
+ MobileScanner(
72
+ controller: controller,
73
+ onDetect: _handleBarcode,
74
+ ),
75
+ Positioned.fill(
76
+ child: GestureDetector(
77
+ onTap: () => setState(() {
78
+ _textInputMode = false;
79
+ }),
80
+ child: RepaintBoundary(
81
+ child: AnimatedSwitcher(
82
+ switchInCurve: Curves.easeOutCubic,
83
+ switchOutCurve: Curves.easeInQuad,
84
+ duration: textModeSwitchDuration,
85
+ child: _textInputMode
86
+ ? BackdropFilter(
87
+ key: ValueKey(1),
88
+ filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
89
+ child: Container(
90
+ color: _frontFlashMode ? Colors.white : Colors.black.withAlpha(153),
91
+ ),
92
+ )
93
+ : TweenAnimationBuilder<double>(
94
+ key: const ValueKey(0),
95
+ tween: Tween<double>(begin: 0.0, end: targetRadius),
96
+ duration: const Duration(milliseconds: 500),
97
+ curve: Curves.easeOutCubic,
98
+ child: BackdropFilter(
99
+ filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
100
+ child: Container(
101
+ color: _frontFlashMode ? Colors.white : Colors.black.withAlpha(153),
102
+ ),
103
+ ),
104
+ builder: (context, radius, child) {
105
+ return ClipPath(
106
+ clipper: HoleClipper(
107
+ width: cutoutSize,
108
+ height: cutoutSize,
109
+ radius: radius,
110
+ ),
111
+ child: child,
112
+ );
113
+ },
114
+ ),
115
+ ),
116
+ ),
117
+ ),
118
+ ),
119
+ AnimatedOpacity(
120
+ opacity: _textInputMode || isScanningURQR ? 0 : 1,
121
+ duration: textModeSwitchDuration,
122
+ child: TweenAnimationBuilder<double>(
123
+ tween: Tween<double>(begin: 0.0, end: targetRadius),
124
+ duration: const Duration(milliseconds: 500),
125
+ curve: Curves.easeOutCubic,
126
+ builder: (context, radius, child) => Center(
127
+ child: Container(
128
+ width: cutoutSize,
129
+ height: cutoutSize,
130
+ decoration: BoxDecoration(
131
+ border: Border.all(color: Colors.white, width: 4.0),
132
+ borderRadius: BorderRadius.circular(radius),
133
+ ),
134
+ ),
135
+ ),
136
+ ),
137
+ ),
138
+ SafeArea(
139
+ child: Column(
140
+ children: [
141
+ ModalTopBar(
142
+ title: "",
143
+ trailingWidget: AnimatedOpacity(
144
+ duration: textModeSwitchDuration,
145
+ opacity: _textInputMode ? 0 : 1,
146
+ child: Row(
147
+ spacing: 8,
148
+ children: [
149
+ if ((_numCameras ?? 0) > 1)
150
+ ModernButton.svg(
151
+ size: 36,
152
+ iconSize: 24,
153
+ svgPath: "assets/new-ui/camera_flip.svg",
154
+ onPressed: () {
155
+ controller.switchCamera();
156
+ setState(() {
157
+ _frontFlashMode = false;
158
+ });
159
+ },
160
+ iconColor: buttonIconColor,
161
+ backgroundColor: buttonColor,
162
+ ),
163
+ ModernButton(
164
+ size: 36,
165
+ iconSize: 24,
166
+ icon: Icon(
167
+ (controller.value.torchState == TorchState.on || _frontFlashMode)
168
+ ? Icons.flash_off_outlined
169
+ : Icons.flash_on),
170
+ onPressed: () {
171
+ if (controller.value.cameraDirection == CameraFacing.front) {
172
+ setState(() {
173
+ _frontFlashMode = !_frontFlashMode;
174
+ });
175
+ } else {
176
+ controller.toggleTorch();
177
+ }
178
+ },
179
+ iconColor: buttonIconColor,
180
+ backgroundColor: buttonColor,
181
+ ),
182
+ ],
183
+ ),
184
+ ),
185
+ leadingWidget: Row(
186
+ textBaseline: TextBaseline.ideographic,
187
+ spacing: 24,
188
+ children: [
189
+ ModernButton(
190
+ size: 36,
191
+ iconSize: 18,
192
+ icon: Icon(Icons.arrow_back_ios_new),
193
+ onPressed: () {
194
+ if (_textInputMode) {
195
+ setState(() {
196
+ _textInputMode = false;
197
+ });
198
+ } else {
199
+ Navigator.of(context).pop();
200
+ }
201
+ },
202
+ iconColor: buttonIconColor,
203
+ backgroundColor: buttonColor,
204
+ ),
205
+ Text(
206
+ S.of(context).scan,
207
+ style: TextStyle(
208
+ fontSize: 18, fontWeight: FontWeight.w600, color: buttonIconColor),
209
+ )
210
+ ],
211
+ ),
212
+ )
213
+ ],
214
+ ),
215
+ ),
216
+ Positioned(
217
+ bottom: 18 +
218
+ max(MediaQuery.of(context).viewInsets.bottom,
219
+ MediaQuery.of(context).viewPadding.bottom),
220
+ left: 16,
221
+ right: 16,
222
+ child: RepaintBoundary(
223
+ child: AnimatedOpacity(
224
+ opacity: _textInputMode ? 1 : 0,
225
+ duration: textModeSwitchDuration,
226
+ child: Container(
227
+ decoration: BoxDecoration(
228
+ color: Theme.of(context).colorScheme.surfaceContainer,
229
+ borderRadius: BorderRadius.circular(18)),
230
+ child: Row(
231
+ spacing: 10,
232
+ children: [
233
+ Expanded(
234
+ child: TextField(
235
+ enabled: _textInputMode,
236
+ controller: textController,
237
+ focusNode: textFocusNode,
238
+ onSubmitted: (val) {
239
+ if (val.isNotEmpty) {
240
+ Navigator.of(context).pop(val);
241
+ } else {
242
+ setState(() {
243
+ _textInputMode = false;
244
+ });
245
+ }
246
+ },
247
+ decoration: InputDecoration(hintText: S.of(context).enter_code),
248
+ ),
249
+ ),
250
+ FloatingIconButton(
251
+ iconPath: "assets/new-ui/paste.svg",
252
+ onPressed: () async {
253
+ final data = await Clipboard.getData("text/plain");
254
+ if (data?.text != null) {
255
+ textController.text = data!.text!;
256
+ }
257
+ }),
258
+ SizedBox(
259
+ width: 2,
260
+ )
261
+ ],
262
+ ),
263
+ ),
264
+ ),
265
+ ),
266
+ ),
267
+ Positioned(
268
+ bottom: 120,
269
+ left: 0,
270
+ right: 0,
271
+ child: RepaintBoundary(
272
+ child: AnimatedOpacity(
273
+ duration: textModeSwitchDuration,
274
+ opacity: _textInputMode ? 0 : 1,
275
+ child: Row(
276
+ mainAxisAlignment: MainAxisAlignment.center,
277
+ spacing: 8,
278
+ children: [
279
+ // "not mvp"
280
+ // ScanPageButton(
281
+ // onTap: () async {
282
+ // FilePickerResult? res = await FilePicker.platform.pickFiles(
283
+ // type: FileType.image,
284
+ // allowMultiple: false,
285
+ // withData: false,
286
+ // );
287
+ //
288
+ // if (res != null && res.paths.isNotEmpty && res.paths.first != null) {
289
+ // final capture = await controller.analyzeImage(res.paths.first!);
290
+ // if (capture != null) {
291
+ // _handleBarcode(capture);
292
+ // }
293
+ // }
294
+ // },
295
+ // icon: Icons.photo_outlined,
296
+ // label: S.of(context).gallery,
297
+ // buttonColor: buttonColor,
298
+ // buttonIconColor: buttonIconColor),
299
+ if (widget.showManualInput)
300
+ ScanPageButton(
301
+ onTap: () {
302
+ setState(() {
303
+ _textInputMode = true;
304
+ });
305
+ Future.delayed(textModeSwitchDuration)
306
+ .then((val) => textFocusNode.requestFocus());
307
+ },
308
+ icon: Icons.edit_outlined,
309
+ label: S.of(context).manual_input,
310
+ buttonColor: buttonColor,
311
+ buttonIconColor: buttonIconColor),
312
+ if (widget.showHelp)
313
+ ScanPageButton(
314
+ onTap: () async {
315
+ if(_textInputMode) return;
316
+ try {
317
+ controller.stop();
318
+ await showModalBottomSheet(
319
+ context: context,
320
+ isScrollControlled: true,
321
+ useSafeArea: true,
322
+ backgroundColor: Theme.of(context).colorScheme.surface,
323
+ builder: (context) => ScanPageNetworkList());
324
+
325
+ } finally {
326
+ controller.start();
327
+ }
328
+
329
+ },
330
+ icon: Icons.question_mark,
331
+ buttonColor: buttonColor,
332
+ buttonIconColor: buttonIconColor)
333
+ ],
334
+ ),
335
+ ),
336
+ )),
337
+ AnimatedOpacity(
338
+ duration: Duration(milliseconds: 500),
339
+ opacity: isScanningURQR ? 1 : 0,
340
+ child: Center(
341
+ child: SegmentedCircularProgress(
342
+ progress: URQrProgress(
343
+ expectedPartCount: decoder.expectedPartCount() ?? 0,
344
+ processedPartsCount: decoder.processedPartsCount(),
345
+ receivedPartIndexes: decoder.receivedPartIndexes().toList(),
346
+ percentage: decoder.estimatedPercentComplete(),
347
+ ),
348
+ size: cutoutSize + 20,
349
+ activeColor: Theme.of(context).colorScheme.primary,
350
+ inactiveColor: Theme.of(context).colorScheme.onSurfaceVariant),
351
+ ),
352
+ ),
353
+ Positioned(
354
+ bottom: 120,
355
+ left: 0,
356
+ right: 0,
357
+ child: AnimatedOpacity(
358
+ duration: Duration(milliseconds: 500),
359
+ opacity: isScanningURQR ? 1 : 0,
360
+ child: Row(
361
+ mainAxisAlignment: MainAxisAlignment.center,
362
+ children: [
363
+ Text(
364
+ "${decoder.processedPartsCount()}",
365
+ style: TextStyle(
366
+ fontSize: 45,
367
+ fontWeight: FontWeight.w500,
368
+ color: Theme.of(context).colorScheme.primary),
369
+ ),
370
+ Text(
371
+ "/",
372
+ style: TextStyle(
373
+ fontSize: 45, color: Theme.of(context).colorScheme.onSurfaceVariant),
374
+ ),
375
+ Text(
376
+ "${decoder.expectedPartCount()}",
377
+ style: TextStyle(fontSize: 45, color: Theme.of(context).colorScheme.onSurface),
378
+ ),
379
+ ],
380
+ ),
381
+ ),
382
+ )
383
+ ],
384
+ ),
385
+ );
386
+ }
387
+
388
+ void _handleBarcode(BarcodeCapture barcodes) {
389
+ try {
390
+ _handleBarcodeInternal(barcodes);
391
+ } catch (e, st) {
392
+ showPopUp<void>(
393
+ context: context,
394
+ builder: (context) {
395
+ return AlertWithOneAction(
396
+ alertTitle: S.of(context).error,
397
+ alertContent: S.of(context).error_dialog_content,
398
+ buttonText: 'ok',
399
+ buttonAction: () {
400
+ Navigator.of(context).pop();
401
+ },
402
+ );
403
+ },
404
+ );
405
+ printV("$e\n$st");
406
+ }
407
+ }
408
+
409
+ void _handleBarcodeInternal(BarcodeCapture barcodes) {
410
+ for (final barcode in barcodes.barcodes) {
411
+ if (barcode.rawValue?.trim().isEmpty ?? false == false) continue;
412
+ if (barcode.rawValue!.startsWith("ur:")) {
413
+ if (urCodes.contains(barcode.rawValue)) continue;
414
+ decoder.receivePart(barcode.rawValue!);
415
+ setState(() {
416
+ urCodes.add(barcode.rawValue!);
417
+ ur = URQRToURQRData(urCodes);
418
+ });
419
+ if (decoder.estimatedPercentComplete() == 1) {
420
+ setState(() {
421
+ popped = true;
422
+ });
423
+ SchedulerBinding.instance.addPostFrameCallback((_) {
424
+ Navigator.of(context).pop(ur.inputs.join("\n"));
425
+ });
426
+ }
427
+ ;
428
+ }
429
+ }
430
+ if (urCodes.isNotEmpty) return;
431
+ if (mounted) {
432
+ setState(() {
433
+ _barcode = barcodes.barcodes.firstOrNull;
434
+ });
435
+ if (_barcode != null && popped != true) {
436
+ setState(() {
437
+ popped = true;
438
+ });
439
+ Navigator.of(context).pop(_barcode!.rawValue ?? _barcode!.rawBytes);
440
+ }
441
+ }
442
+ }
443
+}
444
+
445
+class ScanPageButton extends StatelessWidget {
446
+ const ScanPageButton(
447
+ {super.key,
448
+ required this.onTap,
449
+ required this.icon,
450
+ this.label,
451
+ required this.buttonColor,
452
+ required this.buttonIconColor});
453
+
454
+ final VoidCallback onTap;
455
+ final IconData icon;
456
+ final String? label;
457
+ final Color buttonColor;
458
+ final Color buttonIconColor;
459
+
460
+ @override
461
+ Widget build(BuildContext context) {
462
+ return GestureDetector(
463
+ onTap: onTap,
464
+ child: Container(
465
+ decoration: BoxDecoration(color: buttonColor, borderRadius: BorderRadius.circular(99999)),
466
+ child: Padding(
467
+ padding: EdgeInsets.only(
468
+ top: 10, bottom: 10, left: label == null ? 10 : 16, right: label == null ? 10 : 20),
469
+ child: Row(
470
+ spacing: 10,
471
+ children: [
472
+ Icon(icon, size: 28, color: buttonIconColor),
473
+ if (label != null)
474
+ Text(label!,
475
+ style: TextStyle(
476
+ fontSize: 16, fontWeight: FontWeight.w500, color: buttonIconColor))
477
+ ],
478
+ ),
479
+ ),
480
+ ));
481
+ }
482
+}
483
+
484
+class HoleClipper extends CustomClipper<Path> {
485
+ final double width;
486
+ final double height;
487
+ final double radius;
488
+
489
+ HoleClipper({
490
+ required this.width,
491
+ required this.height,
492
+ required this.radius,
493
+ });
494
3
-class ScanPage extends StatelessWidget {
4
- const ScanPage({super.key});
495
+ @override
496
+ Path getClip(Size size) {
497
+ final Path fullScreenPath = Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height));
498
+
499
+ final Path cutoutPath = Path()
500
+ ..addRRect(
501
+ RRect.fromRectAndRadius(
502
+ Rect.fromCenter(
503
+ center: Offset(size.width / 2, size.height / 2),
504
+ width: width,
505
+ height: height,
506
+ ),
507
+ Radius.circular(radius),
508
+ ),
509
+ );
510
+
511
+ return Path.combine(
512
+ PathOperation.difference,
513
+ fullScreenPath,
514
+ cutoutPath,
515
+ );
516
+ }
517
+
518
+ @override
519
+ bool shouldReclip(covariant HoleClipper oldClipper) {
520
+ return oldClipper.width != width || oldClipper.height != height || oldClipper.radius != radius;
521
+ }
522
+}
523
+
524
+class SegmentedCircularProgress extends StatelessWidget {
525
+ final URQrProgress progress;
526
+ final Color activeColor;
527
+ final Color inactiveColor;
528
+ final double size;
529
+
530
+ const SegmentedCircularProgress({
531
+ Key? key,
532
+ required this.progress,
533
+ required this.activeColor,
534
+ required this.inactiveColor,
535
+ required this.size,
536
+ }) : super(key: key);
537
538
@override
539
Widget build(BuildContext context) {
8
- return const Placeholder();
540
+ return CustomPaint(
541
+ size: Size(size, size),
542
+ painter: _SegmentedCirclePainter(
543
+ progress: progress,
544
+ activeColor: activeColor,
545
+ inactiveColor: inactiveColor,
546
+ ),
547
+ );
548
+ }
549
+}
550
+
551
+class _SegmentedCirclePainter extends CustomPainter {
552
+ final URQrProgress progress;
553
+ static const double strokeWidth = 10;
554
+ static const double gapAngle = 0.08;
555
+ final Color activeColor;
556
+ final Color inactiveColor;
557
+
558
+ _SegmentedCirclePainter({
559
+ required this.progress,
560
+ required this.activeColor,
561
+ required this.inactiveColor,
562
+ });
563
+
564
+ @override
565
+ void paint(Canvas canvas, Size size) {
566
+ final rect = Rect.fromLTWH(0, 0, size.width, size.height);
567
+ final totalSegments = progress.expectedPartCount;
568
+
569
+ final sweepAngle = (2 * pi - (gapAngle * totalSegments)) / totalSegments;
570
+
571
+ final paint = Paint()
572
+ ..style = PaintingStyle.stroke
573
+ ..strokeWidth = strokeWidth
574
+ ..strokeCap = StrokeCap.butt;
575
+
576
+ double startAngle = -pi / 2 + (gapAngle / 2);
577
+
578
+ for (int i = 0; i < totalSegments; i++) {
579
+ bool isHighlighted = progress.receivedPartIndexes.contains(i);
580
+
581
+ paint.color = isHighlighted ? activeColor : inactiveColor;
582
+
583
+ canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
584
+
585
+ startAngle += sweepAngle + gapAngle;
586
+ }
587
+ }
588
+
589
+ @override
590
+ bool shouldRepaint(covariant _SegmentedCirclePainter oldDelegate) {
591
+ return !oldDelegate.progress.equals(progress) ||
592
+ oldDelegate.progress.expectedPartCount != progress.expectedPartCount;
593
}
594
}
lib/new-ui/widgets/coins_page/action_row/coin_action_row.dart
+1
-12
@@ -157,17 +157,7 @@ class CoinActionRow extends StatelessWidget {
157
}
158
159
Future<void> _onPressedScan(BuildContext context) async {
160
- if (false && FeatureFlag.hasNewUiExtraPages) {
161
- showModalBottomSheet(
162
- context: context,
163
- isScrollControlled: true,
164
- builder: (context) => FractionallySizedBox(
165
- heightFactor: 0.9,
166
- child: ScanPage(),
167
- ),
168
- );
169
- } else {
170
- final code = await presentQRScanner(context);
160
+ final code = await presentQRScanner(context, showHelp: true);
161
162
if (code == null || code.isEmpty) return;
163
@@ -227,5 +217,4 @@ class CoinActionRow extends StatelessWidget {
217
),
218
);
219
}
230
- }
220
}
lib/new-ui/widgets/scan_page/network_list.dart
new
+105
@@ -0,0 +1,105 @@
1
+import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
2
+import 'package:cake_wallet/generated/i18n.dart';
3
+import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
4
+import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
5
+import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
6
+import 'package:cake_wallet/wallet_types.g.dart';
7
+import 'package:flutter/material.dart';
8
+import 'package:hive/hive.dart';
9
+import 'package:url_launcher/url_launcher.dart';
10
+
11
+class ScanPageNetworkList extends StatelessWidget {
12
+ const ScanPageNetworkList({super.key});
13
+
14
+ @override
15
+ Widget build(BuildContext context) {
16
+ return Container(
17
+ decoration: BoxDecoration(
18
+ borderRadius: BorderRadius.vertical(top: Radius.circular(18)),
19
+ color: Theme.of(context).colorScheme.surface),
20
+ child: Column(
21
+ children: [
22
+ ModalTopBar(
23
+ title: S.of(context).compatible_services,
24
+ leadingIcon: Icon(Icons.arrow_back_ios_new),
25
+ onLeadingPressed: Navigator.of(context).pop,
26
+ ),
27
+ Padding(
28
+ padding: const EdgeInsets.symmetric(horizontal: 24.0),
29
+ child: Column(
30
+ spacing: 32,
31
+ children: [
32
+ Column(
33
+ spacing: 24,
34
+ children: [
35
+ CakeImageWidget(
36
+ imageUrl: "assets/new-ui/scan_service.svg",
37
+ width: 75,
38
+ height: 75,
39
+ colorFilter:
40
+ ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
41
+ ),
42
+ Text(
43
+ S.of(context).compatible_services_desc,
44
+ style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
45
+ textAlign: TextAlign.center,
46
+ )
47
+ ],
48
+ ),
49
+ NewListSections(sections: {
50
+ "base": [
51
+ ListItemRegularRow(
52
+ keyValue: "addrs",
53
+ label: S.of(context).crypto_addresses,
54
+ showArrow: false,
55
+ iconPath: "assets/new-ui/navbar/wallets.svg",
56
+ iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
57
+ subtitle: "${availableWalletTypes.length} ${S.of(context).networks}"),
58
+ ListItemRegularRow(
59
+ keyValue: "invoices",
60
+ label: S.of(context).payment_invoices,
61
+ showArrow: false,
62
+ iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
63
+ iconPath: "assets/new-ui/payment_invoices.svg"),
64
+ ListItemRegularRow(
65
+ keyValue: "contacts",
66
+ label: S.of(context).contacts,
67
+ showArrow: false,
68
+ iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
69
+ iconPath: "assets/new-ui/navbar/contacts.svg"),
70
+ ListItemRegularRow(
71
+ keyValue: "nodes",
72
+ label: S.of(context).nodes,
73
+ showArrow: false,
74
+ iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
75
+ iconPath: "assets/new-ui/node_minimal.svg"),
76
+ ],
77
+ "extra": [
78
+ ListItemRegularRow(
79
+ iconPath: "assets/new-ui/wc_min.svg",
80
+ keyValue: "wc",
81
+ label: "WalletConnect",
82
+ subtitle: S.of(context).wc_desc,
83
+ trailingIconPath: "assets/new-ui/external_link.svg",
84
+ onTap: () {
85
+ launchUrl(Uri.https("walletconnect.com"));
86
+ }),
87
+ ListItemRegularRow(
88
+ iconPath: "assets/new-ui/ocp_min.svg",
89
+ keyValue: "ocp",
90
+ label: "OpenCryptoPay",
91
+ subtitle: S.of(context).ocp_desc,
92
+ trailingIconPath: "assets/new-ui/external_link.svg",
93
+ onTap: () {
94
+ launchUrl(Uri.https("opencryptopay.io"));
95
+ })
96
+ ]
97
+ })
98
+ ],
99
+ ),
100
+ )
101
+ ],
102
+ ),
103
+ );
104
+ }
105
+}
lib/src/screens/ur/animated_ur_page.dart
+3
-3
@@ -87,7 +87,7 @@ class AnimatedURPage extends BasePage {
87
try {
88
switch (urQrType) {
89
case "ur:xmr-txunsigned": // ur:xmr-txsigned
90
- final ur = await presentQRScanner(context);
90
+ final ur = await presentQRScanner(context, showManualInput: false);
91
if (ur == null) return;
92
final result =
93
await monero!.commitTransactionUR(animatedURmodel.wallet, ur);
@@ -96,7 +96,7 @@ class AnimatedURPage extends BasePage {
96
}
97
break;
98
case "ur:xmr-output": // xmr-keyimage
99
- final ur = await presentQRScanner(context);
99
+ final ur = await presentQRScanner(context, showManualInput: false);
100
if (ur == null) return;
101
final result =
102
await monero!.importKeyImagesUR(animatedURmodel.wallet, ur);
@@ -105,7 +105,7 @@ class AnimatedURPage extends BasePage {
105
}
106
break;
107
case "ur:psbt": // psbt
108
- final ur = await presentQRScanner(context);
108
+ final ur = await presentQRScanner(context, showManualInput: false);
109
if (ur == null) return;
110
await bitcoin!
111
.commitPsbtUR(animatedURmodel.wallet, ur.trim().split("\n"));
lib/src/widgets/new_list_row/list_item_regular_row_widget.dart
+5
@@ -26,6 +26,7 @@ class ListItemRegularRowWidget extends StatelessWidget {
26
this.copyableText,
27
this.leadingIconErrorWidget,
28
this.leadingIconSize,
29
+ this.iconColor
30
});
31
32
final String keyValue;
@@ -46,6 +47,7 @@ class ListItemRegularRowWidget extends StatelessWidget {
47
final String? copyableText;
48
final Widget? leadingIconErrorWidget;
49
final double? leadingIconSize;
50
+ final Color? iconColor;
51
52
@override
53
Widget build(BuildContext context) {
@@ -85,6 +87,9 @@ class ListItemRegularRowWidget extends StatelessWidget {
87
width: leadingIconSize ?? 24,
88
height: leadingIconSize ?? 24,
89
errorWidget: leadingIconErrorWidget,
90
+ colorFilter: iconColor == null
91
+ ? null
92
+ : ColorFilter.mode(iconColor!, BlendMode.srcIn),
93
)),
94
Flexible(
95
child: Column(
lib/src/widgets/new_list_row/new_list_section.dart
+1
@@ -109,6 +109,7 @@ class NewListSections extends StatelessWidget {
109
bottomWidget: item.bottomWidget,
110
leadingIconErrorWidget: item.leadingIconErrorWidget,
111
leadingIconSize: item.leadingIconSize,
112
+ iconColor: item.iconColor,
113
);
114
}
115
res/pictures/camera_flash.svg
new
+3
@@ -0,0 +1,3 @@
1
+<svg width="12" height="20" viewBox="0 0 12 20" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M3 20V12H0V0H10L8 7H12L3 20Z" fill="#E3E3E3"/>
3
+</svg>
res/pictures/camera_flip.svg
new
+10
@@ -0,0 +1,10 @@
1
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <g clip-path="url(#clip0_20409_705448)">
3
+ <path d="M8 17C7.45 17 6.97917 16.8042 6.5875 16.4125C6.19583 16.0208 6 15.55 6 15V9C6 8.45 6.19583 7.97917 6.5875 7.5875C6.97917 7.19583 7.45 7 8 7H9L10 6H14L15 7H16C16.55 7 17.0208 7.19583 17.4125 7.5875C17.8042 7.97917 18 8.45 18 9V15C18 15.55 17.8042 16.0208 17.4125 16.4125C17.0208 16.8042 16.55 17 16 17H8ZM12 14C12.55 14 13.0208 13.8042 13.4125 13.4125C13.8042 13.0208 14 12.55 14 12C14 11.45 13.8042 10.9792 13.4125 10.5875C13.0208 10.1958 12.55 10 12 10C11.45 10 10.9792 10.1958 10.5875 10.5875C10.1958 10.9792 10 11.45 10 12C10 12.55 10.1958 13.0208 10.5875 13.4125C10.9792 13.8042 11.45 14 12 14ZM8.55 0.5C9.11667 0.316667 9.6875 0.1875 10.2625 0.1125C10.8375 0.0375 11.4167 0 12 0C13.5667 0 15.0458 0.279167 16.4375 0.8375C17.8292 1.39583 19.0625 2.17083 20.1375 3.1625C21.2125 4.15417 22.0917 5.32083 22.775 6.6625C23.4583 8.00417 23.8667 9.45 24 11H22C21.8833 9.8 21.5667 8.67917 21.05 7.6375C20.5333 6.59583 19.8708 5.675 19.0625 4.875C18.2542 4.075 17.325 3.42917 16.275 2.9375C15.225 2.44583 14.1 2.15 12.9 2.05L14.45 3.6L13.05 5L8.55 0.5ZM15.45 23.5C14.8833 23.6833 14.3125 23.8125 13.7375 23.8875C13.1625 23.9625 12.5833 24 12 24C10.4333 24 8.95417 23.7208 7.5625 23.1625C6.17083 22.6042 4.9375 21.8292 3.8625 20.8375C2.7875 19.8458 1.90833 18.6792 1.225 17.3375C0.541667 15.9958 0.133333 14.55 0 13H2C2.13333 14.2 2.45417 15.3208 2.9625 16.3625C3.47083 17.4042 4.12917 18.325 4.9375 19.125C5.74583 19.925 6.675 20.5708 7.725 21.0625C8.775 21.5542 9.9 21.85 11.1 21.95L9.55 20.4L10.95 19L15.45 23.5Z" fill="#E3E3E3"/>
4
+ </g>
5
+ <defs>
6
+ <clipPath id="clip0_20409_705448">
7
+ <rect width="24" height="24" fill="white"/>
8
+ </clipPath>
9
+ </defs>
10
+</svg>
res/pictures/external_link.svg
new
+10
@@ -0,0 +1,10 @@
1
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <g clip-path="url(#clip0_20667_718695)">
3
+ <path d="M5.20517 14.2083C4.86839 14.2083 4.58333 14.0917 4.35 13.8583C4.11667 13.625 4 13.3399 4 13.0032V4.08017C4 3.74339 4.11667 3.45833 4.35 3.225C4.58333 2.99167 4.86839 2.875 5.20517 2.875H9.41017V3.875H5.20517C5.15383 3.875 5.10683 3.89639 5.06417 3.93917C5.02139 3.98183 5 4.02883 5 4.08017V13.0032C5 13.0545 5.02139 13.1015 5.06417 13.1442C5.10683 13.1869 5.15383 13.2083 5.20517 13.2083H14.1282C14.1795 13.2083 14.2265 13.1869 14.2692 13.1442C14.3119 13.1015 14.3333 13.0545 14.3333 13.0032V8.79817H15.3333V13.0032C15.3333 13.3399 15.2167 13.625 14.9833 13.8583C14.75 14.0917 14.4649 14.2083 14.1282 14.2083H5.20517ZM8.14617 10.7647L7.44367 10.0622L13.6308 3.875H11V2.875H15.3333V7.20833H14.3333V4.5775L8.14617 10.7647Z" fill="white"/>
4
+ </g>
5
+ <defs>
6
+ <clipPath id="clip0_20667_718695">
7
+ <rect width="16" height="16" fill="white"/>
8
+ </clipPath>
9
+ </defs>
10
+</svg>
res/pictures/node_minimal.svg
new
+3
@@ -0,0 +1,3 @@
1
+<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M0.876 18.124C0.292 17.54 0 16.8384 0 16.0193C0 15.2001 0.292 14.4985 0.876 13.9145C1.46 13.3305 2.16158 13.0385 2.98075 13.0385C3.27442 13.0385 3.55967 13.0824 3.8365 13.1702C4.1135 13.2581 4.36733 13.3815 4.598 13.5405L8.75 9.3885V5.8595C8.1065 5.69417 7.57383 5.34742 7.152 4.81925C6.73017 4.29108 6.51925 3.67825 6.51925 2.98075C6.51925 2.16158 6.81125 1.46 7.39525 0.875999C7.97925 0.291999 8.68083 0 9.5 0C10.3192 0 11.0208 0.291999 11.6048 0.875999C12.1888 1.46 12.4808 2.16158 12.4808 2.98075C12.4808 3.67825 12.2714 4.29108 11.8527 4.81925C11.4342 5.34742 10.9 5.69417 10.25 5.8595V9.3885L14.4173 13.5405C14.6481 13.3815 14.8994 13.2581 15.1712 13.1702C15.4429 13.0824 15.7256 13.0385 16.0193 13.0385C16.8384 13.0385 17.54 13.3305 18.124 13.9145C18.708 14.4985 19 15.2001 19 16.0193C19 16.8384 18.708 17.54 18.124 18.124C17.54 18.708 16.8384 19 16.0193 19C15.2001 19 14.4985 18.708 13.9145 18.124C13.3305 17.54 13.0385 16.8384 13.0385 16.0193C13.0385 15.7706 13.068 15.5286 13.127 15.2933C13.186 15.0581 13.2725 14.8379 13.3865 14.6328L9.5 10.7463L5.6135 14.6328C5.7275 14.8379 5.814 15.0581 5.873 15.2933C5.932 15.5286 5.9615 15.7706 5.9615 16.0193C5.9615 16.8384 5.6695 17.54 5.0855 18.124C4.5015 18.708 3.79992 19 2.98075 19C2.16158 19 1.46 18.708 0.876 18.124ZM17.0673 17.0673C17.3558 16.7787 17.5 16.4294 17.5 16.0193C17.5 15.6089 17.3558 15.2596 17.0673 14.9712C16.7787 14.6827 16.4294 14.5385 16.0193 14.5385C15.6089 14.5385 15.2596 14.6827 14.9712 14.9712C14.6827 15.2596 14.5385 15.6089 14.5385 16.0193C14.5385 16.4294 14.6827 16.7787 14.9712 17.0673C15.2596 17.3558 15.6089 17.5 16.0193 17.5C16.4294 17.5 16.7787 17.3558 17.0673 17.0673ZM10.548 4.02875C10.8365 3.74042 10.9808 3.39108 10.9808 2.98075C10.9808 2.57058 10.8365 2.22125 10.548 1.93275C10.2597 1.64425 9.91033 1.5 9.5 1.5C9.08967 1.5 8.74033 1.64425 8.452 1.93275C8.1635 2.22125 8.01925 2.57058 8.01925 2.98075C8.01925 3.39108 8.1635 3.74042 8.452 4.02875C8.74033 4.31725 9.08967 4.4615 9.5 4.4615C9.91033 4.4615 10.2597 4.31725 10.548 4.02875ZM4.02875 17.0673C4.31725 16.7787 4.4615 16.4294 4.4615 16.0193C4.4615 15.6089 4.31725 15.2596 4.02875 14.9712C3.74042 14.6827 3.39108 14.5385 2.98075 14.5385C2.57058 14.5385 2.22125 14.6827 1.93275 14.9712C1.64425 15.2596 1.5 15.6089 1.5 16.0193C1.5 16.4294 1.64425 16.7787 1.93275 17.0673C2.22125 17.3558 2.57058 17.5 2.98075 17.5C3.39108 17.5 3.74042 17.3558 4.02875 17.0673Z" fill="white"/>
3
+</svg>
res/pictures/ocp_min.svg
new
+5
@@ -0,0 +1,5 @@
1
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="24" height="24" rx="4.8" fill="#2231EF"/>
3
+ <path d="M15.5209 17.6564L20.678 12.4993C20.8111 12.3661 20.8111 12.15 20.678 12.0149L14.5135 5.85229L12.2109 8.15482L16.0729 12.0168C16.2061 12.15 16.2061 12.3661 16.0729 12.5012L13.2203 15.3538L15.5229 17.6564H15.5209Z" fill="white"/>
4
+ <path d="M14.0849 19.099L7.48811 12.5022C7.35494 12.369 7.35494 12.1528 7.48811 12.0177L14.0849 5.4209H9.62079C9.53008 5.4209 9.44323 5.45757 9.37954 5.52126L2.88113 12.0177C2.74796 12.1509 2.74796 12.3671 2.88113 12.5022L9.37761 18.9987C9.4413 19.0623 9.52815 19.099 9.61886 19.099H14.083H14.0849Z" fill="white"/>
5
+</svg>
res/pictures/payment_invoices.svg
new
+3
@@ -0,0 +1,3 @@
1
+<svg width="17" height="19" viewBox="0 0 17 19" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M2.5 18.8078C1.80133 18.8078 1.21 18.5658 0.726 18.0818C0.242 17.5978 0 17.0064 0 16.3078V13.8077H3V0L4.38475 1.1925L5.7885 0L7.19225 1.1925L8.59625 0L10 1.1925L11.4038 0L12.8077 1.1925L14.2115 0L15.6152 1.1925L17 0V16.3078C17 17.0064 16.758 17.5978 16.274 18.0818C15.79 18.5658 15.1987 18.8078 14.5 18.8078H2.5ZM14.5 17.3078C14.7833 17.3078 15.0208 17.2119 15.2125 17.0203C15.4042 16.8286 15.5 16.5911 15.5 16.3078V2.30775H4.5V13.8077H13.5V16.3078C13.5 16.5911 13.5958 16.8286 13.7875 17.0203C13.9792 17.2119 14.2167 17.3078 14.5 17.3078ZM5.69225 6.05775V4.55775H11.3652V6.05775H5.69225ZM5.69225 9.05775V7.55775H11.3652V9.05775H5.69225ZM13.4423 6.19225C13.1974 6.19225 12.9888 6.10608 12.8163 5.93375C12.6439 5.76125 12.5577 5.55258 12.5577 5.30775C12.5577 5.06292 12.6439 4.85425 12.8163 4.68175C12.9888 4.50942 13.1974 4.42325 13.4423 4.42325C13.6871 4.42325 13.8958 4.50942 14.0682 4.68175C14.2407 4.85425 14.327 5.06292 14.327 5.30775C14.327 5.55258 14.2407 5.76125 14.0682 5.93375C13.8958 6.10608 13.6871 6.19225 13.4423 6.19225ZM13.4423 9.19225C13.1974 9.19225 12.9888 9.10608 12.8163 8.93375C12.6439 8.76125 12.5577 8.55258 12.5577 8.30775C12.5577 8.06292 12.6439 7.85425 12.8163 7.68175C12.9888 7.50942 13.1974 7.42325 13.4423 7.42325C13.6871 7.42325 13.8958 7.50942 14.0682 7.68175C14.2407 7.85425 14.327 8.06292 14.327 8.30775C14.327 8.55258 14.2407 8.76125 14.0682 8.93375C13.8958 9.10608 13.6871 9.19225 13.4423 9.19225ZM2.5 17.3078H12V15.3077H1.5V16.3078C1.5 16.5911 1.59583 16.8286 1.7875 17.0203C1.97917 17.2119 2.21667 17.3078 2.5 17.3078Z" fill="white"/>
3
+</svg>
res/pictures/scan_service.svg
new
+14
@@ -0,0 +1,14 @@
1
+<svg width="75" height="75" viewBox="0 0 75 75" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <g clip-path="url(#clip0_20667_718582)">
3
+ <path d="M15.625 37.5H59.375" stroke="white" stroke-width="6.25" stroke-linecap="round" stroke-linejoin="round"/>
4
+ <path d="M9.375 21.875V15.625C9.375 13.9674 10.0335 12.3777 11.2056 11.2056C12.3777 10.0335 13.9674 9.375 15.625 9.375H21.875" stroke="white" stroke-width="6.25" stroke-linecap="round" stroke-linejoin="round"/>
5
+ <path d="M9.375 53.125V59.375C9.375 61.0326 10.0335 62.6223 11.2056 63.7944C12.3777 64.9665 13.9674 65.625 15.625 65.625H21.875" stroke="white" stroke-width="6.25" stroke-linecap="round" stroke-linejoin="round"/>
6
+ <path d="M53.125 9.375H59.375C61.0326 9.375 62.6223 10.0335 63.7944 11.2056C64.9665 12.3777 65.625 13.9674 65.625 15.625V21.875" stroke="white" stroke-width="6.25" stroke-linecap="round" stroke-linejoin="round"/>
7
+ <path d="M53.125 65.625H59.375C61.0326 65.625 62.6223 64.9665 63.7944 63.7944C64.9665 62.6223 65.625 61.0326 65.625 59.375V53.125" stroke="white" stroke-width="6.25" stroke-linecap="round" stroke-linejoin="round"/>
8
+ </g>
9
+ <defs>
10
+ <clipPath id="clip0_20667_718582">
11
+ <rect width="75" height="75" fill="white"/>
12
+ </clipPath>
13
+ </defs>
14
+</svg>
res/pictures/wc_min.svg
new
+5
@@ -0,0 +1,5 @@
1
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="24" height="24" rx="4.8" fill="#3396FF"/>
3
+ <path d="M15.9495 10.9809L17.4329 9.50916C14.0804 6.18314 9.91496 6.18314 6.5625 9.50916L8.04587 10.9809C10.5954 8.45117 13.4016 8.45117 15.9515 10.9809H15.9495Z" fill="white"/>
4
+ <path d="M15.4568 14.4112L11.9979 10.979L8.53847 14.4112L5.07908 10.979L3.59766 12.4488L8.53847 17.353L11.9979 13.9209L15.4568 17.353L20.3977 12.4492L18.9162 10.9794L15.4568 14.4112Z" fill="white"/>
5
+</svg>
res/values/strings_en.arb
+10
@@ -217,6 +217,8 @@
217
"color_theme": "Color theme",
218
"coming_soon_tag": "Coming Soon",
219
"commit_transaction_amount_fee": "Commit transaction\nAmount: ${amount}\nFee: ${fee}",
220
+ "compatible_services": "Compatible Services",
221
+ "compatible_services_desc": "Use the Scan tool to read the following types of QR codes supported on Cake Wallet:",
222
"configure": "Configure",
223
"confirm": "Confirm",
224
"confirm_delete_template": "This action will delete this template. Do you wish to continue?",
@@ -280,6 +282,7 @@
282
"creating_new_wallet": "Creating new wallet",
283
"creating_new_wallet_error": "Error: ${description}",
284
"creation_date": "Creation Date",
285
+ "crypto_addresses": "Crypto Addresses",
286
"csv_nothing_to_export": "Nothing to export",
287
"current_swap": "Current Swap",
288
"current_wallet": "Current wallet",
@@ -496,6 +499,7 @@
499
"frozen": "Frozen",
500
"frozen_balance": "Frozen Balance",
501
"full_balance": "Full Balance",
502
+ "gallery": "Gallery",
503
"gas_exceeds_allowance": "Gas required by transaction exceeds allowance.",
504
"gas_price": "Gas price",
505
"general": "General",
@@ -547,6 +551,7 @@
551
"infobox_auto_address": "Your receive address will rotate every time you receive a transaction",
552
"infobox_multichain": "Use this addess to receive any token or collectible on",
553
"infobox_rotation": "Your receive address will rotate every time you close and reopen this screen",
554
+ "input": "Input",
555
"inputs": "Inputs",
556
"insufficient_funds_for_tx": "Insufficient funds to successfully execute transaction.",
557
"insufficient_lamport_for_tx": "You do not have enough SOL to cover the transaction and its transaction fee. Kindly add more SOL to your wallet or reduce the SOL amount you\\'re sending.",
@@ -612,6 +617,7 @@
617
"manage_pow_nodes": "Manage PoW nodes",
618
"manage_providers": "Manage providers",
619
"manage_yats": "Manage Yats",
620
+ "manual_input": "Manual Input",
621
"mark_as_redeemed": "Mark As Redeemed",
622
"market_place": "Marketplace",
623
"mask": "Mask",
@@ -657,6 +663,7 @@
663
"nanogpt_subtitle": "All the newest models (GPT-5.3, Claude 4.6).\\nNo subscription, pay with crypto.",
664
"narrow": "Narrow",
665
"network": "Network",
666
+ "networks": "Networks",
667
"new_first_wallet_text": "Keeping your crypto safe is a piece of cake",
668
"new_node_testing": "New node testing",
669
"new_subaddress_create": "Create",
@@ -698,6 +705,7 @@
705
"note_tap_to_change": "Note (tap to change)",
706
"notification_permission_denied": "Notification permission got permamently denied, please manually enable it in settings",
707
"nullURIError": "URI is null",
708
+ "ocp_desc": "Pay with crypto in stores",
709
"offer_expires_in": "Offer expires in: ",
710
"offline": "Offline",
711
"ok": "OK",
@@ -746,6 +754,7 @@
754
"payjoin_unavailable_sheet_content": "Receiving a Payjoin transaction requires you to have Bitcoin ready to use.\n\nAs soon as your wallet has funds, Payjoin will be automatically enabled.",
755
"payjoin_unavailable_sheet_title": "Why is Payjoin unavailable?",
756
"payment_id": "Payment ID: ",
757
+ "payment_invoices": "Payment Invoices",
758
"payment_made_easy": "Payments made easy",
759
"payment_was_received": "Your payment was received.",
760
"payments": "Payments",
@@ -1372,6 +1381,7 @@
1381
"wc_approve_request_title": "Approve request",
1382
"wc_connect_request_title": "Connect request",
1383
"wc_connected_to": "Connected to ${name}",
1384
+ "wc_desc": "Easily connect to DApps",
1385
"wc_max_network_fee": "Max network fee",
1386
"wc_message_to_sign": "Message to sign",
1387
"wc_network_fee": "Network fee",