Merge pull request #69 from Yubico/drag-n-drop

Draft for drag n dropping files
This commit is contained in:
Dennis Fokin 2022-03-29 15:08:09 +02:00 committed by GitHub
commit f6aeb0b9f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 262 additions and 175 deletions

View File

@ -125,7 +125,7 @@ final menuActionsProvider = Provider.autoDispose<List<MenuAction>>((ref) {
}); });
abstract class QrScanner { abstract class QrScanner {
Future<String> scanQr(); Future<String> scanQr([String? imageData]);
} }
final qrScannerProvider = Provider<QrScanner?>( final qrScannerProvider = Provider<QrScanner?>(

View File

@ -9,8 +9,8 @@ class RpcQrScanner implements QrScanner {
RpcQrScanner(this._rpc); RpcQrScanner(this._rpc);
@override @override
Future<String> scanQr() async { Future<String> scanQr([String? imageData]) async {
final result = await _rpc.command('qr', []); final result = await _rpc.command('qr', [], params: {'image': imageData});
return result['result']; return result['result'];
} }
} }

View File

@ -1,4 +1,5 @@
import 'dart:math'; import 'dart:math';
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -7,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../app/state.dart'; import '../../app/state.dart';
import '../../app/models.dart'; import '../../app/models.dart';
import '../../app/views/responsive_dialog.dart'; import '../../app/views/responsive_dialog.dart';
import '../../widgets/file_drop_target.dart';
import '../models.dart'; import '../models.dart';
import '../state.dart'; import '../state.dart';
import 'utils.dart'; import 'utils.dart';
@ -109,6 +111,24 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
return ResponsiveDialog( return ResponsiveDialog(
title: const Text('Add account'), 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( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -248,8 +268,8 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
Chip( Chip(
label: DropdownButtonHideUnderline( label: DropdownButtonHideUnderline(
child: DropdownButton<int>( child: DropdownButton<int>(
value: value: int.tryParse(_periodController.text) ??
int.tryParse(_periodController.text) ?? defaultPeriod, defaultPeriod,
isDense: true, isDense: true,
underline: null, underline: null,
items: [20, 30, 45, 60] items: [20, 30, 45, 60]
@ -300,6 +320,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
)) ))
.toList(), .toList(),
), ),
),
actions: [ actions: [
TextButton( TextButton(
onPressed: isValid onPressed: isValid

View 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(),
],
),
);
}

View File

@ -42,6 +42,7 @@ dependencies:
json_annotation: ^4.4.0 json_annotation: ^4.4.0
freezed_annotation: ^1.0.0 freezed_annotation: ^1.0.0
window_manager: ^0.2.0 window_manager: ^0.2.0
desktop_drop: ^0.3.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -110,7 +110,7 @@ class RootNode(RpcNode):
@action(closes_child=False) @action(closes_child=False)
def qr(self, params, event, signal): def qr(self, params, event, signal):
return dict(result=scan_qr()) return dict(result=scan_qr(params.get("image")))
def _id_from_fingerprint(fp): def _id_from_fingerprint(fp):

View File

@ -1,14 +1,20 @@
import mss import mss
import zxingcpp import zxingcpp
import base64
import io
from PIL import Image 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: with mss.mss() as sct:
monitor = sct.monitors[0] # 0 is the special "all monitors" value. monitor = sct.monitors[0] # 0 is the special "all monitors" value.
sct_img = sct.grab(monitor) # mss format sct_img = sct.grab(monitor) # mss format
img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX") img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
result = zxingcpp.read_barcode(img) result = zxingcpp.read_barcode(img)
if result.valid: if result.valid:
return result.text return result.text