Analyze qr code image ()

This commit is contained in:
Neeraj Gupta 2023-11-16 15:40:47 +05:30 committed by GitHub
commit 233858ad09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 611 additions and 19 deletions

12
.vscode/settings.json vendored
View file

@ -1,7 +1,7 @@
{
"workbench.colorCustomizations": {
"activityBar.background": "#44116A",
"titleBar.activeBackground": "#5F1895",
"titleBar.activeForeground": "#FDFBFE"
}
}
"workbench.colorCustomizations": {
"activityBar.background": "#44116A",
"titleBar.activeBackground": "#5F1895",
"titleBar.activeForeground": "#FDFBFE"
}
}

View file

@ -53,12 +53,14 @@
</queries>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32"/>
android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
tools:ignore="ScopedStorage"/>
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
</manifest>

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.16998 14.8299L14.83 9.16992" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.83 14.8299L9.16998 9.16992" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

(image error) Size: 536 B

View file

@ -0,0 +1,20 @@
<svg width="62" height="62" viewBox="0 0 62 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_884_3193)">
<rect x="15" y="14" width="32" height="32" rx="15" fill="white"/>
</g>
<path d="M40.7558 20.2363C40.4408 19.9212 39.9262 19.9212 39.6112 20.2363L20.2363 39.6217C19.9212 39.9367 19.9212 40.4513 20.2363 40.7663C20.3938 40.9133 20.5933 40.9973 20.8033 40.9973C21.0134 40.9973 21.2129 40.9133 21.3704 40.7558L40.7558 21.3704C41.0814 21.0554 41.0814 20.5513 40.7558 20.2363Z" fill="#292D32"/>
<path d="M33.4575 21.5971V27.5408L27.5347 33.4636V31.8464H24.2898C22.8196 31.8464 22.4101 30.9433 23.3867 29.8407L30.4961 21.7546L31.3362 20.799C32.5018 19.4759 33.4575 19.8329 33.4575 21.5971Z" fill="#292D32"/>
<path d="M37.6039 31.1619L30.4946 39.2479L29.6545 40.2035C28.4888 41.5267 27.5332 41.1696 27.5332 39.4054V36.6121L34.9891 29.1562H36.7008C38.171 29.1562 38.5806 30.0593 37.6039 31.1619Z" fill="#292D32"/>
<defs>
<filter id="filter0_d_884_3193" x="0" y="0" width="62" height="62" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="5" operator="dilate" in="SourceAlpha" result="effect1_dropShadow_884_3193"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.17 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_884_3193"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_884_3193" result="shape"/>
</filter>
</defs>
</svg>

After

(image error) Size: 1.7 KiB

View file

@ -0,0 +1,18 @@
<svg width="62" height="62" viewBox="0 0 62 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_5498_6943)">
<rect x="15" y="14" width="32" height="32" rx="15" fill="white"/>
</g>
<path d="M36.844 29.1561H33.5997V21.5968C33.5997 19.8329 32.6443 19.476 31.4789 20.7988L30.639 21.7543L23.5311 29.8386C22.5547 30.941 22.9642 31.8439 24.4341 31.8439H27.6783V39.4032C27.6783 41.1671 28.6337 41.524 29.7991 40.2012L30.639 39.2457L37.7469 31.1615C38.7233 30.0591 38.3138 29.1561 36.844 29.1561Z" fill="#292D32"/>
<defs>
<filter id="filter0_d_5498_6943" x="0" y="0" width="62" height="62" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="5" operator="dilate" in="SourceAlpha" result="effect1_dropShadow_5498_6943"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.17 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5498_6943"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5498_6943" result="shape"/>
</filter>
</defs>
</svg>

After

(image error) Size: 1.3 KiB

View file

