Compare commits

...

2 Commits

Author SHA1 Message Date
7957e4894a File list drag and drop 2025-11-15 13:22:05 +08:00
f94f80c375 👽 Update the indexed file api calls 2025-11-15 03:06:41 +08:00
14 changed files with 119 additions and 54 deletions

View File

@@ -1,13 +1,12 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/file.dart'; import 'package:island/models/file.dart';
import 'package:island/models/folder.dart';
part 'file_list_item.freezed.dart'; part 'file_list_item.freezed.dart';
@freezed @freezed
sealed class FileListItem with _$FileListItem { sealed class FileListItem with _$FileListItem {
const factory FileListItem.file(SnCloudFileIndex fileIndex) = FileItem; const factory FileListItem.file(SnCloudFileIndex fileIndex) = FileItem;
const factory FileListItem.folder(SnCloudFolder folder) = FolderItem; const factory FileListItem.folder(String folderName) = FolderItem;
const factory FileListItem.unindexedFile(SnCloudFile file) = const factory FileListItem.unindexedFile(SnCloudFile file) =
UnindexedFileItem; UnindexedFileItem;
} }

View File

@@ -122,11 +122,11 @@ return unindexedFile(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( SnCloudFileIndex fileIndex)? file,TResult Function( SnCloudFolder folder)? folder,TResult Function( SnCloudFile file)? unindexedFile,required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( SnCloudFileIndex fileIndex)? file,TResult Function( String folderName)? folder,TResult Function( SnCloudFile file)? unindexedFile,required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case FileItem() when file != null: case FileItem() when file != null:
return file(_that.fileIndex);case FolderItem() when folder != null: return file(_that.fileIndex);case FolderItem() when folder != null:
return folder(_that.folder);case UnindexedFileItem() when unindexedFile != null: return folder(_that.folderName);case UnindexedFileItem() when unindexedFile != null:
return unindexedFile(_that.file);case _: return unindexedFile(_that.file);case _:
return orElse(); return orElse();
@@ -145,11 +145,11 @@ return unindexedFile(_that.file);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( SnCloudFileIndex fileIndex) file,required TResult Function( SnCloudFolder folder) folder,required TResult Function( SnCloudFile file) unindexedFile,}) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( SnCloudFileIndex fileIndex) file,required TResult Function( String folderName) folder,required TResult Function( SnCloudFile file) unindexedFile,}) {final _that = this;
switch (_that) { switch (_that) {
case FileItem(): case FileItem():
return file(_that.fileIndex);case FolderItem(): return file(_that.fileIndex);case FolderItem():
return folder(_that.folder);case UnindexedFileItem(): return folder(_that.folderName);case UnindexedFileItem():
return unindexedFile(_that.file);} return unindexedFile(_that.file);}
} }
/// A variant of `when` that fallback to returning `null` /// A variant of `when` that fallback to returning `null`
@@ -164,11 +164,11 @@ return unindexedFile(_that.file);}
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( SnCloudFileIndex fileIndex)? file,TResult? Function( SnCloudFolder folder)? folder,TResult? Function( SnCloudFile file)? unindexedFile,}) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( SnCloudFileIndex fileIndex)? file,TResult? Function( String folderName)? folder,TResult? Function( SnCloudFile file)? unindexedFile,}) {final _that = this;
switch (_that) { switch (_that) {
case FileItem() when file != null: case FileItem() when file != null:
return file(_that.fileIndex);case FolderItem() when folder != null: return file(_that.fileIndex);case FolderItem() when folder != null:
return folder(_that.folder);case UnindexedFileItem() when unindexedFile != null: return folder(_that.folderName);case UnindexedFileItem() when unindexedFile != null:
return unindexedFile(_that.file);case _: return unindexedFile(_that.file);case _:
return null; return null;
@@ -256,10 +256,10 @@ $SnCloudFileIndexCopyWith<$Res> get fileIndex {
class FolderItem implements FileListItem { class FolderItem implements FileListItem {
const FolderItem(this.folder); const FolderItem(this.folderName);
final SnCloudFolder folder; final String folderName;
/// Create a copy of FileListItem /// Create a copy of FileListItem
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -271,16 +271,16 @@ $FolderItemCopyWith<FolderItem> get copyWith => _$FolderItemCopyWithImpl<FolderI
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is FolderItem&&(identical(other.folder, folder) || other.folder == folder)); return identical(this, other) || (other.runtimeType == runtimeType&&other is FolderItem&&(identical(other.folderName, folderName) || other.folderName == folderName));
} }
@override @override
int get hashCode => Object.hash(runtimeType,folder); int get hashCode => Object.hash(runtimeType,folderName);
@override @override
String toString() { String toString() {
return 'FileListItem.folder(folder: $folder)'; return 'FileListItem.folder(folderName: $folderName)';
} }
@@ -291,11 +291,11 @@ abstract mixin class $FolderItemCopyWith<$Res> implements $FileListItemCopyWith<
factory $FolderItemCopyWith(FolderItem value, $Res Function(FolderItem) _then) = _$FolderItemCopyWithImpl; factory $FolderItemCopyWith(FolderItem value, $Res Function(FolderItem) _then) = _$FolderItemCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
SnCloudFolder folder String folderName
}); });
$SnCloudFolderCopyWith<$Res> get folder;
} }
/// @nodoc /// @nodoc
@@ -308,23 +308,14 @@ class _$FolderItemCopyWithImpl<$Res>
/// Create a copy of FileListItem /// Create a copy of FileListItem
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') $Res call({Object? folder = null,}) { @pragma('vm:prefer-inline') $Res call({Object? folderName = null,}) {
return _then(FolderItem( return _then(FolderItem(
null == folder ? _self.folder : folder // ignore: cast_nullable_to_non_nullable null == folderName ? _self.folderName : folderName // ignore: cast_nullable_to_non_nullable
as SnCloudFolder, as String,
)); ));
} }
/// Create a copy of FileListItem
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnCloudFolderCopyWith<$Res> get folder {
return $SnCloudFolderCopyWith<$Res>(_self.folder, (value) {
return _then(_self.copyWith(folder: value));
});
}
} }
/// @nodoc /// @nodoc

