mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-22 16:32:01 +03:00
Read QR code from file on Android
This commit is contained in:
parent
8a9d465bb1
commit
184e7a7f2c
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Yubico.
|
||||
* Copyright (C) 2022-2023 Yubico.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -34,8 +34,8 @@ class MethodChannelQRScannerZxing extends QRScannerZxingPlatform {
|
||||
|
||||
@override
|
||||
Future<String?> scanBitmap(Uint8List bytes) async {
|
||||
final version = await methodChannel
|
||||
final result = await methodChannel
|
||||
.invokeMethod<String>('scanBitmap', {'bytes': bytes});
|
||||
return version;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Yubico.
|
||||
* Copyright (C) 2022-2023 Yubico.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,16 +16,25 @@
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:qrscanner_zxing/qrscanner_zxing_method_channel.dart';
|
||||
import 'package:yubico_authenticator/app/state.dart';
|
||||
import 'package:yubico_authenticator/exception/cancellation_exception.dart';
|
||||
import 'package:yubico_authenticator/theme.dart';
|
||||
|
||||
import 'package:qrscanner_zxing/qrscanner_zxing_method_channel.dart';
|
||||
|
||||
import '../../app/message.dart';
|
||||
import '../../oath/views/add_account_page.dart';
|
||||
import '../../oath/views/utils.dart';
|
||||
import 'qr_scanner_view.dart';
|
||||
|
||||
class AndroidQrScanner implements QrScanner {
|
||||
static const String kQrScannerRequestManualEntry =
|
||||
'__QR_SCANNER_ENTER_MANUALLY__';
|
||||
static const String kQrScannerRequestReadFromFile =
|
||||
'__QR_SCANNER_SCAN_FROM_FILE__';
|
||||
final WithContext _withContext;
|
||||
|
||||
AndroidQrScanner(this._withContext);
|
||||
@ -33,8 +42,7 @@ class AndroidQrScanner implements QrScanner {
|
||||
@override
|
||||
Future<String?> scanQr([String? imageData]) async {
|
||||
if (imageData == null) {
|
||||
var scannedCode = await _withContext(
|
||||
(context) async =>
|
||||
var scannedCode = await _withContext((context) async =>
|
||||
await Navigator.of(context).push(PageRouteBuilder(
|
||||
pageBuilder: (_, __, ___) =>
|
||||
Theme(data: AppTheme.darkTheme, child: const QrScannerView()),
|
||||
@ -55,6 +63,59 @@ class AndroidQrScanner implements QrScanner {
|
||||
return await zxingChannel.scanBitmap(base64Decode(imageData));
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> handleScannedData(
|
||||
String? qrData, WidgetRef ref, AppLocalizations l10n) async {
|
||||
final withContext = ref.read(withContextProvider);
|
||||
switch (qrData) {
|
||||
case null:
|
||||
break;
|
||||
case kQrScannerRequestManualEntry:
|
||||
await withContext((context) => showBlurDialog(
|
||||
context: context,
|
||||
routeSettings: const RouteSettings(name: 'oath_add_account'),
|
||||
builder: (context) {
|
||||
return const OathAddAccountPage(
|
||||
null,
|
||||
null,
|
||||
credentials: null,
|
||||
);
|
||||
},
|
||||
));
|
||||
case kQrScannerRequestReadFromFile:
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
allowedExtensions: ['png', 'jpg', 'gif', 'webp'],
|
||||
type: FileType.custom,
|
||||
allowMultiple: false,
|
||||
lockParentWindow: true,
|
||||
withData: true,
|
||||
dialogTitle: 'Select file with QR code');
|
||||
|
||||
if (result == null || !result.isSinglePick) {
|
||||
// no result
|
||||
return;
|
||||
}
|
||||
|
||||
final bytes = result.files.first.bytes;
|
||||
final scanner = ref.read(qrScannerProvider);
|
||||
if (bytes != null && scanner != null) {
|
||||
final b64bytes = base64Encode(bytes);
|
||||
final qrData = await scanner.scanQr(b64bytes);
|
||||
if (qrData != null) {
|
||||
await withContext((context) =>
|
||||
handleUri(context, null, qrData, null, null, l10n));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// no QR code found
|
||||
await withContext(
|
||||
(context) async => showMessage(context, l10n.l_qr_not_found));
|
||||
|
||||
default:
|
||||
await withContext(
|
||||
(context) => handleUri(context, null, qrData, null, null, l10n));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QrScanner? Function(dynamic) androidQrScannerProvider(hasCamera) {
|
||||
|
@ -14,13 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:yubico_authenticator/app/state.dart';
|
||||
import 'package:yubico_authenticator/android/qr_scanner/qr_scanner_provider.dart';
|
||||
|
||||
import '../keys.dart' as keys;
|
||||
import 'qr_scanner_scan_status.dart';
|
||||
@ -79,53 +76,34 @@ class QRScannerUI extends ConsumerWidget {
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop('');
|
||||
Navigator.of(context).pop(
|
||||
AndroidQrScanner.kQrScannerRequestManualEntry);
|
||||
},
|
||||
key: keys.manualEntryButton,
|
||||
child: Text(
|
||||
l10n.s_enter_manually,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
)),
|
||||
const SizedBox(width: 16),
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop('');
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
allowedExtensions: ['png', 'jpg'],
|
||||
type: FileType.custom,
|
||||
allowMultiple: false,
|
||||
lockParentWindow: true,
|
||||
dialogTitle: 'Select file with QR code');
|
||||
if (result != null && result.files.isNotEmpty) {
|
||||
final fileWithCode = result.files.first;
|
||||
final bytes = fileWithCode.bytes;
|
||||
if (bytes == null || bytes.isEmpty) {
|
||||
//err return
|
||||
return;
|
||||
}
|
||||
if (bytes.length > 3 * 1024 * 1024) {
|
||||
// too big file
|
||||
return;
|
||||
}
|
||||
final scanner = ref.read(qrScannerProvider);
|
||||
if (scanner != null) {
|
||||
await scanner.scanQr(base64UrlEncode(bytes));
|
||||
}
|
||||
}
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(
|
||||
AndroidQrScanner.kQrScannerRequestReadFromFile);
|
||||
},
|
||||
key: keys.readFromImage,
|
||||
child: Text(
|
||||
l10n.s_read_from_image,
|
||||
l10n.s_read_from_file,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
)),
|
||||
))
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8)
|
||||
const SizedBox(height: 16)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
@ -15,19 +15,18 @@
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../android/app_methods.dart';
|
||||
import '../../android/qr_scanner/qr_scanner_provider.dart';
|
||||
import '../../android/state.dart';
|
||||
import '../../exception/cancellation_exception.dart';
|
||||
import '../../core/state.dart';
|
||||
import '../../exception/cancellation_exception.dart';
|
||||
import '../../fido/views/fido_screen.dart';
|
||||
import '../../oath/views/add_account_page.dart';
|
||||
import '../../oath/views/oath_screen.dart';
|
||||
import '../../oath/views/utils.dart';
|
||||
import '../../piv/views/piv_screen.dart';
|
||||
import '../../widgets/custom_icons.dart';
|
||||
import '../message.dart';
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'device_error_screen.dart';
|
||||
@ -99,33 +98,16 @@ class MainPage extends ConsumerWidget {
|
||||
icon: const Icon(Icons.person_add_alt_1),
|
||||
tooltip: l10n.s_add_account,
|
||||
onPressed: () async {
|
||||
final withContext = ref.read(withContextProvider);
|
||||
final scanner = ref.read(qrScannerProvider);
|
||||
if (scanner != null) {
|
||||
try {
|
||||
final qrData = await scanner.scanQr();
|
||||
if (qrData != null) {
|
||||
await withContext((context) =>
|
||||
handleUri(context, null, qrData, null, null, l10n));
|
||||
return;
|
||||
}
|
||||
await AndroidQrScanner.handleScannedData(qrData, ref, l10n);
|
||||
} on CancellationException catch (_) {
|
||||
// ignored - user cancelled
|
||||
return;
|
||||
}
|
||||
}
|
||||
await withContext((context) => showBlurDialog(
|
||||
context: context,
|
||||
routeSettings:
|
||||
const RouteSettings(name: 'oath_add_account'),
|
||||
builder: (context) {
|
||||
return const OathAddAccountPage(
|
||||
null,
|
||||
null,
|
||||
credentials: null,
|
||||
);
|
||||
},
|
||||
));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -516,7 +516,7 @@
|
||||
"q_want_to_scan": "Would like to scan?",
|
||||
"q_no_qr": "No QR code?",
|
||||
"s_enter_manually": "Enter manually",
|
||||
"s_read_from_image": "Provide image file",
|
||||
"s_read_from_file": "Read from file",
|
||||
|
||||
"@_factory_reset": {},
|
||||
"s_reset": "Reset",
|
||||
|
@ -30,10 +30,10 @@ import '../../app/message.dart';
|
||||
import '../../app/models.dart';
|
||||
import '../../app/state.dart';
|
||||
import '../../app/views/user_interaction.dart';
|
||||
import '../../exception/apdu_exception.dart';
|
||||
import '../../exception/cancellation_exception.dart';
|
||||
import '../../core/state.dart';
|
||||
import '../../desktop/models.dart';
|
||||
import '../../exception/apdu_exception.dart';
|
||||
import '../../exception/cancellation_exception.dart';
|
||||
import '../../management/models.dart';
|
||||
import '../../widgets/choice_filter_chip.dart';
|
||||
import '../../widgets/file_drop_target.dart';
|
||||
@ -56,6 +56,7 @@ class OathAddAccountPage extends ConsumerStatefulWidget {
|
||||
final OathState? state;
|
||||
final List<OathCredential>? credentials;
|
||||
final CredentialData? credentialData;
|
||||
|
||||
const OathAddAccountPage(
|
||||
this.devicePath,
|
||||
this.state, {
|
||||
|
@ -20,6 +20,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:yubico_authenticator/oath/icon_provider/icon_pack_dialog.dart';
|
||||
import 'package:yubico_authenticator/oath/views/add_account_dialog.dart';
|
||||
|
||||
import '../../android/qr_scanner/qr_scanner_provider.dart';
|
||||
import '../../app/message.dart';
|
||||
import '../../app/models.dart';
|
||||
import '../../app/state.dart';
|
||||
@ -28,11 +29,8 @@ import '../../app/views/action_list.dart';
|
||||
import '../../core/state.dart';
|
||||
import '../models.dart';
|
||||
import '../keys.dart' as keys;
|
||||
import '../state.dart';
|
||||
import 'add_account_page.dart';
|
||||
import 'manage_password_dialog.dart';
|
||||
import 'reset_dialog.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
Widget oathBuildActions(
|
||||
BuildContext context,
|
||||
@ -59,38 +57,14 @@ Widget oathBuildActions(
|
||||
icon: const Icon(Icons.person_add_alt_1_outlined),
|
||||
onTap: used != null && (capacity == null || capacity > used)
|
||||
? (context) async {
|
||||
final credentials = ref.read(credentialsProvider);
|
||||
final withContext = ref.read(withContextProvider);
|
||||
Navigator.of(context).pop();
|
||||
if (isAndroid) {
|
||||
final qrScanner = ref.read(qrScannerProvider);
|
||||
if (qrScanner != null) {
|
||||
final qrData = await qrScanner.scanQr();
|
||||
if (qrData != null) {
|
||||
await withContext((context) => handleUri(
|
||||
context,
|
||||
credentials,
|
||||
qrData,
|
||||
devicePath,
|
||||
oathState,
|
||||
l10n,
|
||||
));
|
||||
return;
|
||||
await AndroidQrScanner.handleScannedData(
|
||||
qrData, ref, l10n);
|
||||
}
|
||||
}
|
||||
await withContext((context) => showBlurDialog(
|
||||
context: context,
|
||||
routeSettings:
|
||||
const RouteSettings(name: 'oath_add_account'),
|
||||
builder: (context) {
|
||||
return OathAddAccountPage(
|
||||
devicePath,
|
||||
oathState,
|
||||
credentials: credentials,
|
||||
credentialData: null,
|
||||
);
|
||||
},
|
||||
));
|
||||
} else {
|
||||
await showBlurDialog(
|
||||
context: context,
|
||||
|
Loading…
Reference in New Issue
Block a user