@ -0,0 +1,18 @@
<svg width="62" height="62" viewBox="0 0 62 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_884_3200)">
<rect x="15" y="14" width="32" height="32" rx="15" fill="white"/>
</g>
<path d="M35.19 20H26.81C23.17 20 21 22.17 21 25.81V34.19C21 35.28 21.19 36.23 21.56 37.03C22.42 38.93 24.26 40 26.81 40H35.19C38.83 40 41 37.83 41 34.19V31.9V25.81C41 22.17 38.83 20 35.19 20ZM39.37 30.5C38.59 29.83 37.33 29.83 36.55 30.5L32.39 34.07C31.61 34.74 30.35 34.74 29.57 34.07L29.23 33.79C28.52 33.17 27.39 33.11 26.59 33.65L22.85 36.16C22.63 35.6 22.5 34.95 22.5 34.19V25.81C22.5 22.99 23.99 21.5 26.81 21.5H35.19C38.01 21.5 39.5 22.99 39.5 25.81V30.61L39.37 30.5Z" fill="#292D32"/>
<defs>
<filter id="filter0_d_884_3200" x="0" y="0" width="62" height="62" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="5" operator="dilate" in="SourceAlpha" result="effect1_dropShadow_884_3200"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.17 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_884_3200"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_884_3200" result="shape"/>
</filter>
</defs>
</svg>

After

(image error) Size: 1.4 KiB

View file

@ -340,6 +340,13 @@
"showQRAuthMessage": "Authenticate to show QR code",
"confirmAccountDeleteTitle": "Confirm account deletion",
"confirmAccountDeleteMessage": "This account is linked to other ente apps, if you use any.\n\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
"reminderText": "Reminder",
"reminderPopupBody": "Please delete the screenshot before resuming any photo cloud sync",
"invalidQrCodeText": "Invalid QR code",
"googleAuthImagePopupBody": "Please turn off all photo cloud sync from all apps, including iCloud, Google Photo, OneDrive, etc. \nAlso if you have a second smartphone, it is safer to import by scanning QR code.",
"importGoogleAuthImageButtonText": "Import from image",
"unableToRecognizeQrCodeText": "Unable to recognize a valid code from the uploaded image",
"qrCodeImageNotSelectedText": "Qr code image not selected",
"androidBiometricHint": "Verify identity",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
@ -391,5 +398,6 @@
"iOSOkButton": "OK",
"@iOSOkButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
}
},
"parsingErrorText": "Error while parsing Google Auth QR code"
}

View file

