feat: drag and drop
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:file_transfer/functions/share_file.dart';
|
||||
import 'package:file_transfer/models/shared_file.dart';
|
||||
import 'package:file_transfer/routes.dart';
|
||||
import 'package:file_transfer/utils/platform_helper.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dropzone/flutter_dropzone.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
|
||||
@@ -11,10 +15,64 @@ class HomePageController extends GetxController {
|
||||
final _sharedFile = Rxn<SharedFile>();
|
||||
final _isUploading = false.obs;
|
||||
final _error = RxnString();
|
||||
final _isDragging = false.obs;
|
||||
final dropzoneController = Rxn<DropzoneViewController>();
|
||||
|
||||
SharedFile? get sharedFile => _sharedFile.value;
|
||||
bool get isUploading => _isUploading.value;
|
||||
String? get error => _error.value;
|
||||
bool get isDragging => _isDragging.value;
|
||||
bool get isWeb => kIsWeb;
|
||||
|
||||
Future<void> handleDroppedFile(dynamic file) async {
|
||||
_isDragging.value = false;
|
||||
_isUploading.value = true;
|
||||
_error.value = null;
|
||||
|
||||
try {
|
||||
String filename;
|
||||
Uint8List bytes;
|
||||
|
||||
if (kIsWeb && file is DropzoneFileInterface) {
|
||||
// Web: use dropzone controller
|
||||
filename = await dropzoneController.value!.getFilename(file);
|
||||
bytes = await dropzoneController.value!.getFileData(file);
|
||||
} else if (file is String) {
|
||||
// Desktop: file path
|
||||
final filePath = file;
|
||||
final fileObj = File(filePath);
|
||||
filename = fileObj.uri.pathSegments.last;
|
||||
bytes = await fileObj.readAsBytes();
|
||||
} else {
|
||||
throw Exception('Unsupported file type: ${file.runtimeType}');
|
||||
}
|
||||
|
||||
final mimeType = lookupMimeType(
|
||||
filename,
|
||||
headerBytes: bytes.take(8).toList(),
|
||||
);
|
||||
|
||||
final sharedFile = await shareFile(
|
||||
bytes: bytes,
|
||||
contentType: mimeType,
|
||||
filename: filename,
|
||||
);
|
||||
|
||||
_sharedFile.value = sharedFile;
|
||||
_isUploading.value = false;
|
||||
} catch (e) {
|
||||
_error.value = e.toString();
|
||||
_isUploading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void onDragHover() {
|
||||
_isDragging.value = true;
|
||||
}
|
||||
|
||||
void onDragLeave() {
|
||||
_isDragging.value = false;
|
||||
}
|
||||
|
||||
Future<void> pickAndShareFile() async {
|
||||
_isUploading.value = true;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:file_transfer/controllers/home_controller.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dropzone/flutter_dropzone.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class UploadView extends GetView<HomePageController> {
|
||||
@@ -12,63 +15,192 @@ class UploadView extends GetView<HomePageController> {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('File Transfer')),
|
||||
body: SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(horizontal: isSmallScreen ? 16 : 24),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: MediaQuery.of(context).padding.top),
|
||||
Icon(
|
||||
Icons.upload_file_rounded,
|
||||
size: isSmallScreen ? 64 : 80,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
SizedBox(height: isSmallScreen ? 20 : 24),
|
||||
Text(
|
||||
'Select a file to share',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: isSmallScreen ? 16 : 18,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(height: isSmallScreen ? 24 : 32),
|
||||
Obx(
|
||||
() => FilledButton.icon(
|
||||
onPressed: controller.isUploading
|
||||
? null
|
||||
: controller.pickAndShareFile,
|
||||
icon: controller.isUploading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.upload_file),
|
||||
label: Text(
|
||||
controller.isUploading ? 'Uploading...' : 'Select File',
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: isSmallScreen ? 10 : 12),
|
||||
OutlinedButton.icon(
|
||||
onPressed: controller.pasteAndOpenLink,
|
||||
icon: const Icon(Icons.paste),
|
||||
label: const Text('Paste Share Link'),
|
||||
),
|
||||
Obx(() {
|
||||
if (controller.error != null) {
|
||||
return _buildErrorBox(context, isSmallScreen, colorScheme);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}),
|
||||
],
|
||||
body: kIsWeb
|
||||
? _buildWebDropzone(context, isSmallScreen, colorScheme)
|
||||
: _buildDesktopDropzone(context, isSmallScreen, colorScheme),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWebDropzone(
|
||||
BuildContext context,
|
||||
bool isSmallScreen,
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
return Stack(
|
||||
children: [
|
||||
Obx(
|
||||
() => IgnorePointer(
|
||||
ignoring: !controller.isDragging,
|
||||
child: Container(
|
||||
color: controller.isDragging
|
||||
? colorScheme.primary.withValues(alpha: 0.1)
|
||||
: Colors.transparent,
|
||||
child: Center(
|
||||
child: controller.isDragging
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.file_download_rounded,
|
||||
size: 80,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Drop file to upload',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DropzoneView(
|
||||
operation: DragOperation.copy,
|
||||
cursor: CursorType.grab,
|
||||
onCreated: (DropzoneViewController ctrl) {
|
||||
controller.dropzoneController.value = ctrl;
|
||||
},
|
||||
onHover: () {
|
||||
controller.onDragHover();
|
||||
},
|
||||
onLeave: () {
|
||||
controller.onDragLeave();
|
||||
},
|
||||
onDropFile: (DropzoneFileInterface file) {
|
||||
controller.handleDroppedFile(file);
|
||||
},
|
||||
),
|
||||
_buildContent(context, isSmallScreen, colorScheme),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDesktopDropzone(
|
||||
BuildContext context,
|
||||
bool isSmallScreen,
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
return DropTarget(
|
||||
onDragDone: (event) {
|
||||
if (event.files.isNotEmpty) {
|
||||
controller.handleDroppedFile(event.files.first.path);
|
||||
}
|
||||
},
|
||||
onDragEntered: (event) {
|
||||
controller.onDragHover();
|
||||
},
|
||||
onDragExited: (event) {
|
||||
controller.onDragLeave();
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
Obx(
|
||||
() => IgnorePointer(
|
||||
ignoring: !controller.isDragging,
|
||||
child: Container(
|
||||
color: controller.isDragging
|
||||
? colorScheme.primary.withValues(alpha: 0.1)
|
||||
: Colors.transparent,
|
||||
child: Center(
|
||||
child: controller.isDragging
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.file_download_rounded,
|
||||
size: 80,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Drop file to upload',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildContent(context, isSmallScreen, colorScheme),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(
|
||||
BuildContext context,
|
||||
bool isSmallScreen,
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
return SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(horizontal: isSmallScreen ? 16 : 24),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(height: MediaQuery.of(context).padding.top),
|
||||
Icon(
|
||||
Icons.upload_file_rounded,
|
||||
size: isSmallScreen ? 64 : 80,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
SizedBox(height: isSmallScreen ? 20 : 24),
|
||||
Text(
|
||||
'Drag & drop a file or select to share',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: isSmallScreen ? 16 : 18,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
SizedBox(height: isSmallScreen ? 24 : 32),
|
||||
Obx(
|
||||
() => FilledButton.icon(
|
||||
onPressed: controller.isUploading
|
||||
? null
|
||||
: controller.pickAndShareFile,
|
||||
icon: controller.isUploading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.upload_file),
|
||||
label: Text(
|
||||
controller.isUploading ? 'Uploading...' : 'Select File',
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: isSmallScreen ? 10 : 12),
|
||||
OutlinedButton.icon(
|
||||
onPressed: controller.pasteAndOpenLink,
|
||||
icon: const Icon(Icons.paste),
|
||||
label: const Text('Paste Share Link'),
|
||||
),
|
||||
Obx(() {
|
||||
if (controller.error != null) {
|
||||
return _buildErrorBox(context, isSmallScreen, colorScheme);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,11 +6,15 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <desktop_drop/desktop_drop_plugin.h>
|
||||
#include <file_saver/file_saver_plugin.h>
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
|
||||
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
|
||||
g_autoptr(FlPluginRegistrar) file_saver_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
|
||||
file_saver_plugin_register_with_registrar(file_saver_registrar);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_drop
|
||||
file_saver
|
||||
flutter_secure_storage_linux
|
||||
url_launcher_linux
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import desktop_drop
|
||||
import file_picker
|
||||
import file_saver
|
||||
import flutter_secure_storage_darwin
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
|
||||
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
|
||||
|
||||
@@ -121,6 +121,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.12"
|
||||
desktop_drop:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: desktop_drop
|
||||
sha256: e70b46b2d61f1af7a81a40d1f79b43c28a879e30a4ef31e87e9c27bea4d784e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
dio:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -206,6 +214,30 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_dropzone:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_dropzone
|
||||
sha256: d0917c4e366ebb3a98c9c02faac79223d119af5e236967c70e7e4002cdfb20d3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.1"
|
||||
flutter_dropzone_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_dropzone_platform_interface
|
||||
sha256: "96d2c51c86063ba150551c3b40fd26c5a18785bee071c0c751502d28a545df3b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
flutter_dropzone_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_dropzone_web
|
||||
sha256: "03b216f9443add6ad1d075ab513a354a47edf3d1b87d830a8ed314f0d3a2beac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -666,6 +698,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
universal_platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: universal_platform
|
||||
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
unorm_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -9,10 +9,12 @@ environment:
|
||||
dependencies:
|
||||
crypto: ^3.0.7
|
||||
cryptography: ^2.9.0
|
||||
desktop_drop: ^0.7.0
|
||||
file_picker: ^10.3.10
|
||||
file_saver: ^0.3.1
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_dropzone: ^4.2.1
|
||||
get: ^4.7.3
|
||||
mime: ^2.0.0
|
||||
ndk: ^0.7.1-dev.19
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <desktop_drop/desktop_drop_plugin.h>
|
||||
#include <file_saver/file_saver_plugin.h>
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
DesktopDropPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
|
||||
FileSaverPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSaverPlugin"));
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_drop
|
||||
file_saver
|
||||
flutter_secure_storage_windows
|
||||
url_launcher_windows
|
||||
|
||||
Reference in New Issue
Block a user