View File

@@ -1,7 +1,6 @@
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/file.dart'; import 'package:island/models/file.dart';
import 'package:island/models/file_list_item.dart'; import 'package:island/models/file_list_item.dart';
import 'package:island/models/folder.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
@@ -32,17 +31,15 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
queryParameters: {'path': _currentPath}, queryParameters: {'path': _currentPath},
); );
final List<SnCloudFolder> folders = final List<String> folders =
(response.data['folders'] as List) (response.data['folders'] as List).map((e) => e as String).toList();
.map((e) => SnCloudFolder.fromJson(e as Map<String, dynamic>))
.toList();
final List<SnCloudFileIndex> files = final List<SnCloudFileIndex> files =
(response.data['files'] as List) (response.data['files'] as List)
.map((e) => SnCloudFileIndex.fromJson(e as Map<String, dynamic>)) .map((e) => SnCloudFileIndex.fromJson(e as Map<String, dynamic>))
.toList(); .toList();
final List<FileListItem> items = [ final List<FileListItem> items = [
...folders.map((folder) => FileListItem.folder(folder)), ...folders.map((folderName) => FileListItem.folder(folderName)),
...files.map((file) => FileListItem.file(file)), ...files.map((file) => FileListItem.file(file)),
]; ];

View File

@@ -45,7 +45,7 @@ final billingQuotaProvider =
// ignore: unused_element // ignore: unused_element
typedef BillingQuotaRef = AutoDisposeFutureProviderRef<Map<String, dynamic>?>; typedef BillingQuotaRef = AutoDisposeFutureProviderRef<Map<String, dynamic>?>;
String _$cloudFileListNotifierHash() => String _$cloudFileListNotifierHash() =>
r'5b919f2212ce64c567b9f31912ed18fc4e4bc87d'; r'5f2f80357cb31ac6473df5ac2101f9a462004f81';
/// See also [CloudFileListNotifier]. /// See also [CloudFileListNotifier].
@ProviderFor(CloudFileListNotifier) @ProviderFor(CloudFileListNotifier)

