CakeWallet/lib/entities/qr_scanner.dart
Omar Hatem a34cf24897
Remove haven (#2085)
* decred: Add decred. (#1322)

* multi: Add initial decred screens. (#1165)

Use a mock libwallet for now.

* cw_decred: add libdcrwallet dependency and link library for android, ios and macos (#1240)

* change cw_decred from package to plugin

* add libdcrwallet dependency and link library for android, ios and macos

* remove spvwallet, make some libdcrwallet fns async, light refactor

* libdcrwallet: use json payload returns

* use specific libwallet commit hash

* decred: fix Rename wallet.

---------

Co-authored-by: JoeGruff <joegruffins@gmail.com>

* decred: Add sync.

* decred: Add send transaction.

* decred: Fix fee estimation.

* decred: List transactions.

* decred: Add rescan.

* decred: Sign message.

* decred: Add new addr and addrs.

* decred: Add change wallet pass.

* decred: Add restore from seed.

* decred: Add watching only wallets.

* decred: Enable mainnet.

* decred: Allow using blank node address.

This allows a persistent peer to be unset, falling back to decred
seeders.

* decred: Rescan from wallet birthday.

* add and update macos build scripts, update build readme, gitignore macos project.pbxproj

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* multi: hide decred rescan page if it's not ready

-  move hasRescan method to WalletBase and implement for decred

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* cw_decred: fix bug where decred wallets are not loaded after app restart

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* add buy and sell for decred via onramp

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* bug-fix: account for other send outputs that are part of the same tx

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* decred: Return address with no peers.

* decred: Update pubspec.

* decred: Add verify message.

* upgrade hive_generator dep in cw_decred

* decred: Clean up code.

---------

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>
Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com>
Co-authored-by: Philemon Ukane <ukanephilemon@gmail.com>

* fix extracted addresses not used
fix conflicts with main

* remove print [skip ci]

* minor formatting

* fix initial migration version

* add build decred script to workflow

* install go before build decred
fix switch cases

* trial 2 to fix decred build

* re-install go

* revert build script change

* refactor/clean nodes functions

* Fix address book issue
Fix send ALL (to be continued with the fees point)

* Fix transactions display issues
Add missing file

* Fix unconfirmed balance not displayed
Change Wallet order
Minor cleanup

* Fix workflow

* Fix workflow

* Fix workflow

* test

* hardcode path for now

* fix + cleanup decred build script to work on mac and linux

* Update decred build script

* Run actions on pull requests, extract commit message

* run after checkout

* add safe directory

* Get commit message from base.sha instead of last commit

* base -> head

* Do not merge main branch into pr

* [skip slack] [run tests] clone by sha

* Proper name for decred library in the build script

* Throw an error when ANDROID_HOME or ANDROID_NDK_VERSION is missing

* Fix conflicts with main

* minor code enhancement

* decred: Add used address history.  (#1941)

* decred: Update pubspec.

* decred testnet

* decred: Add used address history.

* decred: Remove default node list.

* populate transaction history before sync begins

* decred: Add some awaits.

* decred: Fix send all.

* decred: Add clang export to build script.

* decred: Update logo colors.

* cleanup cw_decred.dart

* make decred wallet addresses selectable in receive page

* decred: Always set default addr when used.

* decred: Add back default node list.

* decred: Allow creating addresses manually.

---------

Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* minor fixes and cleanup

* minor fix, feel free to test now

* - Fix transaction details
- Fix Nodes
- Add processing sync status

* Add decred info card

* push missing file

* Add missing text for decred info card

* minor: change docs link [skip ci]

* decred: Update derivation info. (#2013)

* decred: Update derivation info.

* decred: Allow unsynced unused addresses.

* decred: Update dcrwallet dep to 4.3.0.

* Merge main and fix conflicts

* Merge main and fix conflicts

* decred: Fix background sync panic. (#2080)

* decred: Run libwallet in isolate. (#2077)

* decred: Fix contact save inquiry. (#2083)

Also fix tx time and the fee shown on pending transactions.

* Disable send button in view only decred wallets

* - Fix frozen coins
- Add URI support
- Fix fees in tx details
- Handle empty coins send
- Handle wallets in address book

* Merge main

* remove print [skip ci]

* Fix restore from QR

* minor improvement for QR restore

* Remove Haven Wallet

* Remove haven scripts

* minor fixes [skip ci]

* decred: Get slip44 addrs before sync completes. (#2092)

* - Fix loading wallet more than one time
- Fix minor UI issue

* fix merge issue

* fix merge issue

---------

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>
Co-authored-by: JoeGruffins <34998433+JoeGruffins@users.noreply.github.com>
Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com>
Co-authored-by: Philemon Ukane <ukanephilemon@gmail.com>
Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
2025-03-21 04:52:05 +02:00

373 lines
9.9 KiB
Dart

import 'dart:math';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:fast_scanner/fast_scanner.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
var isQrScannerShown = false;
Future<String?> presentQRScanner(BuildContext context) async {
isQrScannerShown = true;
try {
final result = await Navigator.of(context).push<String>(
MaterialPageRoute(
builder:(context) {
return BarcodeScannerSimple();
},
),
);
isQrScannerShown = false;
return result;
} catch (e) {
isQrScannerShown = false;
rethrow;
}
}
// https://github.com/MrCyjaneK/fast_scanner/blob/master/example/lib/barcode_scanner_simple.dart
class BarcodeScannerSimple extends StatefulWidget {
const BarcodeScannerSimple({super.key});
@override
State<BarcodeScannerSimple> createState() => _BarcodeScannerSimpleState();
}
class _BarcodeScannerSimpleState extends State<BarcodeScannerSimple> {
Barcode? _barcode;
bool popped = false;
List<String> urCodes = [];
late var ur = URQRToURQRData(urCodes);
void _handleBarcode(BarcodeCapture barcodes) {
try {
_handleBarcodeInternal(barcodes);
} catch (e) {
showPopUp<void>(
context: context,
builder: (context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: S.of(context).error_dialog_content,
buttonText: 'ok',
buttonAction: () {
Navigator.of(context).pop();
},
);
},
);
printV(e);
}
}
void _handleBarcodeInternal(BarcodeCapture barcodes) {
for (final barcode in barcodes.barcodes) {
// don't handle unknown QR codes
if (barcode.rawValue?.trim().isEmpty??false == false) continue;
if (barcode.rawValue!.startsWith("ur:")) {
if (urCodes.contains(barcode.rawValue)) continue;
setState(() {
urCodes.add(barcode.rawValue!);
ur = URQRToURQRData(urCodes);
});
if (ur.progress == 1) {
setState(() {
popped = true;
});
SchedulerBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pop(ur.inputs.join("\n"));
});
};
}
}
if (urCodes.isNotEmpty) return;
if (mounted) {
setState(() {
_barcode = barcodes.barcodes.firstOrNull;
});
if (_barcode != null && popped != true) {
setState(() {
popped = true;
});
Navigator.of(context).pop(_barcode!.rawValue ?? _barcode!.rawBytes);
}
}
}
final MobileScannerController ctrl = MobileScannerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Scan'),
actions: [
SwitchCameraButton(controller: ctrl),
ToggleFlashlightButton(controller: ctrl),
],
),
backgroundColor: Colors.black,
body: Stack(
children: [
MobileScanner(
onDetect: _handleBarcode,
controller: ctrl,
),
if (ur.inputs.length != 0)
Center(child:
Text(
"${ur.inputs.length}/${ur.count}",
style: Theme.of(context).textTheme.displayLarge?.copyWith(color: Colors.white)
),
),
SizedBox(
child: Center(
child: SizedBox(
width: 250,
height: 250,
child: CustomPaint(
painter: ProgressPainter(
urQrProgress: URQrProgress(
expectedPartCount: ur.count - 1,
processedPartsCount: ur.inputs.length,
receivedPartIndexes: _urParts(),
percentage: ur.progress,
),
),
),
),
),
),
],
),
);
}
List<int> _urParts() {
List<int> l = [];
for (var inp in ur.inputs) {
try {
l.add(int.parse(inp.split("/")[1].split("-")[0]));
} catch (e) {}
}
return l;
}
}
class ToggleFlashlightButton extends StatelessWidget {
const ToggleFlashlightButton({required this.controller, super.key});
final MobileScannerController controller;
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: controller,
builder: (context, state, child) {
if (!state.isInitialized || !state.isRunning) {
return const SizedBox.shrink();
}
switch (state.torchState) {
case TorchState.auto:
return IconButton(
iconSize: 32.0,
icon: const Icon(Icons.flash_auto),
onPressed: () async {
await controller.toggleTorch();
},
);
case TorchState.off:
return IconButton(
iconSize: 32.0,
icon: const Icon(Icons.flash_off),
onPressed: () async {
await controller.toggleTorch();
},
);
case TorchState.on:
return IconButton(
iconSize: 32.0,
icon: const Icon(Icons.flash_on),
onPressed: () async {
await controller.toggleTorch();
},
);
case TorchState.unavailable:
return const Icon(
Icons.no_flash,
color: Colors.grey,
);
}
},
);
}
}
class SwitchCameraButton extends StatelessWidget {
const SwitchCameraButton({required this.controller, super.key});
final MobileScannerController controller;
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: controller,
builder: (context, state, child) {
if (!state.isInitialized || !state.isRunning) {
return const SizedBox.shrink();
}
final int? availableCameras = state.availableCameras;
if (availableCameras != null && availableCameras < 2) {
return const SizedBox.shrink();
}
final Widget icon;
switch (state.cameraDirection) {
case CameraFacing.front:
icon = const Icon(Icons.camera_front);
case CameraFacing.back:
icon = const Icon(Icons.camera_rear);
}
return IconButton(
iconSize: 32.0,
icon: icon,
onPressed: () async {
await controller.switchCamera();
},
);
},
);
}
}
class URQRData {
URQRData(
{required this.tag,
required this.str,
required this.progress,
required this.count,
required this.error,
required this.inputs});
final String tag;
final String str;
final double progress;
final int count;
final String error;
final List<String> inputs;
Map<String, dynamic> toJson() {
return {
"tag": tag,
"str": str,
"progress": progress,
"count": count,
"error": error,
"inputs": inputs,
};
}
}
URQRData URQRToURQRData(List<String> urqr_) {
final urqr = urqr_.toSet().toList();
urqr.sort((s1, s2) {
final s1s = s1.split("/");
final s1frameStr = s1s[1].split("-");
final s1curFrame = int.parse(s1frameStr[0]);
final s2s = s2.split("/");
final s2frameStr = s2s[1].split("-");
final s2curFrame = int.parse(s2frameStr[0]);
return s1curFrame - s2curFrame;
});
String tag = '';
int count = 0;
String bw = '';
for (var elm in urqr) {
final s = elm.substring(elm.indexOf(":") + 1); // strip down ur: prefix
final s2 = s.split("/");
tag = s2[0];
final frameStr = s2[1].split("-");
// final curFrame = int.parse(frameStr[0]);
count = int.parse(frameStr[1]);
final byteWords = s2[2];
bw += byteWords;
}
String? error;
return URQRData(
tag: tag,
str: bw,
progress: count == 0 ? 0 : (urqr.length / count),
count: count,
error: error ?? "",
inputs: urqr,
);
}
class ProgressPainter extends CustomPainter {
final URQrProgress urQrProgress;
ProgressPainter({required this.urQrProgress});
@override
void paint(Canvas canvas, Size size) {
final c = Offset(size.width / 2.0, size.height / 2.0);
final radius = size.width * 0.9;
final rect = Rect.fromCenter(center: c, width: radius, height: radius);
const fullAngle = 360.0;
var startAngle = 0.0;
for (int i = 0; i < urQrProgress.expectedPartCount.toInt(); i++) {
var sweepAngle =
(1 / urQrProgress.expectedPartCount) * fullAngle * pi / 180.0;
drawSector(canvas, urQrProgress.receivedPartIndexes.contains(i), rect,
startAngle, sweepAngle);
startAngle += sweepAngle;
}
}
void drawSector(Canvas canvas, bool isActive, Rect rect, double startAngle,
double sweepAngle) {
final paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 8
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round
..color = isActive ? const Color(0xffff6600) : Colors.white70;
canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
}
@override
bool shouldRepaint(covariant ProgressPainter oldDelegate) {
return urQrProgress != oldDelegate.urQrProgress;
}
}
class URQrProgress {
int expectedPartCount;
int processedPartsCount;
List<int> receivedPartIndexes;
double percentage;
URQrProgress({
required this.expectedPartCount,
required this.processedPartsCount,
required this.receivedPartIndexes,
required this.percentage,
});
bool equals(URQrProgress? progress) {
if (progress == null) {
return false;
}
return processedPartsCount == progress.processedPartsCount;
}
}