@ -0,0 +1,268 @@
import 'dart:io';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/code.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart';
import 'package:ente_auth/ui/settings/data/import/qr_scanner_overlay.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:logging/logging.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
class QrScanner extends StatefulWidget {
const QrScanner({super.key});
@override
State<QrScanner> createState() => _QrScannerState();
}
class _QrScannerState extends State<QrScanner> {
bool isNavigationPerformed = false;
bool isScannedByImage = false;
MobileScannerController scannerController = MobileScannerController(
detectionSpeed: DetectionSpeed.normal,
facing: CameraFacing.back,
);
@override
void dispose() {
scannerController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return SafeArea(
child: Scaffold(
backgroundColor: Colors.black,
body: Stack(
alignment: Alignment.topLeft,
children: [
Stack(
children: [
MobileScanner(
controller: scannerController,
onDetect: (capture) async {
if (!isNavigationPerformed) {
isNavigationPerformed = true;
if (capture.barcodes[0].rawValue!
.startsWith(kGoogleAuthExportPrefix)) {
if (isScannedByImage) {
final result = await showDialogWidget(
context: context,
title: l10n.reminderText,
body: l10n.reminderPopupBody,
buttons: [
ButtonWidget(
buttonType: ButtonType.primary,
labelText: l10n.ok,
isInAlert: true,
buttonSize: ButtonSize.large,
buttonAction: ButtonAction.first,
),
],
);
if (result?.action != null &&
result!.action == ButtonAction.first) {
isScannedByImage = false;
}
}
HapticFeedback.vibrate();
try {
List<Code> codes =
parseGoogleAuth(capture.barcodes[0].rawValue!);
scannerController.dispose();
Navigator.of(context).pop(codes);
} catch (e) {
showToast(context, l10n.parsingErrorText);
Logger("Code parsing error").severe(
"Error while parsing Google Auth QR code",
e,
);
throw Exception(
'Failed to parse Google Auth QR code \n ${e.toString()}',
);
}
} else {
showToast(context, l10n.invalidQrCodeText);
isNavigationPerformed = false;
}
}
},
),
const QRScannerOverlay(),
Positioned(
top: 150,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Torch button
IconButton(
icon: ValueListenableBuilder(
valueListenable: scannerController.torchState,
builder: (context, state, child) {
switch (state) {
case TorchState.on:
return SvgPicture.asset(
'assets/scanner-icons/icons/flash_on.svg',
);
case TorchState.off:
return SvgPicture.asset(
'assets/scanner-icons/icons/flash_off.svg',
);
}
},
),
iconSize: 60,
onPressed: () => scannerController.toggleTorch(),
),
IconButton(
icon: SvgPicture.asset(
'assets/scanner-icons/icons/gallery.svg',
),
iconSize: 60,
onPressed: () async {
final result = await showDialogWidget(
context: context,
title: l10n.importFromApp(
"Google Authenticator (saved image)",
),
body: l10n.googleAuthImagePopupBody,
buttons: [
ButtonWidget(
buttonType: ButtonType.primary,
labelText: l10n.importGoogleAuthImageButtonText,
isInAlert: true,
buttonSize: ButtonSize.large,
buttonAction: ButtonAction.first,
),
ButtonWidget(
buttonType: ButtonType.secondary,
labelText: l10n.cancel,
buttonSize: ButtonSize.large,
isInAlert: true,
buttonAction: ButtonAction.second,
),
],
);
if (result?.action != null &&
result!.action != ButtonAction.cancel) {
if (result.action == ButtonAction.first) {
List<AssetEntity>? assets =
await AssetPicker.pickAssets(
context,
pickerConfig: const AssetPickerConfig(
maxAssets: 1,
requestType: RequestType.image,
),
);
if (assets != null && assets.isNotEmpty) {
AssetEntity asset = assets.first;
File? file = await asset.file;
String path = file!.path;
if (await scannerController
.analyzeImage(path)) {
isScannedByImage = true;
if (!mounted) return;
} else {
if (!mounted) return;
isScannedByImage = false;
showToast(
context,
l10n.unableToRecognizeQrCodeText,
);
}
} else {
if (!mounted) return;
showToast(
context,
l10n.qrCodeImageNotSelectedText,
);
}
}
}
},
),
],
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
color: Colors.white,
),
child: Padding(
padding: const EdgeInsets.fromLTRB(
40,
15,
40,
18,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
height: 5,
width: 40,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(50),
),
),
const SizedBox(
height: 25,
),
Text(
l10n.scanACode,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.black,
),
),
],
),
),
),
),
],
),
Positioned(
left: 25,
top: 25,
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: SvgPicture.asset(
'assets/scanner-icons/icons/cross.svg',
colorFilter:
const ColorFilter.mode(Colors.white, BlendMode.srcATop),
height: 30,
),
),
),
],
),
),
);
}
}

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:base32/base32.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/code.dart';
@ -10,7 +11,7 @@ import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/scanner_gauth_page.dart';
import 'package:ente_auth/ui/settings/data/import/analyze_qr_code.dart';
import 'package:ente_auth/ui/settings/data/import/import_success.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
@ -45,7 +46,7 @@ Future<void> showGoogleAuthInstruction(BuildContext context) async {
final List<Code>? codes = await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return const ScannerGoogleAuthPage();
return const QrScanner();
},
),
);

View file

@ -5,7 +5,7 @@ import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart';
import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart';
import 'package:ente_auth/ui/settings/data/import/raivo_plain_text_import.dart';
import 'package:ente_auth/ui/settings/data/import_page.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ImportService {
static final ImportService _instance = ImportService._internal();

View file