View File

@@ -99,9 +99,7 @@ class FileListScreen extends HookConsumerWidget {
completer.future completer.future
.then((uploadedFile) { .then((uploadedFile) {
if (uploadedFile != null) { if (uploadedFile != null) {
// Refresh the file list after successful upload
ref.invalidate(cloudFileListNotifierProvider); ref.invalidate(cloudFileListNotifierProvider);
showSnackBar('File uploaded successfully');
} }
}) })
.catchError((error) { .catchError((error) {

View File

@@ -1,3 +1,4 @@
import 'package:desktop_drop/desktop_drop.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
@@ -5,8 +6,10 @@ import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/file_list_item.dart'; import 'package:island/models/file_list_item.dart';
import 'package:island/models/file.dart';
import 'package:island/pods/file_list.dart'; import 'package:island/pods/file_list.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/services/file_uploader.dart';
import 'package:island/utils/format.dart'; import 'package:island/utils/format.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
@@ -36,6 +39,8 @@ class FileListView extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final dragging = useState(false);
useEffect(() { useEffect(() {
if (mode.value == FileListMode.normal) { if (mode.value == FileListMode.normal) {
final notifier = ref.read(cloudFileListNotifierProvider.notifier); final notifier = ref.read(cloudFileListNotifierProvider.notifier);
@@ -267,7 +272,7 @@ class FileListView extends HookConsumerWidget {
), ),
), ),
title: Text( title: Text(
folderItem.folder.name, folderItem.folderName,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -276,8 +281,8 @@ class FileListView extends HookConsumerWidget {
// Navigate to folder // Navigate to folder
final newPath = final newPath =
currentPath.value == '/' currentPath.value == '/'
? '/${folderItem.folder.name}' ? '/${folderItem.folderName}'
: '${currentPath.value}/${folderItem.folder.name}'; : '${currentPath.value}/${folderItem.folderName}';
currentPath.value = newPath; currentPath.value = newPath;
}, },
), ),
@@ -291,7 +296,52 @@ class FileListView extends HookConsumerWidget {
), ),
}; };
return Column( return DropTarget(
onDragDone: (details) async {
dragging.value = false;
// Handle file upload
for (final file in details.files) {
final universalFile = UniversalFile(
data: file,
type: UniversalFileType.file,
displayName: file.name,
);
final completer = FileUploader.createCloudFile(
fileData: universalFile,
ref: ref,
path: currentPath.value,
onProgress: (progress, _) {
// Progress is handled by the upload tasks system
if (progress != null) {
debugPrint('Upload progress: ${(progress * 100).toInt()}%');
}
},
);
completer.future
.then((uploadedFile) {
if (uploadedFile != null) {
ref.invalidate(cloudFileListNotifierProvider);
}
})
.catchError((error) {
showSnackBar('Failed to upload file: $error');
});
}
},
onDragEntered: (details) {
dragging.value = true;
},
onDragExited: (details) {
dragging.value = false;
},
child: Container(
color:
dragging.value
? Theme.of(context).primaryColor.withOpacity(0.1)
: null,
child: Column(
children: [ children: [
const Gap(8), const Gap(8),
_buildPathNavigation(ref, currentPath), _buildPathNavigation(ref, currentPath),
@@ -300,12 +350,16 @@ class FileListView extends HookConsumerWidget {
child: CustomScrollView( child: CustomScrollView(
slivers: [ slivers: [
bodyWidget, bodyWidget,
if (mode.value == FileListMode.normal && currentPath.value == '/') const SliverGap(12),
if (mode.value == FileListMode.normal &&
currentPath.value == '/')
SliverToBoxAdapter(child: _buildUnindexedFilesEntry(ref)), SliverToBoxAdapter(child: _buildUnindexedFilesEntry(ref)),
], ],
), ),
), ),
], ],
),
),
); );
} }

View File

@@ -6,6 +6,7 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <desktop_drop/desktop_drop_plugin.h>
#include <file_saver/file_saver_plugin.h> #include <file_saver/file_saver_plugin.h>
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <flutter_platform_alert/flutter_platform_alert_plugin.h> #include <flutter_platform_alert/flutter_platform_alert_plugin.h>
@@ -29,6 +30,9 @@
#include <window_manager/window_manager_plugin.h> #include <window_manager/window_manager_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { 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 = g_autoptr(FlPluginRegistrar) file_saver_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
file_saver_plugin_register_with_registrar(file_saver_registrar); file_saver_plugin_register_with_registrar(file_saver_registrar);

View File

@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
desktop_drop
file_saver file_saver
file_selector_linux file_selector_linux
flutter_platform_alert flutter_platform_alert

View File

@@ -6,6 +6,7 @@ import FlutterMacOS
import Foundation import Foundation
import connectivity_plus import connectivity_plus
import desktop_drop
import device_info_plus import device_info_plus
import file_picker import file_picker
import file_saver import file_saver
@@ -48,6 +49,7 @@ import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))

