mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-24 18:52:55 +03:00
Merge pull request #69 from Yubico/drag-n-drop
Draft for drag n dropping files
This commit is contained in:
commit
f6aeb0b9f0
@ -125,7 +125,7 @@ final menuActionsProvider = Provider.autoDispose<List<MenuAction>>((ref) {
|
||||
});
|
||||
|
||||
abstract class QrScanner {
|
||||
Future<String> scanQr();
|
||||
Future<String> scanQr([String? imageData]);
|
||||
}
|
||||
|
||||
final qrScannerProvider = Provider<QrScanner?>(
|
||||
|
@ -9,8 +9,8 @@ class RpcQrScanner implements QrScanner {
|
||||
RpcQrScanner(this._rpc);
|
||||
|
||||
@override
|
||||
Future<String> scanQr() async {
|
||||
final result = await _rpc.command('qr', []);
|
||||
Future<String> scanQr([String? imageData]) async {
|
||||
final result = await _rpc.command('qr', [], params: {'image': imageData});
|
||||
return result['result'];
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'dart:math';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -7,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../app/state.dart';
|
||||
import '../../app/models.dart';
|
||||
import '../../app/views/responsive_dialog.dart';
|
||||
import '../../widgets/file_drop_target.dart';
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'utils.dart';
|
||||
@ -109,6 +111,24 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
|
||||
return ResponsiveDialog(
|
||||
title: const Text('Add account'),
|
||||
child: FileDropTarget(
|
||||
onFileDropped: (fileData) async {
|
||||
if (qrScanner != null) {
|
||||
final b64Image = base64Encode(fileData);
|
||||
final otpauth = await qrScanner.scanQr(b64Image);
|
||||
final data = CredentialData.fromUri(Uri.parse(otpauth));
|
||||
setState(() {
|
||||
_issuerController.text = data.issuer ?? '';
|
||||
_accountController.text = data.name;
|
||||
_secretController.text = data.secret;
|
||||
_oathType = data.oathType;
|
||||
_hashAlgorithm = data.hashAlgorithm;
|
||||
_periodController.text = '${data.period}';
|
||||
_digits = data.digits;
|
||||
_qrState = _QrScanState.success;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -248,8 +268,8 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
Chip(
|
||||
label: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<int>(
|
||||
value:
|
||||
int.tryParse(_periodController.text) ?? defaultPeriod,
|
||||
value: int.tryParse(_periodController.text) ??
|
||||
defaultPeriod,
|
||||
isDense: true,
|
||||
underline: null,
|
||||
items: [20, 30, 45, 60]
|
||||
@ -300,6 +320,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: isValid
|
||||
|
59
lib/widgets/file_drop_target.dart
Executable file
59
lib/widgets/file_drop_target.dart
Executable file
@ -0,0 +1,59 @@
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FileDropTarget extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Function(List<int> filedata) onFileDropped;
|
||||
final Widget? overlay;
|
||||
|
||||
const FileDropTarget({
|
||||
Key? key,
|
||||
required this.child,
|
||||
required this.onFileDropped,
|
||||
this.overlay,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _FileDropTargetState();
|
||||
}
|
||||
|
||||
class _FileDropTargetState extends State<FileDropTarget> {
|
||||
bool _hovering = false;
|
||||
|
||||
Widget _buildDefaultOverlay() => Positioned.fill(
|
||||
child: Container(
|
||||
color: Colors.blue.withOpacity(0.4),
|
||||
child: Icon(
|
||||
Icons.upload_file,
|
||||
size: 200,
|
||||
color: Colors.black.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => DropTarget(
|
||||
onDragEntered: (_) {
|
||||
setState(() {
|
||||
_hovering = true;
|
||||
});
|
||||
},
|
||||
onDragExited: (_) {
|
||||
setState(() {
|
||||
_hovering = false;
|
||||
});
|
||||
},
|
||||
onDragDone: (details) async {
|
||||
for (final file in details.files) {
|
||||
widget.onFileDropped(await file.readAsBytes());
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
widget.child,
|
||||
if (_hovering) widget.overlay ?? _buildDefaultOverlay(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
@ -42,6 +42,7 @@ dependencies:
|
||||
json_annotation: ^4.4.0
|
||||
freezed_annotation: ^1.0.0
|
||||
window_manager: ^0.2.0
|
||||
desktop_drop: ^0.3.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -110,7 +110,7 @@ class RootNode(RpcNode):
|
||||
|
||||
@action(closes_child=False)
|
||||
def qr(self, params, event, signal):
|
||||
return dict(result=scan_qr())
|
||||
return dict(result=scan_qr(params.get("image")))
|
||||
|
||||
|
||||
def _id_from_fingerprint(fp):
|
||||
|
@ -1,14 +1,20 @@
|
||||
import mss
|
||||
import zxingcpp
|
||||
import base64
|
||||
import io
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def scan_qr():
|
||||
def scan_qr(image_data = None):
|
||||
if (image_data):
|
||||
msg = base64.b64decode(image_data)
|
||||
buf = io.BytesIO(msg)
|
||||
img = Image.open(buf)
|
||||
else:
|
||||
with mss.mss() as sct:
|
||||
monitor = sct.monitors[0] # 0 is the special "all monitors" value.
|
||||
sct_img = sct.grab(monitor) # mss format
|
||||
img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
|
||||
|
||||
result = zxingcpp.read_barcode(img)
|
||||
if result.valid:
|
||||
return result.text
|
||||
|
Loading…
Reference in New Issue
Block a user