Improve qr code scanner ux
This commit is contained in:
@@ -57,6 +57,12 @@
|
|||||||
"shortBtnTxt": "New Profile",
|
"shortBtnTxt": "New Profile",
|
||||||
"fromClipboard": "Add From Clipboard",
|
"fromClipboard": "Add From Clipboard",
|
||||||
"scanQr": "Scan QR code",
|
"scanQr": "Scan QR code",
|
||||||
|
"qrScanner": {
|
||||||
|
"permissionDeniedError": "Permission denied",
|
||||||
|
"unexpectedError": "Something went wrong",
|
||||||
|
"torchSemanticLabel": "Flash light",
|
||||||
|
"facingSemanticLabel": "Camera facing"
|
||||||
|
},
|
||||||
"manually": "Manual Entry",
|
"manually": "Manual Entry",
|
||||||
"addingProfileMsg": "Adding Profile",
|
"addingProfileMsg": "Adding Profile",
|
||||||
"failureMsg": "Failed to add profile"
|
"failureMsg": "Failed to add profile"
|
||||||
|
|||||||
@@ -59,7 +59,13 @@
|
|||||||
"scanQr": "اسکن QR کد",
|
"scanQr": "اسکن QR کد",
|
||||||
"manually": "افزودن دستی",
|
"manually": "افزودن دستی",
|
||||||
"addingProfileMsg": "در حال افزودن پروفایل",
|
"addingProfileMsg": "در حال افزودن پروفایل",
|
||||||
"failureMsg": "در افزودن پروفایل خطایی رخ داد"
|
"failureMsg": "در افزودن پروفایل خطایی رخ داد",
|
||||||
|
"qrScanner": {
|
||||||
|
"permissionDeniedError": "اجازه رد شد",
|
||||||
|
"unexpectedError": "خطایی رخ داده",
|
||||||
|
"torchSemanticLabel": "چراغ فلاش",
|
||||||
|
"facingSemanticLabel": "جهت دوربین"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"buttonTxt": "بروزرسانی",
|
"buttonTxt": "بروزرسانی",
|
||||||
|
|||||||
@@ -59,7 +59,13 @@
|
|||||||
"scanQr": "Сканировать QR-код",
|
"scanQr": "Сканировать QR-код",
|
||||||
"manually": "Ввести вручную",
|
"manually": "Ввести вручную",
|
||||||
"addingProfileMsg": "Добавление профиля",
|
"addingProfileMsg": "Добавление профиля",
|
||||||
"failureMsg": "Не удалось добавить профиль"
|
"failureMsg": "Не удалось добавить профиль",
|
||||||
|
"qrScanner": {
|
||||||
|
"permissionDeniedError": "Доступ запрещен",
|
||||||
|
"unexpectedError": "Что-то пошло не так",
|
||||||
|
"torchSemanticLabel": "Вспышка",
|
||||||
|
"facingSemanticLabel": "Перед камерой"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"buttonTxt": "Обновить",
|
"buttonTxt": "Обновить",
|
||||||
|
|||||||
@@ -59,7 +59,13 @@
|
|||||||
"scanQr": "扫二维码",
|
"scanQr": "扫二维码",
|
||||||
"manually": "手动输入",
|
"manually": "手动输入",
|
||||||
"addingProfileMsg": "添加配置文件",
|
"addingProfileMsg": "添加配置文件",
|
||||||
"failureMsg": "添加配置文件失败"
|
"failureMsg": "添加配置文件失败",
|
||||||
|
"qrScanner": {
|
||||||
|
"permissionDeniedError": "没有权限",
|
||||||
|
"unexpectedError": "出了些问题",
|
||||||
|
"torchSemanticLabel": "手电筒",
|
||||||
|
"facingSemanticLabel": "相机朝向"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"buttonTxt": "更新",
|
"buttonTxt": "更新",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import 'package:dartx/dartx.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hiddify/core/core_providers.dart';
|
||||||
import 'package:hiddify/utils/utils.dart';
|
import 'package:hiddify/utils/utils.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
@@ -18,15 +20,17 @@ class QRCodeScannerScreen extends HookConsumerWidget with PresLogger {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final t = ref.watch(translationsProvider);
|
||||||
|
|
||||||
final controller = useMemoized(
|
final controller = useMemoized(
|
||||||
() => MobileScannerController(
|
() => MobileScannerController(detectionTimeoutMs: 500),
|
||||||
detectionTimeoutMs: 500,
|
|
||||||
formats: [BarcodeFormat.qrCode],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => controller.dispose, []);
|
useEffect(() => controller.dispose, []);
|
||||||
|
|
||||||
|
final size = MediaQuery.sizeOf(context);
|
||||||
|
final overlaySize = (size.shortestSide - 12).coerceAtMost(248);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
extendBodyBehindAppBar: true,
|
extendBodyBehindAppBar: true,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -48,6 +52,7 @@ class QRCodeScannerScreen extends HookConsumerWidget with PresLogger {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
tooltip: t.profile.add.qrScanner.torchSemanticLabel,
|
||||||
onPressed: () => controller.toggleTorch(),
|
onPressed: () => controller.toggleTorch(),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -62,11 +67,14 @@ class QRCodeScannerScreen extends HookConsumerWidget with PresLogger {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
tooltip: t.profile.add.qrScanner.facingSemanticLabel,
|
||||||
onPressed: () => controller.switchCamera(),
|
onPressed: () => controller.switchCamera(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: MobileScanner(
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
MobileScanner(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
onDetect: (capture) {
|
onDetect: (capture) {
|
||||||
final data = capture.barcodes.first;
|
final data = capture.barcodes.first;
|
||||||
@@ -76,7 +84,93 @@ class QRCodeScannerScreen extends HookConsumerWidget with PresLogger {
|
|||||||
Navigator.of(context, rootNavigator: true).pop(data.url?.url);
|
Navigator.of(context, rootNavigator: true).pop(data.url?.url);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
errorBuilder: (_, error, __) {
|
||||||
|
final message = switch (error.errorCode) {
|
||||||
|
MobileScannerErrorCode.permissionDenied =>
|
||||||
|
t.profile.add.qrScanner.permissionDeniedError,
|
||||||
|
_ => t.profile.add.qrScanner.unexpectedError,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 8),
|
||||||
|
child: Icon(Icons.error, color: Colors.white),
|
||||||
|
),
|
||||||
|
Text(message),
|
||||||
|
Text(error.errorDetails?.message ?? ''),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CustomPaint(
|
||||||
|
painter: ScannerOverlay(
|
||||||
|
Rect.fromCenter(
|
||||||
|
center: size.center(Offset.zero),
|
||||||
|
width: overlaySize,
|
||||||
|
height: overlaySize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ScannerOverlay extends CustomPainter {
|
||||||
|
ScannerOverlay(this.scanWindow);
|
||||||
|
|
||||||
|
final Rect scanWindow;
|
||||||
|
final double borderRadius = 12.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final backgroundPath = Path()..addRect(Rect.largest);
|
||||||
|
final cutoutPath = Path()
|
||||||
|
..addRRect(
|
||||||
|
RRect.fromRectAndCorners(
|
||||||
|
scanWindow,
|
||||||
|
topLeft: Radius.circular(borderRadius),
|
||||||
|
topRight: Radius.circular(borderRadius),
|
||||||
|
bottomLeft: Radius.circular(borderRadius),
|
||||||
|
bottomRight: Radius.circular(borderRadius),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final backgroundPaint = Paint()
|
||||||
|
..color = Colors.black.withOpacity(0.5)
|
||||||
|
..style = PaintingStyle.fill
|
||||||
|
..blendMode = BlendMode.dstOut;
|
||||||
|
|
||||||
|
final backgroundWithCutout = Path.combine(
|
||||||
|
PathOperation.difference,
|
||||||
|
backgroundPath,
|
||||||
|
cutoutPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
final borderPaint = Paint()
|
||||||
|
..color = Colors.white
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 3.0;
|
||||||
|
|
||||||
|
final borderRect = RRect.fromRectAndCorners(
|
||||||
|
scanWindow,
|
||||||
|
topLeft: Radius.circular(borderRadius),
|
||||||
|
topRight: Radius.circular(borderRadius),
|
||||||
|
bottomLeft: Radius.circular(borderRadius),
|
||||||
|
bottomRight: Radius.circular(borderRadius),
|
||||||
|
);
|
||||||
|
|
||||||
|
canvas.drawPath(backgroundWithCutout, backgroundPaint);
|
||||||
|
canvas.drawRRect(borderRect, borderPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user