@ -0,0 +1,152 @@
import 'package:flutter/material.dart';
class QRScannerOverlay extends StatelessWidget {
const QRScannerOverlay({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
double scanArea = (MediaQuery.of(context).size.width < 400 ||
MediaQuery.of(context).size.height < 400)
? 200.0
: 330.0;
return Stack(
children: [
ColorFiltered(
colorFilter:
ColorFilter.mode(Colors.black.withOpacity(0.9), BlendMode.srcOut),
child: Stack(
children: [
Container(
decoration: const BoxDecoration(
color: Colors.red,
backgroundBlendMode: BlendMode.dstOut,
),
),
Align(
alignment: Alignment.center,
child: Container(
height: scanArea,
width: scanArea,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(20),
),
),
),
],
),
),
Align(
alignment: Alignment.center,
child: CustomPaint(
foregroundPainter: BorderPainter(),
child: SizedBox(
width: scanArea + 25,
height: scanArea + 25,
),
),
),
],
);
}
}
// Creates the white borders
class BorderPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
const width = 4.0;
const radius = 20.0;
const tRadius = 3 * radius;
final rect = Rect.fromLTWH(
width,
width,
size.width - 2 * width,
size.height - 2 * width,
);
final rrect = RRect.fromRectAndRadius(rect, const Radius.circular(radius));
const clippingRect0 = Rect.fromLTWH(
0,
0,
tRadius,
tRadius,
);
final clippingRect1 = Rect.fromLTWH(
size.width - tRadius,
0,
tRadius,
tRadius,
);
final clippingRect2 = Rect.fromLTWH(
0,
size.height - tRadius,
tRadius,
tRadius,
);
final clippingRect3 = Rect.fromLTWH(
size.width - tRadius,
size.height - tRadius,
tRadius,
tRadius,
);
final path = Path()
..addRect(clippingRect0)
..addRect(clippingRect1)
..addRect(clippingRect2)
..addRect(clippingRect3);
canvas.clipPath(path);
canvas.drawRRect(
rrect,
Paint()
..color = Colors.blueAccent
..style = PaintingStyle.stroke
..strokeWidth = width,
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
class BarReaderSize {
static double width = 200;
static double height = 200;
}
class OverlayWithHolePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.black54;
canvas.drawPath(
Path.combine(
PathOperation.difference,
Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height)),
Path()
..addOval(
Rect.fromCircle(
center: Offset(size.width - 44, size.height - 44),
radius: 40,
),
)
..close(),
),
paint,
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}

View file