View File

@@ -3,6 +3,8 @@ PODS:
- FlutterMacOS - FlutterMacOS
- croppy (0.0.1): - croppy (0.0.1):
- FlutterMacOS - FlutterMacOS
- desktop_drop (0.0.1):
- FlutterMacOS
- device_info_plus (0.0.1): - device_info_plus (0.0.1):
- FlutterMacOS - FlutterMacOS
- file_picker (0.0.1): - file_picker (0.0.1):
@@ -258,6 +260,7 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`) - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
- croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`) - croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`)
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
- file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`) - file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`)
@@ -327,6 +330,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
croppy: croppy:
:path: Flutter/ephemeral/.symlinks/plugins/croppy/macos :path: Flutter/ephemeral/.symlinks/plugins/croppy/macos
desktop_drop:
:path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos
device_info_plus: device_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
file_picker: file_picker:
@@ -411,6 +416,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
connectivity_plus: 4adf20a405e25b42b9c9f87feff8f4b6fde18a4e connectivity_plus: 4adf20a405e25b42b9c9f87feff8f4b6fde18a4e
croppy: d9bfc8c02f3cd1851f669a421df298a474b78f43 croppy: d9bfc8c02f3cd1851f669a421df298a474b78f43
desktop_drop: 10a3e6a7fa9dbe350541f2574092fecfa345a07b
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f

View File

@@ -393,6 +393,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.11" version: "0.7.11"
desktop_drop:
dependency: "direct main"
description:
name: desktop_drop
sha256: e70b46b2d61f1af7a81a40d1f79b43c28a879e30a4ef31e87e9c27bea4d784e8
url: "https://pub.dev"
source: hosted
version: "0.7.0"
device_info_plus: device_info_plus:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -166,6 +166,7 @@ dependencies:
flutter_expandable_fab: ^2.5.2 flutter_expandable_fab: ^2.5.2
event_bus: ^2.0.1 event_bus: ^2.0.1
convert: ^3.1.2 convert: ^3.1.2
desktop_drop: ^0.7.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -8,6 +8,7 @@
#include <connectivity_plus/connectivity_plus_windows_plugin.h> #include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <dart_ipc/dart_ipc_plugin_c_api.h> #include <dart_ipc/dart_ipc_plugin_c_api.h>
#include <desktop_drop/desktop_drop_plugin.h>
#include <file_saver/file_saver_plugin.h> #include <file_saver/file_saver_plugin.h>
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <firebase_core/firebase_core_plugin_c_api.h> #include <firebase_core/firebase_core_plugin_c_api.h>
@@ -42,6 +43,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
DartIpcPluginCApiRegisterWithRegistrar( DartIpcPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DartIpcPluginCApi")); registry->GetRegistrarForPlugin("DartIpcPluginCApi"));
DesktopDropPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
FileSaverPluginRegisterWithRegistrar( FileSaverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSaverPlugin")); registry->GetRegistrarForPlugin("FileSaverPlugin"));
FileSelectorWindowsRegisterWithRegistrar( FileSelectorWindowsRegisterWithRegistrar(

View File

@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus connectivity_plus
dart_ipc dart_ipc
desktop_drop
file_saver file_saver
file_selector_windows file_selector_windows
firebase_core firebase_core