From 95c082b62928c4ff45006ec09c3dffaad012465f Mon Sep 17 00:00:00 2001 From: poppingmoon <63451158+poppingmoon@users.noreply.github.com> Date: Sat, 6 Apr 2024 01:27:36 +0000 Subject: [PATCH] feat: make it possible to filter files by type (#55) Add `type` property to `FilePickerSheet`, which controlls pickable file type. For iOS, pass `FileType.media` to `FilePicker.platform.pickFiles()` by default. This is because if `type` is `FileType.any`, iOS opens "Files" app, which is inconvenient for basic usage. With `FileType.media`, it opens "Photos" app, where users can find their images and videos. Close #54 --- lib/view/page/drive_page.dart | 96 ++++++++++++++---------- lib/view/page/image_page.dart | 6 +- lib/view/page/settings/profile_page.dart | 6 +- lib/view/widget/drive_create_sheet.dart | 4 + lib/view/widget/file_picker_sheet.dart | 13 +++- 5 files changed, 83 insertions(+), 42 deletions(-) diff --git a/lib/view/page/drive_page.dart b/lib/view/page/drive_page.dart index cf07acd1..72cf17b7 100644 --- a/lib/view/page/drive_page.dart +++ b/lib/view/page/drive_page.dart @@ -1,4 +1,5 @@ import 'package:collection/collection.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; @@ -27,12 +28,14 @@ class DrivePage extends HookConsumerWidget { this.selectFile = false, this.selectFiles = false, this.selectFolder = false, + this.type = FileType.any, }); final Account account; final bool selectFile; final bool selectFiles; final bool selectFolder; + final FileType type; static const itemMaxCrossAxisExtent = 400.0; @@ -301,45 +304,62 @@ class DrivePage extends HookConsumerWidget { case AsyncValue( valueOrNull: PaginationState(items: final files) )) - SliverGrid.builder( - gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: itemMaxCrossAxisExtent, - ), - itemCount: files.length, - itemBuilder: (context, index) { - final file = files[index]; - final isSelected = - selectedFiles.any((e) => e.id == file.id); - return DriveFileWidget( - account: account, - file: file, - isSelected: isSelected, - onTap: () { - if (selectFile) { - context.pop(file); - } else if (isSelecting) { - if (isSelected) { - ref - .read( - selectedDriveFilesNotifierProvider.notifier, - ) - .remove(file.id); - } else { - ref - .read( - selectedDriveFilesNotifierProvider.notifier, - ) - .add(file); - } - } else { - context.push( - '/$account/drive/file/${folderId != null ? '$folderId/' : ''}${file.id}', - ); - } + Builder( + builder: (context) { + final filtered = files.where( + (file) => switch (type) { + FileType.media => + file.type.startsWith(RegExp('image|video')), + FileType.image => file.type.startsWith('image'), + FileType.video => file.type.startsWith('video'), + FileType.audio => file.type.startsWith('audio'), + FileType.any || FileType.custom => true, + }, + ); + return SliverGrid.builder( + gridDelegate: + const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: itemMaxCrossAxisExtent, + ), + itemCount: filtered.length, + itemBuilder: (context, index) { + final file = filtered.elementAt(index); + final isSelected = + selectedFiles.any((e) => e.id == file.id); + return DriveFileWidget( + account: account, + file: file, + isSelected: isSelected, + onTap: () { + if (selectFile) { + context.pop(file); + } else if (isSelecting) { + if (isSelected) { + ref + .read( + selectedDriveFilesNotifierProvider + .notifier, + ) + .remove(file.id); + } else { + ref + .read( + selectedDriveFilesNotifierProvider + .notifier, + ) + .add(file); + } + } else { + context.push( + '/$account/drive/file/${folderId != null ? '$folderId/' : ''}${file.id}', + ); + } + }, + onLongPress: () => ref + .read(selectedDriveFilesNotifierProvider.notifier) + .add(file), + ); }, - onLongPress: () => ref - .read(selectedDriveFilesNotifierProvider.notifier) - .add(file), ); }, ), diff --git a/lib/view/page/image_page.dart b/lib/view/page/image_page.dart index 8ea1ca63..2b834996 100644 --- a/lib/view/page/image_page.dart +++ b/lib/view/page/image_page.dart @@ -1,6 +1,7 @@ import 'dart:typed_data'; import 'dart:ui'; +import 'package:file_picker/file_picker.dart'; import 'package:flex_color_picker/flex_color_picker.dart'; import 'package:flutter/material.dart' hide Image; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -409,7 +410,10 @@ class ImagePage extends HookConsumerWidget { case _Modes.image: final file = await showModalBottomSheet( context: context, - builder: (context) => FilePickerSheet(account: account), + builder: (context) => FilePickerSheet( + account: account, + type: FileType.image, + ), clipBehavior: Clip.hardEdge, ); if (file == null) return; diff --git a/lib/view/page/settings/profile_page.dart b/lib/view/page/settings/profile_page.dart index 5a75f6cc..5b0a6154 100644 --- a/lib/view/page/settings/profile_page.dart +++ b/lib/view/page/settings/profile_page.dart @@ -1,3 +1,4 @@ +import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -45,7 +46,10 @@ class ProfilePage extends HookConsumerWidget { }) async { final result = await showModalBottomSheet( context: ref.context, - builder: (context) => FilePickerSheet(account: account), + builder: (context) => FilePickerSheet( + account: account, + type: FileType.image, + ), clipBehavior: Clip.hardEdge, ); if (result == null) return null; diff --git a/lib/view/widget/drive_create_sheet.dart b/lib/view/widget/drive_create_sheet.dart index 0c19745e..c7007a73 100644 --- a/lib/view/widget/drive_create_sheet.dart +++ b/lib/view/widget/drive_create_sheet.dart @@ -1,4 +1,5 @@ import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; @@ -28,6 +29,9 @@ class DriveCreateSheet extends HookConsumerWidget { Future _upload(WidgetRef ref, bool keepOriginal) async { final result = await FilePicker.platform.pickFiles( + type: defaultTargetPlatform == TargetPlatform.iOS + ? FileType.media + : FileType.any, allowMultiple: true, ); if (result == null || result.files.isEmpty) return; diff --git a/lib/view/widget/file_picker_sheet.dart b/lib/view/widget/file_picker_sheet.dart index ca5b12e9..d41b0b33 100644 --- a/lib/view/widget/file_picker_sheet.dart +++ b/lib/view/widget/file_picker_sheet.dart @@ -1,4 +1,5 @@ import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -14,10 +15,12 @@ class FilePickerSheet extends ConsumerWidget { const FilePickerSheet({ super.key, required this.account, + this.type, this.allowMultiple = false, }); final Account account; + final FileType? type; final bool allowMultiple; @override @@ -29,8 +32,12 @@ class FilePickerSheet extends ConsumerWidget { leading: const Icon(Icons.upload), title: Text(t.aria.fromDevice), onTap: () async { - final result = await FilePicker.platform - .pickFiles(allowMultiple: allowMultiple); + final result = await FilePicker.platform.pickFiles( + type: defaultTargetPlatform == TargetPlatform.iOS && type == null + ? FileType.media + : type ?? FileType.any, + allowMultiple: allowMultiple, + ); if (!context.mounted) return; if (result case FilePickerResult(:final files)) { if (allowMultiple) { @@ -63,6 +70,7 @@ class FilePickerSheet extends ConsumerWidget { builder: (context) => DrivePage( account: account, selectFiles: true, + type: type ?? FileType.any, ), ); if (!context.mounted) return; @@ -79,6 +87,7 @@ class FilePickerSheet extends ConsumerWidget { builder: (context) => DrivePage( account: account, selectFile: true, + type: type ?? FileType.any, ), ); if (!context.mounted) return;