@ -34,6 +34,7 @@ class ImportCodePage extends StatelessWidget {
switch (type) {
case ImportType.plainText:
return context.l10n.importTypePlainText;
case ImportType.encrypted:
return context.l10n.importTypeEnteEncrypted;
case ImportType.ravio:
@ -66,7 +67,7 @@ class ImportCodePage extends StatelessWidget {
iconButtonType: IconButtonType.secondary,
onTap: () {
Navigator.pop(context);
if(Navigator.canPop(context)) {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
},

View file

@ -74,7 +74,9 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
// Show the lock screen again only if the app is resuming from the
// background, and not when the lock screen was explicitly dismissed
Future.delayed(
Duration.zero, () => _showLockScreen(source: "lifeCycle"));
Duration.zero,
() => _showLockScreen(source: "lifeCycle"),
);
} else {
_hasAuthenticationFailed = false; // Reset failure state
}

View file

@ -10,13 +10,16 @@ import device_info_plus
import file_saver
import flutter_local_notifications
import flutter_secure_storage_macos
import mobile_scanner
import package_info_plus
import path_provider_foundation
import photo_manager
import sentry_flutter
import share_plus
import shared_preferences_foundation
import sqflite
import url_launcher_macos
import video_player_avfoundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
@ -24,11 +27,14 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
PhotoManagerPlugin.register(with: registry.registrar(forPlugin: "PhotoManagerPlugin"))
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
}

View file

@ -386,6 +386,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
extended_image:
dependency: transitive
description:
name: extended_image
sha256: b4d72a27851751cfadaf048936d42939db7cd66c08fdcfe651eeaa1179714ee6
url: "https://pub.dev"
source: hosted
version: "8.1.1"
extended_image_library:
dependency: transitive
description:
name: extended_image_library
sha256: "8bf87c0b14dcb59200c923a9a3952304e4732a0901e40811428834ef39018ee1"
url: "https://pub.dev"
source: hosted
version: "3.6.0"
fake_async:
dependency: transitive
description:
@ -687,6 +703,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
http_client_helper:
dependency: transitive
description:
name: http_client_helper
sha256: "8a9127650734da86b5c73760de2b404494c968a3fd55602045ffec789dac3cb1"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
http_multi_server:
dependency: transitive
description:
@ -839,6 +863,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
mobile_scanner:
dependency: "direct main"
description:
name: mobile_scanner
sha256: cf978740676ba5b0c17399baf117984b31190bb7a6eaa43e51229ed46abc42ee
url: "https://pub.dev"
source: hosted
version: "3.5.2"
mocktail:
dependency: "direct dev"
description:
@ -1007,6 +1039,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.1.0"
photo_manager:
dependency: transitive
description:
name: photo_manager
sha256: c1f21882f22c97cc85a8a67b08d7b979a03c9b7f18f940c10c6860b3a49581d3
url: "https://pub.dev"
source: hosted
version: "2.8.0"
pinput:
dependency: "direct main"
description:
@ -1548,6 +1588,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
video_player:
dependency: transitive
description:
name: video_player
sha256: e16f0a83601a78d165dabc17e4dac50997604eb9e4cc76e10fa219046b70cef3
url: "https://pub.dev"
source: hosted
version: "2.8.1"
video_player_android:
dependency: transitive
description:
name: video_player_android
sha256: "3fe89ab07fdbce786e7eb25b58532d6eaf189ceddc091cb66cba712f8d9e8e55"
url: "https://pub.dev"
source: hosted
version: "2.4.10"
video_player_avfoundation:
dependency: transitive
description:
name: video_player_avfoundation
sha256: fe73d636f82286a3739f5e644f95f09442cacdc436ebbe5436521dc915f3ecac
url: "https://pub.dev"
source: hosted
version: "2.5.1"
video_player_platform_interface:
dependency: transitive
description:
name: video_player_platform_interface
sha256: be72301bf2c0150ab35a8c34d66e5a99de525f6de1e8d27c0672b836fe48f73a
url: "https://pub.dev"
source: hosted
version: "6.2.1"
video_player_web:
dependency: transitive
description:
name: video_player_web
sha256: ab7a462b07d9ca80bed579e30fb3bce372468f1b78642e0911b10600f2c5cb5b
url: "https://pub.dev"
source: hosted
version: "2.1.2"
vm_service:
dependency: transitive
description:
@ -1588,6 +1668,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
wechat_assets_picker:
dependency: "direct main"
description:
name: wechat_assets_picker
sha256: "00c93a04421013040b555cdcccdb8e90f142a171d6c0d968c2b5042a76013601"
url: "https://pub.dev"
source: hosted
version: "8.7.1"
win32:
dependency: transitive
description:
@ -1629,5 +1717,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.10.0"
dart: ">=3.1.0 <4.0.0"
flutter: ">=3.13.0"

View file

@ -58,6 +58,7 @@ dependencies:
local_auth_android: ^1.0.31
local_auth_ios: ^1.1.3
logging: ^1.0.1
mobile_scanner: ^3.5.2
modal_bottom_sheet: ^3.0.0-pre
move_to_background: ^1.0.2
open_filex: ^4.3.2
@ -81,6 +82,7 @@ dependencies:
uni_links: ^0.5.1
url_launcher: ^6.1.5
uuid: ^3.0.4
wechat_assets_picker: ^8.6.3
dev_dependencies:
bloc_test: ^9.0.3
@ -103,6 +105,7 @@ flutter:
- assets/simple-icons/_data/
- assets/custom-icons/icons/
- assets/custom-icons/_data/
- assets/scanner-icons/icons/
fonts:
- family: Inter