mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-25 03:03:50 +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 {
|
abstract class QrScanner {
|
||||||
Future<String> scanQr();
|
Future<String> scanQr([String? imageData]);
|
||||||
}
|
}
|
||||||
|
|
||||||
final qrScannerProvider = Provider<QrScanner?>(
|
final qrScannerProvider = Provider<QrScanner?>(
|
||||||
|
@ -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'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
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
|
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:
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user