Function added: Qr Code Image analyzing by uploading from gallery and scanning Qr code images directly
This commit is contained in:
parent
7855e3925a
commit
3db1c55ce3
15 changed files with 700 additions and 26 deletions
13
.vscode/settings.json
vendored
13
.vscode/settings.json
vendored
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.background": "#44116A",
|
||||
"titleBar.activeBackground": "#5F1895",
|
||||
"titleBar.activeForeground": "#FDFBFE"
|
||||
}
|
||||
}
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.background": "#44116A",
|
||||
"titleBar.activeBackground": "#5F1895",
|
||||
"titleBar.activeForeground": "#FDFBFE"
|
||||
},
|
||||
"java.configuration.updateBuildConfiguration": "interactive"
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
compileSdkVersion 34
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
|
@ -46,7 +46,7 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "io.ente.auth"
|
||||
minSdkVersion 20
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
@ -54,14 +54,14 @@ android {
|
|||
multiDexEnabled true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : file(System.getenv("SIGNING_KEY_PATH"))
|
||||
keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS")
|
||||
keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
|
||||
storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
|
||||
}
|
||||
}
|
||||
// signingConfigs {
|
||||
// release {
|
||||
// storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : file(System.getenv("SIGNING_KEY_PATH"))
|
||||
// keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS")
|
||||
// keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
|
||||
// storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
|
||||
// }
|
||||
// }
|
||||
|
||||
flavorDimensions "default"
|
||||
productFlavors {
|
||||
|
@ -79,7 +79,7 @@ android {
|
|||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix '.debug'
|
||||
|
|
|
@ -52,13 +52,20 @@
|
|||
</intent>
|
||||
</queries>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.MANAGE_MEDIA" />
|
||||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"/>
|
||||
android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- If you want to read images-->
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29"
|
||||
tools:ignore="ScopedStorage"/>
|
||||
android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- If you want to read videos-->
|
||||
<uses-permission
|
||||
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.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
||||
|
||||
</manifest>
|
||||
|
|
5
assets/custom-icons/icons/cross.svg
Normal file
5
assets/custom-icons/icons/cross.svg
Normal 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 Width: | Height: | Size: 536 B |
20
assets/custom-icons/icons/flash_off.svg
Normal file
20
assets/custom-icons/icons/flash_off.svg
Normal 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 Width: | Height: | Size: 1.7 KiB |
18
assets/custom-icons/icons/flash_on.svg
Normal file
18
assets/custom-icons/icons/flash_on.svg
Normal 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 Width: | Height: | Size: 1.3 KiB |
18
assets/custom-icons/icons/gallery.svg
Normal file
18
assets/custom-icons/icons/gallery.svg
Normal 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 Width: | Height: | Size: 1.4 KiB |
282
lib/ui/settings/data/import/analyze_qr_code.dart
Normal file
282
lib/ui/settings/data/import/analyze_qr_code.dart
Normal file
|
@ -0,0 +1,282 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/l10n/l10n.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/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: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;
|
||||
MobileScannerController scannerController = MobileScannerController(
|
||||
detectionSpeed: DetectionSpeed.normal,
|
||||
facing: CameraFacing.back,
|
||||
);
|
||||
@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) {
|
||||
if (!isNavigationPerformed) {
|
||||
isNavigationPerformed = true;
|
||||
HapticFeedback.vibrate();
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: Colors.white,
|
||||
buttonPadding: const EdgeInsets.all(0),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
alignment: Alignment.center,
|
||||
insetPadding: const EdgeInsets.symmetric(
|
||||
vertical: 24,
|
||||
horizontal: 24,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
title: const Text(
|
||||
'Scan result',
|
||||
style: TextStyle(
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18,
|
||||
color: Colors.black,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
content: Text(
|
||||
' ${capture.barcodes[0].rawValue!}',
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15,
|
||||
color: Colors.black,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
actions: [
|
||||
Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
isNavigationPerformed = false;
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Text(
|
||||
'OK',
|
||||
style: TextStyle(
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const QRScannerOverlay(),
|
||||
Positioned(
|
||||
top: 150,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: ValueListenableBuilder(
|
||||
valueListenable: scannerController.torchState,
|
||||
builder: (context, state, child) {
|
||||
switch (state) {
|
||||
case TorchState.on:
|
||||
return SvgPicture.asset(
|
||||
'assets/custom-icons/icons/flash_on.svg',
|
||||
);
|
||||
case TorchState.off:
|
||||
return SvgPicture.asset(
|
||||
'assets/custom-icons/icons/flash_off.svg',
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
iconSize: 60,
|
||||
onPressed: () => scannerController.toggleTorch(),
|
||||
),
|
||||
IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
'assets/custom-icons/icons/gallery.svg',
|
||||
),
|
||||
iconSize: 60,
|
||||
onPressed: () async {
|
||||
final result = await showDialogWidget(
|
||||
context: context,
|
||||
title: 'Google Authenticator (saved image)',
|
||||
body:
|
||||
'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.',
|
||||
buttons: [
|
||||
const ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
labelText: 'Import from image',
|
||||
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,
|
||||
),
|
||||
);
|
||||
|
||||
if (assets != null && assets.isNotEmpty) {
|
||||
AssetEntity asset = assets.first;
|
||||
File? file = await asset.file;
|
||||
String path = file!.path;
|
||||
|
||||
if (await scannerController
|
||||
.analyzeImage(path)) {
|
||||
if (!mounted) return;
|
||||
} else {
|
||||
if (!mounted) return;
|
||||
showToast(context, "Failed to scan image");
|
||||
}
|
||||
} else {
|
||||
if (!mounted) return;
|
||||
showToast(context, "Image not selected");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
const Text(
|
||||
'Scan QR code',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
left: 25,
|
||||
top: 25,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
'assets/custom-icons/icons/cross.svg',
|
||||
colorFilter:
|
||||
const ColorFilter.mode(Colors.white, BlendMode.srcATop),
|
||||
height: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
67
lib/ui/settings/data/import/google_auth_image_import.dart
Normal file
67
lib/ui/settings/data/import/google_auth_image_import.dart
Normal file
|
@ -0,0 +1,67 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/l10n/l10n.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/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
|
||||
|
||||
const kGoogleAuthExportPrefix = 'otpauth-migration://offline?data=';
|
||||
|
||||
Future<void> showGoogleAuthImageInstruction(BuildContext context) async {
|
||||
MobileScannerController scannerController = MobileScannerController(
|
||||
detectionSpeed: DetectionSpeed.normal,
|
||||
);
|
||||
final l10n = context.l10n;
|
||||
final result = await showDialogWidget(
|
||||
context: context,
|
||||
title: 'Google Authenticator (saved image)',
|
||||
body:
|
||||
'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.',
|
||||
buttons: [
|
||||
const ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
labelText: 'Import from image',
|
||||
isInAlert: true,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.first,
|
||||
),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.secondary,
|
||||
labelText: context.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,
|
||||
),
|
||||
);
|
||||
|
||||
if (assets != null && assets.isNotEmpty) {
|
||||
AssetEntity asset = assets.first;
|
||||
File? file = await asset.file;
|
||||
String path = file!.path;
|
||||
|
||||
if (await scannerController.analyzeImage(path)) {
|
||||
final barcode = await scannerController.barcodes.first;
|
||||
showToast(context, "$barcode");
|
||||
} else {
|
||||
showToast(context, "Failed to scan image");
|
||||
}
|
||||
} else {
|
||||
showToast(context, "Image not selected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
import 'package:ente_auth/ui/settings/data/import/aegis_import.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/analyze_qr_code.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import/encrypted_ente_import.dart';
|
||||
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();
|
||||
|
@ -31,6 +32,15 @@ class ImportService {
|
|||
case ImportType.aegis:
|
||||
showAegisImportInstruction(context);
|
||||
break;
|
||||
case ImportType.googleAuthenticatorImage:
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const QrScanner();
|
||||
},
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
145
lib/ui/settings/data/import/qr_scanner_overlay.dart
Normal file
145
lib/ui/settings/data/import/qr_scanner_overlay.dart
Normal file
|
@ -0,0 +1,145 @@
|
|||
|
||||
|
||||
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;
|
||||
}
|
|
@ -15,6 +15,7 @@ enum ImportType {
|
|||
ravio,
|
||||
googleAuthenticator,
|
||||
aegis,
|
||||
googleAuthenticatorImage
|
||||
}
|
||||
|
||||
class ImportCodePage extends StatelessWidget {
|
||||
|
@ -24,6 +25,7 @@ class ImportCodePage extends StatelessWidget {
|
|||
ImportType.ravio,
|
||||
ImportType.aegis,
|
||||
ImportType.googleAuthenticator,
|
||||
ImportType.googleAuthenticatorImage,
|
||||
];
|
||||
|
||||
ImportCodePage({super.key});
|
||||
|
@ -32,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:
|
||||
|
@ -40,6 +43,8 @@ class ImportCodePage extends StatelessWidget {
|
|||
return 'Google Authenticator';
|
||||
case ImportType.aegis:
|
||||
return 'Aegis Authenticator';
|
||||
case ImportType.googleAuthenticatorImage:
|
||||
return 'Google Authenticator (saved image)';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,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);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
|
92
pubspec.lock
92
pubspec.lock
|
@ -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"
|
||||
|
|
|
@ -55,6 +55,7 @@ dependencies:
|
|||
json_annotation: ^4.5.0
|
||||
local_auth: ^2.1.7
|
||||
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
|
||||
|
@ -78,6 +79,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
|
||||
|
|
Loading…
Add table
Reference in a new issue