From 448913f98c956b4a914f176a4fda9ae6e162c8a3 Mon Sep 17 00:00:00 2001 From: Furkan Date: Thu, 29 Dec 2022 08:45:28 +0300 Subject: [PATCH] init --- .flutter-plugins | 5 + .flutter-plugins-dependencies | 2 +- CHANGELOG.md | 2 +- README.md | 429 +++++++++++++++++- .../android/app/src/main/AndroidManifest.xml | 1 + .../lib/examples/bottom_sheet_example.dart | 154 +++++++ .../lib/examples/gallery_picker_example.dart | 143 ++++++ example/lib/examples/multiple_medias.dart | 131 ++++++ .../examples/pick_medias_with_builder.dart | 94 ++++ example/lib/examples/whatsapp_pick_photo.dart | 284 ++++++++++++ example/lib/main.dart | 399 ++++++++-------- example/pubspec.lock | 86 +++- example/pubspec.yaml | 1 + example/test/widget_test.dart | 34 +- lib/controller/bottom_sheet_controller.dart | 41 ++ lib/controller/gallery_controller.dart | 176 ++++--- lib/controller/picker_listener.dart | 24 + lib/gallery_picker.dart | 328 ++++--------- lib/models/config.dart | 4 +- lib/models/gallery_album.dart | 25 +- lib/models/gallery_media.dart | 19 + lib/models/media_file.dart | 63 ++- lib/trash/album_page.dart | 70 --- lib/trash/video_provider.dart | 69 --- lib/trash/viewer_page.dart | 40 -- lib/user_widgets/album_categories_view.dart | 74 +++ lib/user_widgets/album_medias.dart | 28 ++ lib/user_widgets/date_category_view.dart | 64 +++ lib/user_widgets/files_stream_builder.dart | 24 + lib/user_widgets/media_provider.dart | 26 ++ lib/user_widgets/photo_provider.dart | 67 +++ .../thumbnailAlbum.dart | 37 +- lib/user_widgets/thumbnail_media.dart | 57 +++ lib/user_widgets/video_provider.dart | 110 +++++ lib/views/ThumbnailMedia.dart | 8 +- .../album_categories_view.dart | 43 ++ .../gallery_categories_widget.dart | 40 -- lib/views/album_view/album_appbar.dart | 77 ++++ lib/views/album_view/album_medias_view.dart | 40 ++ lib/views/album_view/album_page.dart | 39 ++ ...ry_widget.dart => date_category_view.dart} | 20 +- .../gallery_category_view_page.dart | 51 --- .../gallery_category_view_widget.dart | 27 -- lib/views/album_view/media_view.dart | 59 ++- ..._widget.dart => selected_medias_view.dart} | 40 +- lib/views/bottom_sheet.dart | 154 +++++++ .../gallery_picker_view.dart | 225 +++++++++ .../gallery_picker_view/picker_appbar.dart | 74 +++ .../gallery_picker_view/reload_gallery.dart | 42 ++ .../gallery_picker_view/tappable_appbar.dart | 32 ++ lib/views/hero_page.dart | 20 - pubspec.yaml | 5 +- test/gallery_picker_test.dart | 2 - 53 files changed, 3145 insertions(+), 964 deletions(-) create mode 100644 example/lib/examples/bottom_sheet_example.dart create mode 100644 example/lib/examples/gallery_picker_example.dart create mode 100644 example/lib/examples/multiple_medias.dart create mode 100644 example/lib/examples/pick_medias_with_builder.dart create mode 100644 example/lib/examples/whatsapp_pick_photo.dart create mode 100644 lib/controller/bottom_sheet_controller.dart create mode 100644 lib/controller/picker_listener.dart create mode 100644 lib/models/gallery_media.dart delete mode 100644 lib/trash/album_page.dart delete mode 100644 lib/trash/video_provider.dart delete mode 100644 lib/trash/viewer_page.dart create mode 100644 lib/user_widgets/album_categories_view.dart create mode 100644 lib/user_widgets/album_medias.dart create mode 100644 lib/user_widgets/date_category_view.dart create mode 100644 lib/user_widgets/files_stream_builder.dart create mode 100644 lib/user_widgets/media_provider.dart create mode 100644 lib/user_widgets/photo_provider.dart rename lib/{views => user_widgets}/thumbnailAlbum.dart (72%) create mode 100644 lib/user_widgets/thumbnail_media.dart create mode 100644 lib/user_widgets/video_provider.dart create mode 100644 lib/views/album_categories_view/album_categories_view.dart delete mode 100644 lib/views/album_category_view/gallery_categories_widget.dart create mode 100644 lib/views/album_view/album_appbar.dart create mode 100644 lib/views/album_view/album_medias_view.dart create mode 100644 lib/views/album_view/album_page.dart rename lib/views/album_view/{date_category_widget.dart => date_category_view.dart} (72%) delete mode 100644 lib/views/album_view/gallery_category_view_page.dart delete mode 100644 lib/views/album_view/gallery_category_view_widget.dart rename lib/views/album_view/{selected_item_viewer_widget.dart => selected_medias_view.dart} (64%) create mode 100644 lib/views/bottom_sheet.dart create mode 100644 lib/views/gallery_picker_view/gallery_picker_view.dart create mode 100644 lib/views/gallery_picker_view/picker_appbar.dart create mode 100644 lib/views/gallery_picker_view/reload_gallery.dart create mode 100644 lib/views/gallery_picker_view/tappable_appbar.dart delete mode 100644 lib/views/hero_page.dart diff --git a/.flutter-plugins b/.flutter-plugins index 0b32444..51be248 100644 --- a/.flutter-plugins +++ b/.flutter-plugins @@ -1,4 +1,9 @@ # This is a generated file; do not edit or check into version control. +camera=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\camera-0.10.1\\ +camera_android=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\camera_android-0.10.2\\ +camera_avfoundation=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\camera_avfoundation-0.9.10\\ +camera_web=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\camera_web-0.3.1\\ +flutter_plugin_android_lifecycle=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\flutter_plugin_android_lifecycle-2.0.7\\ permission_handler=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\permission_handler-10.2.0\\ permission_handler_android=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\permission_handler_android-10.2.0\\ permission_handler_apple=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\permission_handler_apple-9.0.7\\ diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index b28d961..5e338ac 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"permission_handler_apple","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler_apple-9.0.7\\\\","native_build":true,"dependencies":[]},{"name":"photo_gallery","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\photo_gallery-1.1.1\\\\","native_build":true,"dependencies":[]},{"name":"video_player_avfoundation","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\video_player_avfoundation-2.3.8\\\\","native_build":true,"dependencies":[]},{"name":"video_thumbnail","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\video_thumbnail-0.5.3\\\\","native_build":true,"dependencies":[]}],"android":[{"name":"permission_handler_android","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler_android-10.2.0\\\\","native_build":true,"dependencies":[]},{"name":"photo_gallery","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\photo_gallery-1.1.1\\\\","native_build":true,"dependencies":[]},{"name":"video_player_android","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\video_player_android-2.3.10\\\\","native_build":true,"dependencies":[]},{"name":"video_thumbnail","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\video_thumbnail-0.5.3\\\\","native_build":true,"dependencies":[]}],"macos":[],"linux":[],"windows":[{"name":"permission_handler_windows","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler_windows-0.1.2\\\\","native_build":true,"dependencies":[]}],"web":[{"name":"video_player_web","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\video_player_web-2.0.13\\\\","dependencies":[]}]},"dependencyGraph":[{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]},{"name":"photo_gallery","dependencies":[]},{"name":"video_player","dependencies":["video_player_android","video_player_avfoundation","video_player_web"]},{"name":"video_player_android","dependencies":[]},{"name":"video_player_avfoundation","dependencies":[]},{"name":"video_player_web","dependencies":[]},{"name":"video_thumbnail","dependencies":[]}],"date_created":"2022-12-25 04:38:44.497275","version":"3.3.9"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"camera_avfoundation","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\camera_avfoundation-0.9.10\\\\","native_build":true,"dependencies":[]},{"name":"permission_handler_apple","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler_apple-9.0.7\\\\","native_build":true,"dependencies":[]},{"name":"photo_gallery","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\photo_gallery-1.1.1\\\\","native_build":true,"dependencies":[]},{"name":"video_player_avfoundation","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\video_player_avfoundation-2.3.8\\\\","native_build":true,"dependencies":[]},{"name":"video_thumbnail","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\video_thumbnail-0.5.3\\\\","native_build":true,"dependencies":[]}],"android":[{"name":"camera_android","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\camera_android-0.10.2\\\\","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"]},{"name":"flutter_plugin_android_lifecycle","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_plugin_android_lifecycle-2.0.7\\\\","native_build":true,"dependencies":[]},{"name":"permission_handler_android","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler_android-10.2.0\\\\","native_build":true,"dependencies":[]},{"name":"photo_gallery","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\photo_gallery-1.1.1\\\\","native_build":true,"dependencies":[]},{"name":"video_player_android","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\video_player_android-2.3.10\\\\","native_build":true,"dependencies":[]},{"name":"video_thumbnail","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\video_thumbnail-0.5.3\\\\","native_build":true,"dependencies":[]}],"macos":[],"linux":[],"windows":[{"name":"permission_handler_windows","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler_windows-0.1.2\\\\","native_build":true,"dependencies":[]}],"web":[{"name":"camera_web","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\camera_web-0.3.1\\\\","dependencies":[]},{"name":"video_player_web","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\video_player_web-2.0.13\\\\","dependencies":[]}]},"dependencyGraph":[{"name":"camera","dependencies":["camera_android","camera_avfoundation","camera_web","flutter_plugin_android_lifecycle"]},{"name":"camera_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"camera_avfoundation","dependencies":[]},{"name":"camera_web","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]},{"name":"photo_gallery","dependencies":[]},{"name":"video_player","dependencies":["video_player_android","video_player_avfoundation","video_player_web"]},{"name":"video_player_android","dependencies":[]},{"name":"video_player_avfoundation","dependencies":[]},{"name":"video_player_web","dependencies":[]},{"name":"video_thumbnail","dependencies":[]}],"date_created":"2022-12-29 08:39:33.547831","version":"3.3.9"} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 41cc7d8..98a80ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,3 @@ ## 0.0.1 -* TODO: Describe initial release. +* Init diff --git a/README.md b/README.md index 02fe8ec..0c8bc73 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,422 @@ - - -TODO: Put a short description of the package here that helps potential users -know whether this package might be useful for them. + ## Features -TODO: List what your package can do. Maybe include images, gifs, or videos. +[✔] Modern design + +[✔] Detailed documentation + +[✔] Pick a media file + +[✔] Pick multiple media files + +[✔] BottomSheet layout + +[✔] Fetch all media files from your phone + +[✔] Comprehensively customizable design (desitination page, hero destination page...) + +[✔] Gallery picker listener + +[✔] Thumbnail widgets for media files + +[✔] MediaProvider widgets to view video / image files + +[✔] Gallery picker StreamBuilder to update your design if selects any file in gallery picker (GalleryPickerBuilder) + +[✔] Ready-to-use widgets + +[✔] Examples provided (example/lib/examples) + +[✔] Null-safety + +
+ + + + + + + +
+ + + + + + + +
+
## Getting started -TODO: List prerequisites and provide or point to information on how to -start using the package. +1) Update kotlin version to `1.6.0` and `classpath 'com.android.tools.build:gradle:7.0.4'` in your `build.gradle` +2) In `android` set the `minSdkVersion` to `25` in your `build.gradle` + +#### Android +Add uses-permission `android/app/src/main/AndroidManifest.xml` file + ```xml + + ``` +#### Ios +Add these configurations to your `ios/Runner/info.plist` file ?????????? + ```xml + NSPhotoLibraryUsageDescription + Privacy - Photo Library Usage Description + NSMotionUsageDescription + Motion usage description + NSPhotoLibraryAddUsageDescription + NSPhotoLibraryAddUsageDescription + ``` ## Usage -TODO: Include short and useful examples for package users. Add longer examples -to `/example` folder. +Quick and simple usage example: + +### Pick Single File ```dart -const like = 'sample'; +MediaFile? media = await GalleryPicker.pickMedias(context: context,singleMedia: true); +``` +### Pick Multiple Files + +```dart +List? medias = await GalleryPicker.pickMedias(context: context); ``` -## Additional information +### Get All Media Files in Gallery -TODO: Tell users more about the package: where to find more information, how to -contribute to the package, how to file issues, what response they can expect -from the package authors, and more. +```dart +GalleryMedia? allmedia = await GalleryPicker.collectGallery; +``` + +### Listen selected files inside gallery picker + +```dart +Stream stream = GalleryPicker.listenSelectedFiles; +``` +Dispose listener +```dart +GalleryPicker.disposeSelectedFilesListener(); +``` + +### BottomSheetLayout + +Gallery Picker could also work as a bottom sheet. Wrap your scaffold's body with BottomSheetLayout + +There is an example at `example/lib/examples/bottom_sheet_example.dart` to see how it could be done. + +```dart + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: BottomSheetLayout( + onSelect: (medias) {}, + child: Column( + children: [ +``` + +### Customizable destination page + +Within the Gallery Picker you can design a page that will be redirected after selecting any image(s). + +Note: There are two builder called multipleMediasBuilder and heroBuilder. If you designed both of them, multipleMediasBuilder will be shown after picking multiple media files, heroBuilder will be shown after picking a single media. Use given hero tag to view your Hero image. You can see a simple example below. + +There is an example at `example/lib/examples/pick_medias_with_builder.dart` to see how it could be done. + +```dart + GalleryPicker.pickMediasWithBuilder( + multipleMediasBuilder: ((medias, context) { + return Scaffold( + appBar: AppBar( + title: const Text('Flippers Page'), + ), + body: GridView.count( + crossAxisCount: 3, + mainAxisSpacing: 5, + crossAxisSpacing: 5, + children: [ + for (var media in medias) + ThumbnailMedia( + media: media, + ) + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MyHomePage( + title: "Selected Medias", + medias: medias, + )), + ); + GalleryPicker.dispose(); + }, + child: const Icon( + Icons.send, + color: Colors.white, + ), + ), + ); + }), + heroBuilder: (tag, media, context) { + return Scaffold( + appBar: AppBar( + title: const Text('Flippers Page'), + ), + body: Container( + color: Colors.lightBlueAccent, + padding: const EdgeInsets.all(16.0), + alignment: Alignment.topLeft, + child: Hero( + tag: tag, + child: Image.memory(media.thumbnail!), + ), + ), + floatingActionButton: FloatingActionButton( + backgroundColor: Colors.orange, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MyHomePage( + title: "Selected Medias", + medias: [media], + )), + ); + GalleryPicker.dispose(); + }, + child: const Icon( + Icons.send, + color: Colors.white, + ), + ), + ); + }, + context: context); +``` + +### Dispose Gallery picker + +```dart +GalleryPicker.dispose(); +``` + +## Customize your gallery picker + +A Config class is provided to user to customize your gallery picker. You can customize any feature you want and select appearance mode. + +#### Customizable appereance features +```dart +List? medias = await GalleryPicker.pickMedias( + context: context, + config: Config( + backgroundColor: Colors.white, + appbarColor: Colors.white, + bottomSheetColor: const Color.fromARGB(255, 247, 248, 250), + appbarIconColor: const Color.fromARGB(255, 130, 141, 148), + underlineColor: const Color.fromARGB(255, 20, 161, 131), + selectedMenuStyle: const TextStyle(color: Colors.black), + unselectedMenuStyle: + const TextStyle(color: Color.fromARGB(255, 102, 112, 117)), + textStyle: const TextStyle( + color: Color.fromARGB(255, 108, 115, 121), + fontWeight: FontWeight.bold), + appbarTextStyle: const TextStyle(color: Colors.black), + recents: "RECENTS", + gallery: "GALLERY", + lastMonth: "Last Month", + lastWeek: "Last Week", + tapPhotoSelect: "Tap photo to select", + selected: "Selected", + months: [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" + ], + selectIcon: Container( + width: 50, + height: 50, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Color.fromARGB(255, 0, 168, 132), + ), + child: const Icon( + Icons.check, + color: Colors.white, + ), + ), + ), + ) +``` + +#### Appearance Mode +```dart +List? medias = await GalleryPicker.pickMedias( + context: context, + config: Config( + mode: Mode.dark + ), + ) +``` + +#### Give an initial selected media files + +```dart +List? medias = await GalleryPicker.pickMedias( + context: context, + initSelectedMedias: this.selectedMedias, + ) +``` +#### Select your priority page + +There are two pages called "Recent" and "Gallery". You could change the initial page. + +```dart +List? medias = await GalleryPicker.pickMedias( + context: context, + startWithRecent: true, + ) +``` + +## MediaFile +GalleryPicker returns MediaFile list. You can reach out features below. + +[✔] Medium +[✔] Id +[✔] MediumType +[✔] Thumbnail +[✔] Check with thumbnailFailed if fetching thumbnail fails +[✔] Check with fileFailed if getting file fails +[✔] File +[✔] getThumbnail function +[✔] getFile function +[✔] getData function +[✔] Check if the file selected in gallery picker + +## Ready-to-use widgets + +### ThumbnailMedia + +```dart +ThumbnailMedia( + media: media, +) +``` +### ThumbnailAlbum + +```dart +ThumbnailAlbum( + album: album, + failIconColor: failIconColor, + mode: mode, + backgroundColor: backgroundColor, +) +``` +### PhotoProvider + +```dart +PhotoProvider( + media: media, +) +``` +### VideoProvider + +```dart +VideoProvider( + media: media, +) +``` +### MediaProvider +MediaProvider works with every media type + +```dart +MediaProvider( + media: media, +) +``` + +### GalleryPickerBuilder + +You can listen and update your design through this builder + +```dart +GalleryPickerBuilder( + builder: (selectedFiles, context) { + return child + }, +) +``` +### AlbumMediasView + +View all media files in the album sorted by its creation date + +```dart +GalleryMedia? allmedia = await GalleryPicker.collectGallery; +``` + +```dart +AlbumMediasView( + galleryAlbum: allmedia!.albums[0], + textStyle: textStyle, +) +``` +### AlbumCategoriesView + +View all album categories + +```dart +GalleryMedia? allmedia = await GalleryPicker.collectGallery; +``` +```dart +AlbumCategoriesView( + albums: allmedia!.albums, + categoryBackgroundColor: categoryBackgroundColor, + categoryFailIconColor: categoryFailIconColor, + mode: mode, + onFocusChange: onFocusChange, + onHover: onHover, + onLongPress: onLongPress, + onPressed: onPressed, +) +``` + +## Examples +Check out our examples! +### Standart Gallery Picker +`example/lib/examples/gallery_picker_example.dart` +### Pick Media Files With Destination Page +`example/lib/examples/pick_medias_with_builder.dart` +### BottomSheet Example +`example/lib/examples/bottom_sheet_example.dart` +### WhatsApp Pick Photo Page +`example/lib/examples/whatsapp_pick_photo.dart` + +## This package was possible to create with: +- The [photo_gallery](https://pub.dev/packages/photo_gallery) package +- The [transparent_image](https://pub.dev/packages/transparent_image) package +- The [get](https://pub.dev/packages/get) package +- The [video_player](https://pub.dev/packages/video_player) package +- The [intl](https://pub.dev/packages/intl) package +- The [bottom_sheet_bar](https://pub.dev/packages/bottom_sheet_bar) package +- The [platform_info](https://pub.dev/packages/platform_info) package +- The [permission_handler](https://pub.dev/packages/permission_handler) package \ No newline at end of file diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 7604f91..cde9521 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ package="com.example.gallery_picker_example"> + createState() => _BottomSheetExampleState(); +} + +class _BottomSheetExampleState extends State { + List selectedMedias = []; + int pageIndex = 0; + var controller = PageController(initialPage: 0); + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: BottomSheetLayout( + onSelect: (List selectedMedias) { + this.selectedMedias = selectedMedias; + pageIndex = 0; + if (this.selectedMedias.isNotEmpty) { + Future.delayed(Duration(milliseconds: 500)).then((value) { + controller.animateToPage(0, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + }); + } + setState(() {}); + }, + config: Config(mode: Mode.dark), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + const Text( + 'These are your selected medias', + ), + const Divider(), + Expanded( + flex: 5, + child: Stack(children: [ + if (selectedMedias.isNotEmpty) + PageView( + controller: controller, + children: [ + for (var media in selectedMedias) + Center( + child: MediaProvider( + media: media, + ), + ) + ], + ), + if (selectedMedias.isNotEmpty) + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () { + if (pageIndex < selectedMedias.length - 1) { + pageIndex++; + controller.animateToPage(pageIndex, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + setState(() {}); + } + }, + child: const Icon( + Icons.chevron_right, + size: 100, + color: Colors.red, + )), + ), + if (selectedMedias.isNotEmpty) + Align( + alignment: Alignment.centerLeft, + child: TextButton( + onPressed: () { + if (pageIndex > 0) { + pageIndex--; + controller.animateToPage(pageIndex, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + setState(() {}); + } + }, + child: const Icon( + Icons.chevron_left, + size: 100, + color: Colors.red, + )), + ), + ]), + ), + SizedBox( + height: 65, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + for (int i = 0; i < selectedMedias.length; i++) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: TextButton( + onPressed: () { + pageIndex = i; + controller.animateToPage(pageIndex, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + setState(() {}); + }, + child: Container( + width: 65, + height: 50, + decoration: BoxDecoration( + border: Border.all( + width: 2, + color: pageIndex == i + ? Colors.red + : Colors.black)), + child: ThumbnailMedia( + media: selectedMedias[i], + )), + ), + ) + ], + ), + ), + Spacer( + flex: 1, + ), + TextButton( + onPressed: () { + print("lol"); + GalleryPicker.openSheet(); + }, + child: const Icon( + Icons.open_in_new, + size: 40, + ), + ), + Spacer( + flex: 1, + ), + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/examples/gallery_picker_example.dart b/example/lib/examples/gallery_picker_example.dart new file mode 100644 index 0000000..26b692b --- /dev/null +++ b/example/lib/examples/gallery_picker_example.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:gallery_picker/gallery_picker.dart'; + +class GalleryPickerExample extends StatefulWidget { + const GalleryPickerExample({super.key}); + + @override + State createState() => _GalleryPickerExampleState(); +} + +class _GalleryPickerExampleState extends State { + List selectedMedias = []; + int pageIndex = 0; + var controller = PageController(initialPage: 0); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Pick medias"), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + const Text( + 'These are your selected medias', + ), + const Divider(), + Expanded( + flex: 5, + child: Stack(children: [ + if (selectedMedias.isNotEmpty) + PageView( + controller: controller, + children: [ + for (var media in selectedMedias) + Center( + child: MediaProvider( + media: media, + ), + ) + ], + ), + if (selectedMedias.isNotEmpty) + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () { + if (pageIndex < selectedMedias.length - 1) { + pageIndex++; + controller.animateToPage(pageIndex, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + setState(() {}); + } + }, + child: const Icon( + Icons.chevron_right, + size: 100, + color: Colors.red, + )), + ), + if (selectedMedias.isNotEmpty) + Align( + alignment: Alignment.centerLeft, + child: TextButton( + onPressed: () { + if (pageIndex > 0) { + pageIndex--; + controller.animateToPage(pageIndex, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + setState(() {}); + } + }, + child: const Icon( + Icons.chevron_left, + size: 100, + color: Colors.red, + )), + ), + ]), + ), + SizedBox( + height: 65, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + for (int i = 0; i < selectedMedias.length; i++) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: TextButton( + onPressed: () { + pageIndex = i; + controller.animateToPage(pageIndex, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + setState(() {}); + }, + child: Container( + width: 65, + height: 50, + decoration: BoxDecoration( + border: Border.all( + width: 2, + color: pageIndex == i + ? Colors.red + : Colors.black)), + child: ThumbnailMedia( + media: selectedMedias[i], + )), + ), + ) + ], + ), + ), + const Spacer( + flex: 2, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: pickMedias, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } + + Future pickMedias() async { + List? medias = await GalleryPicker.pickMedias( + context: context, + config: Config(mode: Mode.dark), + ); + if (medias != null) { + setState(() { + selectedMedias += medias; + }); + } + } +} diff --git a/example/lib/examples/multiple_medias.dart b/example/lib/examples/multiple_medias.dart new file mode 100644 index 0000000..2fc7abb --- /dev/null +++ b/example/lib/examples/multiple_medias.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/container.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:gallery_picker/gallery_picker.dart'; + +import '../main.dart'; + +class MultipleMediasView extends StatefulWidget { + final List medias; + const MultipleMediasView(this.medias, {super.key}); + + @override + State createState() => _MultipleMediasViewState(); +} + +class _MultipleMediasViewState extends State { + int pageIndex = 0; + var controller = PageController(initialPage: 0); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Destination Page"), + ), + body: Column(children: [ + const Spacer(), + Text("These are your selected medias"), + Divider(), + Expanded( + flex: 5, + child: Stack(children: [ + PageView( + controller: controller, + children: [ + for (var media in widget.medias) + Center( + child: MediaProvider( + media: media, + ), + ) + ], + ), + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () { + print(pageIndex); + if (pageIndex < widget.medias.length - 1) { + pageIndex++; + controller.animateToPage(pageIndex, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + setState(() {}); + } + }, + child: const Icon( + Icons.chevron_right, + size: 100, + color: Colors.red, + )), + ), + Align( + alignment: Alignment.centerLeft, + child: TextButton( + onPressed: () { + if (pageIndex > 0) { + pageIndex--; + controller.animateToPage(pageIndex, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + setState(() {}); + } + }, + child: const Icon( + Icons.chevron_left, + size: 100, + color: Colors.red, + )), + ), + ]), + ), + const Divider(), + SizedBox( + height: 50, + child: ListView(scrollDirection: Axis.horizontal, children: [ + for (int i = 0; i < widget.medias.length; i++) + TextButton( + onPressed: () { + pageIndex=i; + controller.animateToPage(i, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + setState(() {}); + }, + child: Container( + width: 50, + height: 50, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: i == pageIndex ? Colors.red : Colors.grey), + child: Text( + (i + 1).toString(), + style: TextStyle(fontSize: 16, color: Colors.black), + ), + )) + ]), + ), + const Spacer(), + ]), + floatingActionButton: FloatingActionButton( + backgroundColor: Colors.blue, + onPressed: () { + GalleryPicker.dispose(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MyHomePage( + title: "Selected Medias", + medias: widget.medias, + )), + ); + }, + child: const Icon( + Icons.send, + color: Colors.white, + ), + ), + ); + } +} diff --git a/example/lib/examples/pick_medias_with_builder.dart b/example/lib/examples/pick_medias_with_builder.dart new file mode 100644 index 0000000..0df5cb8 --- /dev/null +++ b/example/lib/examples/pick_medias_with_builder.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/container.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:gallery_picker/gallery_picker.dart'; + +import '../main.dart'; +import 'multiple_medias.dart'; + +class PickMediasWithBuilder extends StatefulWidget { + const PickMediasWithBuilder({super.key}); + + @override + State createState() => _PickMediasWithBuilderState(); +} + +class _PickMediasWithBuilderState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Pick Medias With Builder"), + ), + body: Stack( + children: [ + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + TextButton( + onPressed: pickMediasWithBuilder, + child: Container( + width: 300, + height: 60, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(20)), + alignment: Alignment.center, + child: Text( + 'Pick Medias With Builder', + style: TextStyle(color: Colors.white), + ), + )), + const Spacer() + ], + ), + ), + ], + ), + ); + } + + pickMediasWithBuilder() { + GalleryPicker.pickMediasWithBuilder( + multipleMediasBuilder: ((medias, context) { + return MultipleMediasView(medias); + }), + heroBuilder: (tag, media, context) { + return Scaffold( + appBar: AppBar( + title: const Text('Hero Page'), + ), + body: Center( + child: Hero( + tag: tag, + child: MediaProvider( + media: media, + width: MediaQuery.of(context).size.width - 50, + height: 300, + ), + )), + floatingActionButton: FloatingActionButton( + backgroundColor: Colors.blue, + onPressed: () { + GalleryPicker.dispose(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MyHomePage( + title: "Selected Medias", + medias: [media], + )), + ); + }, + child: const Icon( + Icons.send, + color: Colors.white, + ), + ), + ); + }, + context: context); + } +} diff --git a/example/lib/examples/whatsapp_pick_photo.dart b/example/lib/examples/whatsapp_pick_photo.dart new file mode 100644 index 0000000..1353414 --- /dev/null +++ b/example/lib/examples/whatsapp_pick_photo.dart @@ -0,0 +1,284 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/container.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:gallery_picker/gallery_picker.dart'; +import 'package:camera/camera.dart'; +import 'package:gallery_picker_example/examples/multiple_medias.dart'; + +class WhatsappPickPhoto extends StatefulWidget { + const WhatsappPickPhoto({super.key}); + + @override + State createState() => _WhatsappPickPhotoState(); +} + +class _WhatsappPickPhotoState extends State { + CameraController? cameraController; + GalleryMedia? gallery; + List selectedMedias = []; + List? cameras; + CameraLensDirection cameraLensDirection = CameraLensDirection.front; + @override + void initState() { + initCamera(); + fetchMedias(); + GalleryPicker.listenSelectedFiles.listen((medias) { + selectedMedias = medias; + setState(() {}); + }); + super.initState(); + } + + Future fetchMedias() async { + gallery = await GalleryPicker.collectGallery; + setState(() {}); + } + + Future initCamera() async { + print(cameraLensDirection); + cameraController = null; + setState(() {}); + cameras ??= await availableCameras(); + cameraController = CameraController( + cameras!.firstWhere( + (camera) => camera.lensDirection == cameraLensDirection), + ResolutionPreset.max); + cameraController!.initialize().then((_) { + if (!mounted) { + return; + } + setState(() {}); + }).catchError((Object e) { + if (e is CameraException) { + switch (e.code) { + case 'CameraAccessDenied': + // Handle access errors here. + break; + default: + // Handle other errors here. + break; + } + } + }); + } + + bool isRecording = false; + bool anyProcess = false; + @override + void dispose() { + cameraController!.dispose(); + GalleryPicker.disposeSelectedFilesListener(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: BottomSheetLayout( + onSelect: (List selectedMedias) { + this.selectedMedias = selectedMedias; + setState(() {}); + }, + initSelectedMedias: selectedMedias, + config: Config(mode: Mode.dark), + child: Stack( + children: [ + if (cameraController != null && + cameraController!.value.isInitialized) + SizedBox( + height: MediaQuery.of(context).size.height, + child: CameraPreview( + cameraController!, + ), + ), + if (gallery != null && gallery!.recent != null) + Positioned( + bottom: 100, + child: SizedBox( + width: MediaQuery.of(context).size.width, + height: 65, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + for (var media in gallery!.recent!.files) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: GestureDetector( + onTap: () { + if (selectedMedias.isEmpty) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + MultipleMediasView([media])), + ); + } else { + selectedMedias.any( + (element) => element.id == media.id) + ? selectedMedias.removeWhere( + (element) => element.id == media.id) + : selectedMedias.add(media); + setState(() {}); + } + }, + onLongPress: () { + if (selectedMedias + .any((element) => element.id == media.id)) { + selectedMedias.removeWhere( + (element) => element.id == media.id); + } else { + selectedMedias.add(media); + } + setState(() {}); + }, + child: Container( + width: 65, + height: 65, + decoration: BoxDecoration( + border: Border.all( + width: 2, + color: selectedMedias.any((element) => + element.id == media.id) + ? Colors.red + : Colors.black)), + child: Stack( + children: [ + SizedBox( + width: 65, + height: 65, + child: ThumbnailMedia( + media: media, + ), + ), + if (selectedMedias.any( + (element) => element.id == media.id)) + Container( + color: Colors.black.withOpacity(0.3), + alignment: Alignment.center, + child: const Icon( + Icons.check, + size: 30, + color: Colors.white, + ), + ) + ], + )), + ), + ) + ], + ), + ), + ), + if (selectedMedias.isNotEmpty) + Positioned( + bottom: 150, + right: 10, + child: FloatingActionButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + MultipleMediasView(selectedMedias)), + ); + }, + mini: true, + child: const Icon( + Icons.check, + color: Colors.white, + ), + )), + Positioned( + bottom: 20, + child: SizedBox( + width: MediaQuery.of(context).size.width, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + TextButton( + onPressed: () { + GalleryPicker.openSheet(); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + shape: BoxShape.circle), + padding: const EdgeInsets.all(8), + child: const Icon( + Icons.image, + size: 20, + color: Colors.white, + ), + ), + ), + GestureDetector( + onTap: () async { + setState(() { + anyProcess = true; + }); + Future.delayed(Duration(milliseconds: 100)) + .then((value) => setState(() { + anyProcess = false; + })); + }, + onLongPressStart: (value) { + setState(() { + isRecording = true; + anyProcess = true; + }); + }, + onLongPressEnd: (value) async { + setState(() { + isRecording = false; + anyProcess = false; + }); + }, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: + Border.all(width: 2, color: Colors.white)), + width: isRecording ? 80 : 65, + height: isRecording ? 80 : 65, + child: Container( + decoration: BoxDecoration( + color: + anyProcess ? Colors.red : Colors.transparent, + shape: BoxShape.circle, + ), + ), + ), + ), + TextButton( + onPressed: () { + if (cameraLensDirection == + CameraLensDirection.front) { + cameraLensDirection = CameraLensDirection.back; + } else { + cameraLensDirection = CameraLensDirection.front; + } + initCamera(); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + shape: BoxShape.circle), + padding: const EdgeInsets.all(8), + child: const Icon( + Icons.cameraswitch, + size: 20, + color: Colors.white, + ), + ), + ), + ], + ), + )) + ], + ), + ), + ); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index f9c1d1c..ddff7b0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:gallery_picker/gallery_picker.dart'; -import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; +import 'package:gallery_picker_example/examples/pick_medias_with_builder.dart'; +import 'examples/gallery_picker_example.dart'; +import 'examples/multiple_medias.dart'; +import 'examples/whatsapp_pick_photo.dart'; void main() { runApp(const MyApp()); @@ -9,30 +13,27 @@ void main() { class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( - primarySwatch: Colors.blue, + brightness: Brightness.light, + /* light theme settings */ ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), + darkTheme: ThemeData( + brightness: Brightness.dark, + /* dark theme settings */ + ), + themeMode: ThemeMode.dark, + home: const GalleryPickerExample(), ); } } class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". + final List? medias; + const MyHomePage({super.key, required this.title, this.medias}); final String title; @@ -41,213 +42,191 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - int _counter = 0; - - void viewGalleryPicker() { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => GalleryPicker( - onSelect: (selectedMedias) { - print(selectedMedias.length); - }, - heroBuilder: (tag, media, context) { - return Scaffold( - appBar: AppBar( - title: const Text('Flippers Page'), - ), - body: Container( - // The blue background emphasizes that it's a new route. - color: Colors.lightBlueAccent, - padding: const EdgeInsets.all(16.0), - alignment: Alignment.topLeft, - child: Hero( - tag: tag, - child: Image.memory(media.thumbnail!), - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => MyHomePage( - title: "messages", - )), - ); - disposeGalleryPicker(); - }, - child: Icon(Icons.send), - ), - ); - }, - multipleMediaBuilder: (medias, context) { - return Scaffold( - appBar: AppBar( - title: const Text('Flippers Page'), - ), - body: GridView.count( - crossAxisCount: 3, - mainAxisSpacing: 5, - crossAxisSpacing: 5, - children: [ - for (var media in medias) Image.memory(media.thumbnail!) - ], - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => MyHomePage( - title: "messages", - )), - ); - disposeGalleryPicker(); - }, - child: Icon(Icons.send), - ), - ); - }, - )), - ); - } - - showBottomSheet() { - showMaterialModalBottomSheet( - context: context, - builder: (context) => SingleChildScrollView( - controller: ModalScrollController.of(context), - child: SizedBox( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - child: GalleryPicker( - onSelect: (selectedMedias) { - print(selectedMedias.length); - }, - heroBuilder: (heroId, media, context) { - return Scaffold( - appBar: AppBar( - title: const Text('Flippers Page'), - ), - body: Container( - // The blue background emphasizes that it's a new route. - color: Colors.lightBlueAccent, - padding: const EdgeInsets.all(16.0), - alignment: Alignment.topLeft, - child: GestureDetector( - onTap: () => Navigator.pop(context), - child: Hero( - tag: heroId, - child: Image.memory(media.thumbnail!), - ), - ), - ), - floatingActionButton: FloatingActionButton( - backgroundColor: Colors.orange, - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => MyHomePage( - title: "messages", - )), - ); - disposeGalleryPicker(); - }, - child: Icon( - Icons.send, - color: Colors.white, - ), - ), - ); - }, - multipleMediaBuilder: (medias, context) { - return Scaffold( - appBar: AppBar( - title: const Text('Flippers Page'), - ), - body: GridView.count( - crossAxisCount: 3, - mainAxisSpacing: 5, - crossAxisSpacing: 5, - children: [ - for (var media in medias) Image.memory(media.thumbnail!) - ], - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => MyHomePage( - title: "messages", - )), - ); - disposeGalleryPicker(); - }, - child: Icon( - Icons.send, - color: Colors.white, - ), - ), - ); - }, - ), - ), - ), - ); - } + List selectedMedias = []; + @override + void initState() { + if (widget.medias != null) { + selectedMedias = widget.medias!; + } + super.initState(); + } + + int pageIndex = 0; + var controller = PageController(initialPage: 0); @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. return Scaffold( + appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), + title: Text("Pick medias"), ), - body: Stack( - children: [ - Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - ], + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + const Text( + 'These are your selected medias', ), - ), - ], + const Divider(), + Expanded( + flex: 5, + child: Stack(children: [ + if (selectedMedias.isNotEmpty) + PageView( + controller: controller, + children: [ + for (var media in selectedMedias) + Center( + child: MediaProvider( + media: media, + ), + ) + ], + ), + if (selectedMedias.isNotEmpty) + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () { + if (pageIndex < selectedMedias.length - 1) { + pageIndex++; + controller.animateToPage(pageIndex, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + setState(() {}); + } + }, + child: const Icon( + Icons.chevron_right, + size: 100, + color: Colors.red, + )), + ), + if (selectedMedias.isNotEmpty) + Align( + alignment: Alignment.centerLeft, + child: TextButton( + onPressed: () { + if (pageIndex > 0) { + pageIndex--; + controller.animateToPage(pageIndex, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + setState(() {}); + } + }, + child: const Icon( + Icons.chevron_left, + size: 100, + color: Colors.red, + )), + ), + ]), + ), + SizedBox( + height: 65, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + for (int i = 0; i < selectedMedias.length; i++) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: TextButton( + onPressed: () { + pageIndex = i; + controller.animateToPage(pageIndex, + duration: const Duration(milliseconds: 500), + curve: Curves.easeIn); + setState(() {}); + }, + child: Container( + width: 65, + height: 50, + decoration: BoxDecoration( + border: Border.all( + width: 2, + color: pageIndex == i + ? Colors.red + : Colors.black)), + child: ThumbnailMedia( + media: selectedMedias[i], + )), + ), + ) + ], + ), + ), + const Spacer( + flex: 2, + ), + ], + ), ), floatingActionButton: FloatingActionButton( - onPressed: showBottomSheet, + onPressed: pickMedias, tooltip: 'Increment', child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + ), ); } + + Future pickMedias() async { + List? medias = await GalleryPicker.pickMedias( + context: context, + initSelectedMedias: selectedMedias, + startWithRecent: true); + if (medias != null) { + setState(() { + this.selectedMedias += medias; + }); + } + } + + pickMediasWithBuilder() { + GalleryPicker.pickMediasWithBuilder( + multipleMediasBuilder: ((medias, context) { + return MultipleMediasView(medias); + }), + heroBuilder: (tag, media, context) { + return Scaffold( + appBar: AppBar( + title: const Text('Hero Page'), + ), + body: Center( + child: Hero( + tag: tag, + child: MediaProvider( + media: media, + width: MediaQuery.of(context).size.width - 50, + height: 300, + ), + )), + floatingActionButton: FloatingActionButton( + backgroundColor: Colors.blue, + onPressed: () { + GalleryPicker.dispose(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MyHomePage( + title: "Selected Medias", + medias: [media], + )), + ); + }, + child: const Icon( + Icons.send, + color: Colors.white, + ), + ), + ); + }, + context: context); + } + + Future getGalleryMedia() async { + GalleryMedia? allmedia = await GalleryPicker.collectGallery; + } } diff --git a/example/pubspec.lock b/example/pubspec.lock index a0a5213..31e25d4 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -15,6 +15,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + bottom_sheet_bar: + dependency: transitive + description: + name: bottom_sheet_bar + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.8" + camera: + dependency: transitive + description: + name: camera + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.1" + camera_android: + dependency: transitive + description: + name: camera_android + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.2" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.10" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.2" + camera_web: + dependency: transitive + description: + name: camera_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1" characters: dependency: transitive description: @@ -36,6 +78,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + cross_file: + dependency: transitive + description: + name: cross_file + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3+2" csslib: dependency: transitive description: @@ -69,6 +118,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" flutter_test: dependency: "direct dev" description: flutter @@ -135,6 +191,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.5" + measure_size: + dependency: transitive + description: + name: measure_size + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" meta: dependency: transitive description: @@ -142,13 +205,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" - modal_bottom_sheet: - dependency: transitive - description: - name: modal_bottom_sheet - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.2" path: dependency: transitive description: @@ -157,7 +213,7 @@ packages: source: hosted version: "1.8.2" permission_handler: - dependency: transitive + dependency: "direct main" description: name: permission_handler url: "https://pub.dartlang.org" @@ -205,6 +261,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" sky_engine: dependency: transitive description: flutter @@ -231,6 +294,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 1a358ab..8c7bec3 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + permission_handler: ^10.2.0 dev_dependencies: flutter_test: diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index ea29f78..c468256 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -8,23 +8,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:gallery_picker_example/main.dart'; - void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); + //testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // // Build our app and trigger a frame. + // await tester.pumpWidget(const MyApp()); +// + // // Verify that our counter starts at 0. + // expect(find.text('0'), findsOneWidget); + // expect(find.text('1'), findsNothing); +// + // // Tap the '+' icon and trigger a frame. + // await tester.tap(find.byIcon(Icons.add)); + // await tester.pump(); +// + // // Verify that our counter has incremented. + // expect(find.text('0'), findsNothing); + // expect(find.text('1'), findsOneWidget); + //}); } diff --git a/lib/controller/bottom_sheet_controller.dart b/lib/controller/bottom_sheet_controller.dart new file mode 100644 index 0000000..53691fc --- /dev/null +++ b/lib/controller/bottom_sheet_controller.dart @@ -0,0 +1,41 @@ +import 'package:bottom_sheet_bar/bottom_sheet_bar.dart'; +import 'package:get/get.dart'; + +import 'gallery_controller.dart'; + +class BottomSheetController extends GetxController { + BottomSheetBarController sheetController; + PhoneGalleryController? galleryController; + bool isClosing = false; + bool appBarTapping = false; + + Future open() async { + await sheetController.expand(); + } + + void tapingStatus(bool value) { + appBarTapping = value; + if (galleryController == null) { + Get.find().update(); + } else { + galleryController!.update(); + } + update(); + } + + Future close() async { + isClosing = true; + update(); + await sheetController.collapse(); + isClosing = false; + update(); + } + + void disposeController() { + isClosing = false; + appBarTapping = false; + GetInstance().delete(); + } + + BottomSheetController(this.sheetController); +} diff --git a/lib/controller/gallery_controller.dart b/lib/controller/gallery_controller.dart index 311c151..c28b3c8 100644 --- a/lib/controller/gallery_controller.dart +++ b/lib/controller/gallery_controller.dart @@ -1,14 +1,16 @@ +import 'dart:io'; import 'dart:typed_data'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:photo_gallery/photo_gallery.dart'; import '../models/config.dart'; -import '../views/hero_page.dart'; +import '../models/gallery_media.dart'; import '/models/gallery_album.dart'; import '/models/medium.dart'; import '../models/media_file.dart'; +import 'picker_listener.dart'; class PhoneGalleryController extends GetxController { late Config config; @@ -17,15 +19,19 @@ class PhoneGalleryController extends GetxController { {required this.onSelect, required this.heroBuilder, required this.isRecent, - required this.multipleMediaBuilder}) { + List? initSelectedMedias, + required this.multipleMediasBuilder}) { this.config = config ?? Config(); + if (initSelectedMedias != null) { + _selectedFiles = initSelectedMedias.map((e) => e).toList(); + } } bool isRecent; Function(List selectedMedias) onSelect; Widget Function(String tag, MediaFile media, BuildContext context)? heroBuilder; Widget Function(List medias, BuildContext context)? - multipleMediaBuilder; + multipleMediasBuilder; GalleryAlbum? selectedAlbum; List _galleryAlbums = []; List get galleryAlbums => _galleryAlbums; @@ -36,10 +42,19 @@ class PhoneGalleryController extends GetxController { bool _pickerMode = false; bool get pickerMode => _pickerMode; + void updateSelectedFiles(List medias) { + _selectedFiles = medias; + if(selectedFiles.isNotEmpty){ + _pickerMode=true; + } + update(); + } + void changeAlbum(GalleryAlbum? album) { selectedAlbum = album; - selectedFiles.clear(); + _selectedFiles.clear(); update(); + updatePickerListener(); } void unselectMedia(MediaFile file) { @@ -48,6 +63,7 @@ class PhoneGalleryController extends GetxController { _pickerMode = false; } update(); + updatePickerListener(); } void selectMedia(MediaFile file) { @@ -58,75 +74,106 @@ class PhoneGalleryController extends GetxController { _pickerMode = true; } update(); + updatePickerListener(); } void switchPickerMode(bool value) { if (!value) { - selectedFiles.clear(); + _selectedFiles.clear(); } _pickerMode = value; update(); + updatePickerListener(); + } + + void updatePickerListener() { + if (GetInstance().isRegistered()) { + print(_selectedFiles.length); + Get.find().updateController(_selectedFiles); + } + } + + static Future promptPermissionSetting() async { + if (Platform.isIOS && + await Permission.storage.request().isGranted && + await Permission.photos.request().isGranted || + Platform.isAndroid && await Permission.storage.request().isGranted) { + return true; + } + return false; } Future initializeAlbums() async { - List tempGalleryAlbums = []; - - List photoAlbums = - await PhotoGallery.listAlbums(mediumType: MediumType.image); - List videoAlbums = - await PhotoGallery.listAlbums(mediumType: MediumType.video); - - for (var photoAlbum in photoAlbums) { - GalleryAlbum entireGalleryAlbum = GalleryAlbum(album: photoAlbum); - await entireGalleryAlbum.initialize(); - entireGalleryAlbum.setType = AlbumType.image; - if (videoAlbums.any((element) => element.name == photoAlbum.name)) { - Album videoAlbum = videoAlbums - .singleWhere((element) => element.name == photoAlbum.name); - GalleryAlbum videoGalleryAlbum = GalleryAlbum(album: videoAlbum); - await videoGalleryAlbum.initialize(); - DateTime? lastPhotoDate = entireGalleryAlbum.lastDate; - DateTime? lastVideoDate = videoGalleryAlbum.lastDate; - - if (lastPhotoDate == null) { - try { - entireGalleryAlbum.thumbnail = await videoAlbum.getThumbnail(highQuality: true); - } catch (e) { - print(e); - } - } else if (lastVideoDate == null) { - } else { - if (lastVideoDate.isBefore(lastPhotoDate)) { - try { - entireGalleryAlbum.thumbnail = await videoAlbum.getThumbnail(highQuality: true); - } catch (e) { - entireGalleryAlbum.thumbnail = null; - print(e); - } - } - } - for (var file in videoGalleryAlbum.files) { - entireGalleryAlbum.addFile(file); - } - entireGalleryAlbum.sort(); - entireGalleryAlbum.setType = AlbumType.mixed; - videoAlbums.remove(videoAlbum); - } - tempGalleryAlbums.add(entireGalleryAlbum); + GalleryMedia? media = await PhoneGalleryController.collectGallery; + if (media != null) { + this._galleryAlbums = media.albums; } - for (var videoAlbum in videoAlbums) { - print(videoAlbum.name); - GalleryAlbum galleryVideoAlbum = GalleryAlbum(album: videoAlbum); - await galleryVideoAlbum.initialize(); - galleryVideoAlbum.setType = AlbumType.video; - tempGalleryAlbums.add(galleryVideoAlbum); - } - - _galleryAlbums = tempGalleryAlbums; _isInitialized = true; update(); } + static Future get collectGallery async { + if (await promptPermissionSetting()) { + List tempGalleryAlbums = []; + + List photoAlbums = + await PhotoGallery.listAlbums(mediumType: MediumType.image); + List videoAlbums = + await PhotoGallery.listAlbums(mediumType: MediumType.video); + + for (var photoAlbum in photoAlbums) { + GalleryAlbum entireGalleryAlbum = GalleryAlbum(album: photoAlbum); + await entireGalleryAlbum.initialize(); + entireGalleryAlbum.setType = AlbumType.image; + if (videoAlbums.any((element) => element.name == photoAlbum.name)) { + Album videoAlbum = videoAlbums + .singleWhere((element) => element.name == photoAlbum.name); + GalleryAlbum videoGalleryAlbum = GalleryAlbum(album: videoAlbum); + await videoGalleryAlbum.initialize(); + DateTime? lastPhotoDate = entireGalleryAlbum.lastDate; + DateTime? lastVideoDate = videoGalleryAlbum.lastDate; + + if (lastPhotoDate == null) { + try { + entireGalleryAlbum.thumbnail = + await videoAlbum.getThumbnail(highQuality: true); + } catch (e) { + print(e); + } + } else if (lastVideoDate == null) { + } else { + if (lastVideoDate.isAfter(lastPhotoDate)) { + try { + entireGalleryAlbum.thumbnail = + await videoAlbum.getThumbnail(highQuality: true); + } catch (e) { + entireGalleryAlbum.thumbnail = null; + print(e); + } + } + } + for (var file in videoGalleryAlbum.files) { + entireGalleryAlbum.addFile(file); + } + entireGalleryAlbum.sort(); + entireGalleryAlbum.setType = AlbumType.mixed; + videoAlbums.remove(videoAlbum); + } + tempGalleryAlbums.add(entireGalleryAlbum); + } + for (var videoAlbum in videoAlbums) { + GalleryAlbum galleryVideoAlbum = GalleryAlbum(album: videoAlbum); + await galleryVideoAlbum.initialize(); + galleryVideoAlbum.setType = AlbumType.video; + tempGalleryAlbums.add(galleryVideoAlbum); + } + + return GalleryMedia(tempGalleryAlbums); + } else { + return null; + } + } + GalleryAlbum? get recent { return _isInitialized ? _galleryAlbums.singleWhere((element) => element.album.name == "All") @@ -147,6 +194,15 @@ class PhoneGalleryController extends GetxController { } bool isSelectedMedia(MediaFile file) { - return _selectedFiles.any((element) => element == file); + return _selectedFiles.any((element) => element.medium.id == file.medium.id); + } + + void disposeController() { + _galleryAlbums = []; + _selectedFiles = []; + _isInitialized = false; + selectedAlbum = null; + Get.delete(); + update(); } } diff --git a/lib/controller/picker_listener.dart b/lib/controller/picker_listener.dart new file mode 100644 index 0000000..932b01c --- /dev/null +++ b/lib/controller/picker_listener.dart @@ -0,0 +1,24 @@ +import 'dart:async'; + +import 'package:get/get.dart'; + +import '../models/media_file.dart'; + +class PickerListener extends GetxController { + StreamController> controller = + StreamController>(); + + Stream> get stream => controller.stream; + + void updateController(List medias) { + print("len:${medias.length}"); + controller.add(medias); + } + + @override + void dispose() { + controller.close(); + GetInstance().delete(); + super.dispose(); + } +} diff --git a/lib/gallery_picker.dart b/lib/gallery_picker.dart index 92c91d6..97199f6 100644 --- a/lib/gallery_picker.dart +++ b/lib/gallery_picker.dart @@ -1,246 +1,118 @@ library gallery_picker; export 'models/config.dart'; - -import 'dart:io'; -import 'package:flutter/foundation.dart'; +export 'models/media_file.dart'; +export 'models/mode.dart'; +export 'models/medium.dart'; +export 'models/gallery_media.dart'; +export 'models/gallery_album.dart'; +export 'user_widgets/thumbnail_media.dart'; +export 'user_widgets/album_categories_view.dart'; +export 'user_widgets/album_medias.dart'; +export 'user_widgets/date_category_view.dart'; +export 'user_widgets/thumbnailAlbum.dart'; +export 'user_widgets/files_stream_builder.dart'; +export 'user_widgets/photo_provider.dart'; +export 'user_widgets/video_provider.dart'; +export 'user_widgets/media_provider.dart'; +export 'views/bottom_sheet.dart'; +export 'views/gallery_picker_view/gallery_picker_view.dart'; import 'package:flutter/material.dart'; +import 'package:gallery_picker/models/gallery_media.dart'; import 'package:get/get.dart'; -import 'package:permission_handler/permission_handler.dart'; import '../../controller/gallery_controller.dart'; +import 'controller/bottom_sheet_controller.dart'; +import 'controller/picker_listener.dart'; import 'models/config.dart'; import 'models/media_file.dart'; -import 'views/album_category_view/gallery_categories_widget.dart'; -import 'views/album_view/gallery_category_view_page.dart'; -import 'views/album_view/gallery_category_view_widget.dart'; -import 'views/hero_page.dart'; +import 'views/gallery_picker_view/gallery_picker_view.dart'; -void disposeGalleryPicker() { - GetInstance().delete(); -} +class GalleryPicker { + static Stream> get listenSelectedFiles { + var controller = Get.put(PickerListener()); + return controller.stream; + } -class GalleryPicker extends StatefulWidget { - Config? config; - Function(List selectedMedias) onSelect; - Widget Function(String tag, MediaFile media, BuildContext context)? - heroBuilder; - Widget Function(List medias, BuildContext context)? - multipleMediaBuilder; - bool startWithRecent; - GalleryPicker( - {super.key, - this.config, - required this.onSelect, - this.heroBuilder, - this.multipleMediaBuilder, - this.startWithRecent = false}); + static void disposeSelectedFilesListener() { + if (GetInstance().isRegistered()) { + Get.find().dispose(); + } + } - @override - State createState() => _GalleryPickerState(); -} - -class _GalleryPickerState extends State { - late PhoneGalleryController galleryController; - late PageController _scrollController; - bool noPhotoSeleceted = true; - late Config config; - @override - void initState() { - _scrollController = - PageController(initialPage: widget.startWithRecent ? 0 : 1); + static void dispose() { if (GetInstance().isRegistered()) { - galleryController = Get.find(); - config = galleryController.config; + Get.find().disposeController(); + } + if (GetInstance().isRegistered()) { + Get.find().disposeController(); + } + } + + static Future?> pickMedias( + {Config? config, + bool startWithRecent = false, + List? initSelectedMedias, + required BuildContext context}) async { + List? medias; + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GalleryPickerView( + onSelect: (mediasTmp) { + medias = mediasTmp; + }, + config: config, + initSelectedMedias: initSelectedMedias, + startWithRecent: startWithRecent, + )), + ); + return medias; + } + + static Future pickMediasWithBuilder( + {Config? config, + required Widget Function(List medias, BuildContext context)? + multipleMediasBuilder, + Widget Function(String tag, MediaFile media, BuildContext context)? + heroBuilder, + List? initSelectedMedias, + bool startWithRecent = false, + required BuildContext context}) async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GalleryPickerView( + onSelect: (medias) {}, + multipleMediasBuilder: multipleMediasBuilder, + heroBuilder: heroBuilder, + config: config, + initSelectedMedias: initSelectedMedias, + startWithRecent: startWithRecent, + )), + ); + } + + static Future openSheet() async { + if (GetInstance().isRegistered()) { + await Get.find().open(); + } + } + + static Future closeSheet() async { + if (GetInstance().isRegistered()) { + await Get.find().close(); + } + } + + static bool get isSheetOpened { + if (GetInstance().isRegistered()) { + return Get.find().sheetController.isExpanded; } else { - galleryController = Get.put(PhoneGalleryController(widget.config, - onSelect: widget.onSelect, - heroBuilder: widget.heroBuilder, - multipleMediaBuilder: widget.multipleMediaBuilder, - isRecent: widget.startWithRecent)); - config = galleryController.config; - } - if (!galleryController.isInitialized) { - initializeGallery(); - } - super.initState(); - } - - @override - void dispose() { - super.dispose(); - } - - Future initializeGallery() async { - if (await _promptPermissionSetting()) { - print("granted"); - await galleryController.initializeAlbums(); + return false; } } - Future _promptPermissionSetting() async { - if (Platform.isIOS && - await Permission.storage.request().isGranted && - await Permission.photos.request().isGranted || - Platform.isAndroid && await Permission.storage.request().isGranted) { - return true; - } - return false; - } - - @override - Widget build(BuildContext context) { - double width = MediaQuery.of(context).size.width; - double height = MediaQuery.of(context).size.height - 119; - return GetBuilder(builder: (galleryController) { - return galleryController.selectedAlbum == null - ? Scaffold( - backgroundColor: config.backgroundColor, - appBar: AppBar( - elevation: 0, - backgroundColor: config.appbarColor, - leading: TextButton( - onPressed: () { - galleryController.switchPickerMode(false); - }, - child: Icon( - Icons.arrow_back, - color: config.appbarIconColor, - )), - title: getTitle(), - actions: [ - !galleryController.pickerMode && galleryController.isRecent - ? TextButton( - onPressed: () { - galleryController.switchPickerMode(true); - }, - child: Icon( - Icons.check_box_outlined, - color: config.appbarIconColor, - )) - : const SizedBox() - ], - ), - body: Column( - children: [ - Container( - width: width, - height: 48, - color: config.appbarColor, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Container( - decoration: galleryController.isRecent - ? BoxDecoration( - border: Border( - bottom: BorderSide( - color: config.underlineColor, - width: 3.0, - ), - )) - : null, - height: 48, - width: width / 2, - child: TextButton( - onPressed: () { - _scrollController.animateTo(0, - duration: const Duration(milliseconds: 50), - curve: Curves.easeIn); - setState(() { - galleryController.isRecent = true; - galleryController.switchPickerMode(false); - }); - }, - child: Text(config.recents, - style: galleryController.isRecent - ? config.selectedMenuStyle - : config.unselectedMenuStyle)), - ), - Container( - decoration: !galleryController.isRecent - ? BoxDecoration( - border: Border( - bottom: BorderSide( - color: config.underlineColor, - width: 3.0, - ), - )) - : null, - height: 48, - width: width / 2, - child: TextButton( - onPressed: () { - _scrollController.animateTo(width, - duration: const Duration(milliseconds: 50), - curve: Curves.easeIn); - galleryController.isRecent = false; - galleryController.switchPickerMode(false); - }, - child: Text( - config.gallery, - style: galleryController.isRecent - ? config.unselectedMenuStyle - : config.selectedMenuStyle, - )), - ) - ], - ), - ), - Expanded( - child: PageView( - controller: _scrollController, - onPageChanged: (value) { - if (value == 0) { - galleryController.isRecent = true; - galleryController.switchPickerMode(false); - } else { - galleryController.isRecent = false; - galleryController.switchPickerMode(false); - } - }, - scrollDirection: Axis.horizontal, - children: [ - SizedBox( - width: width, - height: height - 48, - child: galleryController.isInitialized - ? GalleryCategoryViewWidget( - galleryAlbum: galleryController.recent!, - ) - : const Center( - child: CircularProgressIndicator( - color: Colors.grey, - ))), - SizedBox( - width: width, - height: height - 48, - child: GalleryCategoriesWidget()) - ]), - ), - ], - ), - ) - : GalleryAlbumViewPage( - album: galleryController.selectedAlbum!, - ); - }); - } - - Widget getTitle() { - if (galleryController.pickerMode && - galleryController.selectedFiles.isEmpty) { - return Text( - config.tapPhotoSelect, - style: config.appbarTextStyle, - ); - } else if (galleryController.pickerMode && - galleryController.selectedFiles.isNotEmpty) { - return Text( - "${galleryController.selectedFiles.length} ${config.selected}", - style: config.appbarTextStyle, - ); - } else { - return const SizedBox(); - } + static Future get collectGallery async { + return await PhoneGalleryController.collectGallery; } } diff --git a/lib/models/config.dart b/lib/models/config.dart index 75b5980..d7f0598 100644 --- a/lib/models/config.dart +++ b/lib/models/config.dart @@ -15,7 +15,7 @@ class Config { unselectedMenuStyle; String recents, gallery, lastMonth, lastWeek, tapPhotoSelect, selected; List months; - Mode? mode; + Mode mode; Config( {Color? backgroundColor, @@ -47,7 +47,7 @@ class Config { "November", "December" ], - this.mode = Mode.dark, + this.mode = Mode.light, Widget? selectIcon}) { if (backgroundColor == null) { this.backgroundColor = mode == Mode.dark diff --git a/lib/models/gallery_album.dart b/lib/models/gallery_album.dart index 3d71727..786e0b8 100644 --- a/lib/models/gallery_album.dart +++ b/lib/models/gallery_album.dart @@ -13,6 +13,12 @@ class GalleryAlbum { int get count => dateCategories.expand((element) => element.files).toList().length; String? get name => album.name; + + List get medias { + return dateCategories + .expand((element) => element.files) + .toList(); + } late AlbumType type; @@ -37,9 +43,9 @@ class GalleryAlbum { Future initialize() async { List dateCategory = []; - for (var file in sortAlbumMediaDates((await album.listMedia()).items)) { - MediaFile mediaFile = MediaFile(mediaFile: file); - String name = getDateCategory(file); + for (var medium in sortAlbumMediaDates((await album.listMedia()).items)) { + MediaFile mediaFile = MediaFile(medium: medium); + String name = getDateCategory(medium); if (dateCategory.any((element) => element.name == name)) { dateCategory .singleWhere((element) => element.name == name) @@ -59,7 +65,7 @@ class GalleryAlbum { DateTime? get lastDate { if (dateCategories.isNotEmpty) { - return dateCategories.first.files.first.mediaFile.lastDate; + return dateCategories.first.files.first.medium.lastDate; } else { return null; } @@ -69,9 +75,6 @@ class GalleryAlbum { dateCategories.expand((element) => element.files).toList(); String getDateCategory(Medium mediaFile) { - //print("Creation Date: " + mediaFile.creationDateae.toString()); - //print("Modified Date: " + mediaFile.modifiedDate!.toString()); - //print("-------------------------------------------------------"); if (daysBetween(mediaFile.lastDate!) <= 3) { return "Recent"; } else if (daysBetween(mediaFile.lastDate!) > 3 && @@ -112,19 +115,19 @@ class GalleryAlbum { for (var category in dateCategories) { category.files.sort((a, b) { - if (a.mediaFile.lastDate == null) { + if (a.medium.lastDate == null) { return 1; - } else if (b.mediaFile.lastDate == null) { + } else if (b.medium.lastDate == null) { return -1; } else { - return b.mediaFile.lastDate!.compareTo(a.mediaFile.lastDate!); + return b.medium.lastDate!.compareTo(a.medium.lastDate!); } }); } } void addFile(MediaFile file) { - String name = getDateCategory(file.mediaFile); + String name = getDateCategory(file.medium); if (dateCategories.any((element) => element.name == name)) { dateCategories .singleWhere((element) => element.name == name) diff --git a/lib/models/gallery_media.dart b/lib/models/gallery_media.dart new file mode 100644 index 0000000..78b32d8 --- /dev/null +++ b/lib/models/gallery_media.dart @@ -0,0 +1,19 @@ +import 'gallery_album.dart'; + +class GalleryMedia { + List albums; + GalleryAlbum? get recent { + return albums.singleWhere((element) => element.album.name == "All"); + } + + GalleryAlbum? getAlbum(String name) { + try { + return albums.singleWhere((element) => element.album.name == name); + } catch (e) { + print(e); + return null; + } + } + + GalleryMedia(this.albums); +} diff --git a/lib/models/media_file.dart b/lib/models/media_file.dart index fe05a08..b0d1c4a 100644 --- a/lib/models/media_file.dart +++ b/lib/models/media_file.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; @@ -6,32 +7,68 @@ import 'package:video_thumbnail/video_thumbnail.dart'; import '../controller/gallery_controller.dart'; class MediaFile { - Medium mediaFile; + Medium medium; MediumType? type; Uint8List? thumbnail; - bool thumbnailFailed=false; - - MediaFile({required this.mediaFile}) { - type = mediaFile.mediumType; + Uint8List? data; + late String id; + bool thumbnailFailed = false; + File? file; + MediaFile({required this.medium}) { + type = medium.mediumType; + id = medium.id; } Future getThumbnail() async { - print("zooooortlamaca"); try { thumbnail = - Uint8List.fromList(await mediaFile.getThumbnail(highQuality: true)); + Uint8List.fromList(await medium.getThumbnail(highQuality: true)); } catch (e) { thumbnailFailed = true; } } - void unselect() { - Get.find().unselectMedia(this); + Future getFile() async { + file = await medium.getFile(); + return file!; } - void select() { - Get.find().selectMedia(this); + Future getData() async { + if (file == null) { + await getFile(); + } + data = await file!.readAsBytes(); + return data!; } - bool get isSelected => - Get.find().isSelectedMedia(this); + void unselect({PhoneGalleryController? controller}) { + if (controller != null) { + controller.unselectMedia(this); + } else { + if (GetInstance().isRegistered()) { + Get.find().unselectMedia(this); + } + } + } + + void select({PhoneGalleryController? controller}) { + if (controller != null) { + controller.selectMedia(this); + } else { + if (GetInstance().isRegistered()) { + Get.find().selectMedia(this); + } + } + } + + bool? isSelected({PhoneGalleryController? controller}) { + if (controller != null) { + return controller.isSelectedMedia(this); + } else { + if (GetInstance().isRegistered()) { + return Get.find().isSelectedMedia(this); + } else { + return null; + } + } + } } diff --git a/lib/trash/album_page.dart b/lib/trash/album_page.dart deleted file mode 100644 index 45380b5..0000000 --- a/lib/trash/album_page.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:photo_gallery/photo_gallery.dart'; -import 'package:transparent_image/transparent_image.dart'; -import '/trash/viewer_page.dart'; - -class AlbumPage extends StatefulWidget { - final Album album; - - AlbumPage(Album album) : album = album; - - @override - State createState() => AlbumPageState(); -} - -class AlbumPageState extends State { - List? _media; - - @override - void initState() { - super.initState(); - initAsync(); - } - - void initAsync() async { - MediaPage mediaPage = await widget.album.listMedia(); - setState(() { - _media = mediaPage.items; - }); - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar( - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios), - onPressed: () => Navigator.of(context).pop(), - ), - title: Text(widget.album.name ?? "Unnamed Album"), - ), - body: GridView.count( - crossAxisCount: 4, - mainAxisSpacing: 1.0, - crossAxisSpacing: 1.0, - children: [ - ...?_media?.map( - (medium) => GestureDetector( - onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => ViewerPage(medium))), - child: Container( - color: Colors.grey[300], - child: FadeInImage( - fit: BoxFit.cover, - placeholder: MemoryImage(kTransparentImage), - image: ThumbnailProvider( - mediumId: medium.id, - mediumType: medium.mediumType, - highQuality: true, - ), - ), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/trash/video_provider.dart b/lib/trash/video_provider.dart deleted file mode 100644 index dc70858..0000000 --- a/lib/trash/video_provider.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:photo_gallery/photo_gallery.dart'; -import 'package:video_player/video_player.dart'; - -class VideoProvider extends StatefulWidget { - final String mediumId; - - const VideoProvider({ - required this.mediumId, - }); - - @override - _VideoProviderState createState() => _VideoProviderState(); -} - -class _VideoProviderState extends State { - VideoPlayerController? _controller; - File? _file; - - @override - void initState() { - WidgetsBinding.instance.addPostFrameCallback((_) { - initAsync(); - }); - super.initState(); - } - - Future initAsync() async { - try { - _file = await PhotoGallery.getFile(mediumId: widget.mediumId); - _controller = VideoPlayerController.file(_file!); - _controller?.initialize().then((_) { - // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. - setState(() {}); - }); - } catch (e) { - print("Failed : $e"); - } - } - - @override - Widget build(BuildContext context) { - return _controller == null || !_controller!.value.isInitialized - ? Container() - : Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - AspectRatio( - aspectRatio: _controller!.value.aspectRatio, - child: VideoPlayer(_controller!), - ), - TextButton( - onPressed: () { - setState(() { - _controller!.value.isPlaying - ? _controller!.pause() - : _controller!.play(); - }); - }, - child: Icon( - _controller!.value.isPlaying ? Icons.pause : Icons.play_arrow, - ), - ), - ], - ); - } -} diff --git a/lib/trash/viewer_page.dart b/lib/trash/viewer_page.dart deleted file mode 100644 index 6e34db9..0000000 --- a/lib/trash/viewer_page.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:photo_gallery/photo_gallery.dart'; -import '/trash/video_provider.dart'; -import 'package:transparent_image/transparent_image.dart'; - -class ViewerPage extends StatelessWidget { - final Medium medium; - - ViewerPage(Medium medium) : medium = medium; - - @override - Widget build(BuildContext context) { - - DateTime? date = medium.creationDate ?? medium.modifiedDate; - - return MaterialApp( - home: Scaffold( - appBar: AppBar( - leading: IconButton( - onPressed: () => Navigator.of(context).pop(), - icon: const Icon(Icons.arrow_back_ios), - ), - title: date != null ? Text(date.toLocal().toString()) : null, - ), - body: Container( - alignment: Alignment.center, - child: medium.mediumType == MediumType.image - ? FadeInImage( - fit: BoxFit.cover, - placeholder: MemoryImage(kTransparentImage), - image: PhotoProvider(mediumId: medium.id), - ) - : VideoProvider( - mediumId: medium.id, - ), - ), - ), - ); - } -} diff --git a/lib/user_widgets/album_categories_view.dart b/lib/user_widgets/album_categories_view.dart new file mode 100644 index 0000000..4ecc901 --- /dev/null +++ b/lib/user_widgets/album_categories_view.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:gallery_picker/models/gallery_album.dart'; +import '../../user_widgets/thumbnailAlbum.dart'; +import 'package:get/get.dart'; +import 'package:photo_gallery/photo_gallery.dart'; +import '../../models/config.dart'; +import 'package:transparent_image/transparent_image.dart'; +import '../../../controller/gallery_controller.dart'; +import '../models/mode.dart'; + +class AlbumCategoriesView extends StatelessWidget { + List albums; + Function(GalleryAlbum album)? onPressed; + Function(GalleryAlbum album, bool)? onHover; + Function(GalleryAlbum album)? onLongPress; + Function(GalleryAlbum album, bool)? onFocusChange; + final Color categoryFailIconColor, categoryBackgroundColor; + final Mode mode; + AlbumCategoriesView( + {super.key, + required this.albums, + required this.categoryBackgroundColor, + required this.categoryFailIconColor, + required this.mode, + this.onFocusChange, + this.onHover, + this.onLongPress, + this.onPressed}); + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + return GridView.count( + crossAxisCount: 2, + mainAxisSpacing: 3.0, + crossAxisSpacing: 3.0, + children: [ + ...albums.map( + (album) => TextButton( + onPressed: () { + if (onPressed != null) { + onPressed!(album); + } + }, + onFocusChange: (value) { + if (onFocusChange != null) { + onFocusChange!(album, value); + } + }, + onHover: (value) { + if (onHover != null) { + onHover!(album, value); + } + }, + onLongPress: () { + if (onLongPress != null) { + onLongPress!(album); + } + }, + child: Stack(fit: StackFit.passthrough, children: [ + ThumbnailAlbum( + album: album, + failIconColor: categoryFailIconColor, + mode: mode, + backgroundColor: categoryBackgroundColor), + ]), + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/user_widgets/album_medias.dart b/lib/user_widgets/album_medias.dart new file mode 100644 index 0000000..4a23f07 --- /dev/null +++ b/lib/user_widgets/album_medias.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import '/models/gallery_album.dart'; +import 'date_category_view.dart'; + +// ignore: must_be_immutable +class AlbumMediasView extends StatelessWidget { + TextStyle? textStyle; + Widget Function(BuildContext)? onFileErrorBuilder; + AlbumMediasView( + {super.key, required this.galleryAlbum,this.textStyle}); + GalleryAlbum galleryAlbum; + @override + Widget build(BuildContext context) { + return Stack( + children: [ + ListView( + children: [ + for (var category in galleryAlbum.dateCategories) + DateCategoryWiew( + category: category, + textStyle: textStyle, + ), + ], + ), + ], + ); + } +} diff --git a/lib/user_widgets/date_category_view.dart b/lib/user_widgets/date_category_view.dart new file mode 100644 index 0000000..e37719f --- /dev/null +++ b/lib/user_widgets/date_category_view.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../../../controller/gallery_controller.dart'; +import '../models/media_file.dart'; +import '../views/gridview_static.dart'; +import '/models/gallery_album.dart'; +import 'thumbnail_media.dart'; + +class DateCategoryWiew extends StatelessWidget { + TextStyle? textStyle; + final Widget Function(MediaFile media, BuildContext context)? + onMediaErrorBuilder; + DateCategoryWiew( + {super.key, + required this.category, + this.textStyle, + this.onMediaErrorBuilder}); + DateCategory category; + + int getRowCount() { + if (category.files.length % 4 != 0) { + return category.files.length ~/ 4 + 1; + } else { + return category.files.length ~/ 4; + } + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + category.name, + style: textStyle, + ), + ), + ), + GridViewStatic( + size: MediaQuery.of(context).size.width, + padding: EdgeInsets.zero, + crossAxisCount: 4, + mainAxisSpacing: 1.0, + crossAxisSpacing: 1.0, + children: [ + ...category.files.map( + (medium) => ThumbnailMedia( + media: medium, + onErrorBuilder: onMediaErrorBuilder, + ), + ), + ], + ), + ], + ); + }, + ); + } +} diff --git a/lib/user_widgets/files_stream_builder.dart b/lib/user_widgets/files_stream_builder.dart new file mode 100644 index 0000000..0c3381d --- /dev/null +++ b/lib/user_widgets/files_stream_builder.dart @@ -0,0 +1,24 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/src/widgets/container.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:get/get.dart'; + +import '../controller/picker_listener.dart'; +import '../models/media_file.dart'; + +class FilesStreamBuilder extends StatelessWidget { + final Widget Function(List? medias, BuildContext context) builder; + FilesStreamBuilder({super.key, required this.builder}) { + Get.put(PickerListener()); + } + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: Get.find().stream, + builder: ((context, snapshot) { + print("snapshot:${snapshot.data}"); + return builder(snapshot.data, context); + })); + } +} diff --git a/lib/user_widgets/media_provider.dart b/lib/user_widgets/media_provider.dart new file mode 100644 index 0000000..ea7cccd --- /dev/null +++ b/lib/user_widgets/media_provider.dart @@ -0,0 +1,26 @@ +import 'package:flutter/src/widgets/container.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:gallery_picker/gallery_picker.dart'; +import 'package:photo_gallery/photo_gallery.dart' as photo_gallery; + +class MediaProvider extends StatelessWidget { + final MediaFile media; + final double? width, height; + const MediaProvider( + {super.key, required this.media, this.width, this.height}); + + @override + Widget build(BuildContext context) { + return media.type == photo_gallery.MediumType.image + ? PhotoProvider( + media: media, + width: width, + height: height, + ) + : VideoProvider( + media: media, + width: width, + height: height, + ); + } +} diff --git a/lib/user_widgets/photo_provider.dart b/lib/user_widgets/photo_provider.dart new file mode 100644 index 0000000..a61480c --- /dev/null +++ b/lib/user_widgets/photo_provider.dart @@ -0,0 +1,67 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:photo_gallery/photo_gallery.dart'; +import 'package:video_player/video_player.dart'; + +import '../models/media_file.dart'; + +class PhotoProvider extends StatefulWidget { + final MediaFile media; + final BoxFit fit; + final double? width, height; + + const PhotoProvider({ + super.key, + required this.media, + this.fit = BoxFit.contain, + this.width, + this.height, + }); + + @override + _PhotoProviderState createState() => _PhotoProviderState(); +} + +class _PhotoProviderState extends State { + VideoPlayerController? _controller; + late MediaFile media; + @override + void initState() { + media = widget.media; + WidgetsBinding.instance.addPostFrameCallback((_) { + initMedia(); + }); + super.initState(); + } + + Future initMedia() async { + await media.getData(); + if (mounted) { + setState(() {}); + } + } + + bool anyProcess = false; + @override + Widget build(BuildContext context) { + if (media != widget.media) { + media = widget.media; + if (media.data == null) { + initMedia(); + } + } + return media.data == null + ? Container( + width: widget.width, + height: widget.height, + ) + : Image.memory( + media.data!, + width: widget.width, + height: widget.height, + fit: widget.fit, + ); + } +} diff --git a/lib/views/thumbnailAlbum.dart b/lib/user_widgets/thumbnailAlbum.dart similarity index 72% rename from lib/views/thumbnailAlbum.dart rename to lib/user_widgets/thumbnailAlbum.dart index a38362a..e81498b 100644 --- a/lib/views/thumbnailAlbum.dart +++ b/lib/user_widgets/thumbnailAlbum.dart @@ -1,27 +1,22 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:gallery_picker/models/gallery_album.dart'; -import 'package:get/get.dart'; -import '../controller/gallery_controller.dart'; +import '/models/gallery_album.dart'; import '../models/config.dart'; import '../models/mode.dart'; -import '/models/media_file.dart'; -import 'package:photo_gallery/photo_gallery.dart'; -import 'package:transparent_image/transparent_image.dart'; class ThumbnailAlbum extends StatelessWidget { final GalleryAlbum album; - final Color failIconColor; - final Config config = Get.find().config; - ThumbnailAlbum({super.key, required this.album, required this.failIconColor}); + final Color failIconColor, backgroundColor; + final Mode mode; + ThumbnailAlbum({super.key, required this.album, required this.failIconColor,required this.mode,required this.backgroundColor}); Color adjustFailedBgColor() { - if (config.mode == Mode.dark) { + if (mode == Mode.dark) { return lighten( - config.backgroundColor, + backgroundColor, ); } else { - return darken(config.backgroundColor); + return darken(backgroundColor); } } @@ -58,26 +53,12 @@ class ThumbnailAlbum extends StatelessWidget { color: failIconColor, )) else if (album.thumbnail != null) - FadeInImage( - fadeInDuration: const Duration(milliseconds: 200), + Image.memory( + Uint8List.fromList(album.thumbnail!), fit: BoxFit.cover, - placeholder: MemoryImage(kTransparentImage), - image: MemoryImage(Uint8List.fromList(album.thumbnail!)), ) else const SizedBox(), - Positioned( - bottom: 10, - left: 10, - child: Icon( - album.type == AlbumType.video - ? Icons.video_camera_back - : album.type == AlbumType.image - ? Icons.image - : Icons.folder, - color: Colors.white, - size: 20, - )), Opacity( opacity: 0.5, child: Container( diff --git a/lib/user_widgets/thumbnail_media.dart b/lib/user_widgets/thumbnail_media.dart new file mode 100644 index 0000000..22434dc --- /dev/null +++ b/lib/user_widgets/thumbnail_media.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../../controller/gallery_controller.dart'; +import '../../models/config.dart'; +import '../../models/mode.dart'; +import '/models/media_file.dart'; +import 'package:photo_gallery/photo_gallery.dart'; +import 'package:transparent_image/transparent_image.dart'; + +class ThumbnailMedia extends StatelessWidget { + final MediaFile media; + final bool noIcon; + final Widget Function(MediaFile media,BuildContext context)? onErrorBuilder; + const ThumbnailMedia({super.key, required this.media, this.onErrorBuilder,this.noIcon=false}); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: media.thumbnail == null ? media.getThumbnail() : null, + builder: (context, snapshot) { + return Stack( + fit: StackFit.passthrough, + children: [ + if (media.thumbnailFailed && onErrorBuilder == null) + Icon( + media.type == MediumType.image + ? Icons.image_not_supported + : Icons.videocam_off_rounded, + color: Colors.grey, + ) + else if (media.thumbnailFailed && onErrorBuilder == null) + onErrorBuilder!(media,context) + else if (media.thumbnail != null) + FadeInImage( + fadeInDuration: const Duration(milliseconds: 200), + fit: BoxFit.cover, + placeholder: MemoryImage(kTransparentImage), + image: MemoryImage(media.thumbnail!), + ) + else + const SizedBox(), + if (media.thumbnail != null && !media.thumbnailFailed && !noIcon) + Positioned( + bottom: 10, + left: 10, + child: Icon( + media.medium.mediumType == MediumType.video + ? Icons.video_camera_back + : null, + color: Colors.white, + size: 20, + )), + ], + ); + }); + } +} diff --git a/lib/user_widgets/video_provider.dart b/lib/user_widgets/video_provider.dart new file mode 100644 index 0000000..cfc5309 --- /dev/null +++ b/lib/user_widgets/video_provider.dart @@ -0,0 +1,110 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:photo_gallery/photo_gallery.dart'; +import 'package:video_player/video_player.dart'; + +import '../models/media_file.dart'; + +class VideoProvider extends StatefulWidget { + final MediaFile media; + final double? width, height; + + const VideoProvider({ + required this.media, + this.width, + this.height, + }); + + @override + _VideoProviderState createState() => _VideoProviderState(); +} + +class _VideoProviderState extends State { + VideoPlayerController? _controller; + File? _file; + late MediaFile media; + @override + void initState() { + media = widget.media; + WidgetsBinding.instance.addPostFrameCallback((_) { + initMedia(); + }); + super.initState(); + } + + Future initMedia() async { + try { + if (media.file == null) { + _file = await media.getFile(); + } else { + _file = media.file; + } + _controller = VideoPlayerController.file(_file!); + _controller?.initialize().then((_) { + setState(() {}); + }); + } catch (e) { + print("Failed : $e"); + } + } + + @override + void dispose() { + disposeController(); + super.dispose(); + } + + void disposeController() { + if (_controller != null) { + _controller!.dispose(); + _controller = null; + } + } + + bool anyProcess = false; + @override + Widget build(BuildContext context) { + if (media != widget.media) { + media = widget.media; + disposeController(); + initMedia(); + } + return _controller == null || !_controller!.value.isInitialized + ? Container( + width: widget.width, + height: widget.height, + ) + : SizedBox( + width: widget.width, + height: widget.height, + child: Stack( + children: [ + AspectRatio( + aspectRatio: _controller!.value.aspectRatio, + child: Stack(children: [ + VideoPlayer(_controller!), + Center( + child: TextButton( + onPressed: () { + anyProcess = true; + setState(() { + _controller!.value.isPlaying + ? _controller!.pause() + : _controller!.play(); + }); + }, + child: Icon( + _controller!.value.isPlaying + ? Icons.pause + : Icons.play_arrow, + ), + ), + ), + ]), + ), + ], + ), + ); + } +} diff --git a/lib/views/ThumbnailMedia.dart b/lib/views/ThumbnailMedia.dart index c6a3cb5..33b5169 100644 --- a/lib/views/ThumbnailMedia.dart +++ b/lib/views/ThumbnailMedia.dart @@ -10,8 +10,8 @@ import 'package:transparent_image/transparent_image.dart'; class ThumbnailMedia extends StatelessWidget { final MediaFile file; final Color failIconColor; - final Config config = Get.find().config; - ThumbnailMedia({super.key, required this.file, required this.failIconColor}); + final Config config; + ThumbnailMedia({super.key, required this.file, required this.failIconColor,required this.config}); Color adjustFailedBgColor() { if (config.mode == Mode.dark) { @@ -58,7 +58,7 @@ class ThumbnailMedia extends StatelessWidget { )) else if (file.thumbnail != null) Hero( - tag: file.mediaFile.id, + tag: file.medium.id, child: FadeInImage( fadeInDuration: const Duration(milliseconds: 200), fit: BoxFit.cover, @@ -73,7 +73,7 @@ class ThumbnailMedia extends StatelessWidget { bottom: 10, left: 10, child: Icon( - file.mediaFile.mediumType == MediumType.video + file.medium.mediumType == MediumType.video ? Icons.video_camera_back : null, color: Colors.white, diff --git a/lib/views/album_categories_view/album_categories_view.dart b/lib/views/album_categories_view/album_categories_view.dart new file mode 100644 index 0000000..cb9f7eb --- /dev/null +++ b/lib/views/album_categories_view/album_categories_view.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import '../../user_widgets/thumbnailAlbum.dart'; +import 'package:get/get.dart'; +import 'package:photo_gallery/photo_gallery.dart'; +import '../../models/config.dart'; +import '../album_view/album_page.dart'; +import 'package:transparent_image/transparent_image.dart'; +import '../../../controller/gallery_controller.dart'; + +class AlbumCategoriesView extends StatelessWidget { + PhoneGalleryController controller; + late Config config; + AlbumCategoriesView(this.controller, {super.key}) { + config = controller.config; + } + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + return GridView.count( + crossAxisCount: 2, + mainAxisSpacing: 3.0, + crossAxisSpacing: 3.0, + children: [ + ...controller.galleryAlbums.map( + (album) => GestureDetector( + onTap: () => controller.changeAlbum(album), + child: Stack(fit: StackFit.passthrough, children: [ + ThumbnailAlbum( + album: album, + failIconColor: config.appbarIconColor, + backgroundColor: config.backgroundColor, + mode: config.mode, + ), + ]), + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/views/album_category_view/gallery_categories_widget.dart b/lib/views/album_category_view/gallery_categories_widget.dart deleted file mode 100644 index 4b477a9..0000000 --- a/lib/views/album_category_view/gallery_categories_widget.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:gallery_picker/views/thumbnailAlbum.dart'; -import 'package:get/get.dart'; -import 'package:photo_gallery/photo_gallery.dart'; -import '../../models/config.dart'; -import '../album_view/gallery_category_view_page.dart'; -import 'package:transparent_image/transparent_image.dart'; -import '../../../controller/gallery_controller.dart'; - -class GalleryCategoriesWidget extends StatelessWidget { - var galleryController = Get.find(); - late Config config; - GalleryCategoriesWidget({super.key}) { - config = galleryController.config; - } - @override - Widget build(BuildContext context) { - return Container( - child: LayoutBuilder( - builder: (context, constraints) { - return GridView.count( - crossAxisCount: 2, - mainAxisSpacing: 3.0, - crossAxisSpacing: 3.0, - children: [ - ...galleryController.galleryAlbums.map( - (album) => GestureDetector( - onTap: () => galleryController.changeAlbum(album), - child: Stack(fit: StackFit.passthrough, children: [ - ThumbnailAlbum(album: album, failIconColor: galleryController.config.appbarIconColor), - ]), - ), - ), - ], - ); - }, - ), - ); - } -} diff --git a/lib/views/album_view/album_appbar.dart b/lib/views/album_view/album_appbar.dart new file mode 100644 index 0000000..33a7764 --- /dev/null +++ b/lib/views/album_view/album_appbar.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/container.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:gallery_picker/models/gallery_album.dart'; +import 'package:get/get.dart'; +import '../../controller/bottom_sheet_controller.dart'; +import '../../controller/gallery_controller.dart'; +import '../gallery_picker_view/tappable_appbar.dart'; + +class AlbumAppBar extends StatelessWidget with PreferredSizeWidget { + PhoneGalleryController controller; + BottomSheetController? bottomSheetController; + GalleryAlbum album; + AlbumAppBar( + {super.key, + required this.bottomSheetController, + required this.album, + required this.controller}); + + @override + Widget build(BuildContext context) { + print("zort"); + return TappableAppbar( + controller: bottomSheetController, + child: AppBar( + elevation: 0, + foregroundColor: controller.config.appbarIconColor, + backgroundColor: controller.config.appbarColor, + leading: TextButton( + onPressed: () { + controller.changeAlbum(null); + }, + child: Icon( + Icons.arrow_back, + color: controller.config.appbarIconColor, + )), + title: getTitle(), + actions: [ + !controller.pickerMode + ? TextButton( + onPressed: () { + controller.switchPickerMode(true); + }, + child: Icon( + Icons.check_box_outlined, + color: controller.config.appbarIconColor, + )) + : const SizedBox() + ], + ), + ); + } + + Widget getTitle() { + if (!controller.pickerMode && controller.selectedFiles.isEmpty) { + return Text( + album.album.name!, + style: controller.config.appbarTextStyle, + ); + } else if (controller.pickerMode && controller.selectedFiles.isEmpty) { + return Text( + controller.config.tapPhotoSelect, + style: controller.config.appbarTextStyle, + ); + } else if (controller.pickerMode && controller.selectedFiles.isNotEmpty) { + return Text( + "${controller.selectedFiles.length} ${controller.config.selected}", + style: controller.config.appbarTextStyle, + ); + } else { + return const SizedBox(); + } + } + + @override + Size get preferredSize => Size.fromHeight(48); +} diff --git a/lib/views/album_view/album_medias_view.dart b/lib/views/album_view/album_medias_view.dart new file mode 100644 index 0000000..b23aba7 --- /dev/null +++ b/lib/views/album_view/album_medias_view.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import '/models/gallery_album.dart'; +import '../../../controller/gallery_controller.dart'; +import 'date_category_view.dart'; +import 'selected_medias_view.dart'; + +// ignore: must_be_immutable +class AlbumMediasView extends StatelessWidget { + PhoneGalleryController controller; + bool singleMedia; + AlbumMediasView( + {super.key, + required this.galleryAlbum, + required this.controller, + required this.singleMedia}); + GalleryAlbum galleryAlbum; + @override + Widget build(BuildContext context) { + return Stack( + children: [ + ListView( + children: [ + for (var category in galleryAlbum.dateCategories) + DateCategoryWiew( + category: category, + controller: controller, + singleMedia: singleMedia, + ), + ], + ), + if (controller.selectedFiles.isNotEmpty) + Align( + alignment: Alignment.bottomCenter, + child: SelectedMediasView( + controller: controller, + )) + ], + ); + } +} diff --git a/lib/views/album_view/album_page.dart b/lib/views/album_view/album_page.dart new file mode 100644 index 0000000..7b146ee --- /dev/null +++ b/lib/views/album_view/album_page.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:gallery_picker/views/album_view/album_appbar.dart'; +import 'package:gallery_picker/views/gallery_picker_view/tappable_appbar.dart'; +import 'package:get/get.dart'; +import '../../../controller/gallery_controller.dart'; +import '../../../models/gallery_album.dart'; +import '../../controller/bottom_sheet_controller.dart'; +import '../../models/config.dart'; +import 'album_medias_view.dart'; +import 'selected_medias_view.dart'; + +class AlbumPage extends StatelessWidget { + bool singleMedia; + AlbumPage( + {super.key, + required this.album, + required this.controller, + required this.singleMedia, + required this.bottomSheetController}); + PhoneGalleryController controller; + BottomSheetController? bottomSheetController; + GalleryAlbum album; + @override + Widget build(BuildContext context) { + Config config = controller.config; + return Scaffold( + backgroundColor: config.backgroundColor, + appBar: AlbumAppBar( + bottomSheetController: bottomSheetController, + album: album, + controller: controller), + body: AlbumMediasView( + galleryAlbum: album, + controller: controller, + singleMedia: singleMedia, + ), + ); + } +} diff --git a/lib/views/album_view/date_category_widget.dart b/lib/views/album_view/date_category_view.dart similarity index 72% rename from lib/views/album_view/date_category_widget.dart rename to lib/views/album_view/date_category_view.dart index a6d6d6d..b9c9822 100644 --- a/lib/views/album_view/date_category_widget.dart +++ b/lib/views/album_view/date_category_view.dart @@ -5,10 +5,14 @@ import '../gridview_static.dart'; import '/models/gallery_album.dart'; import 'media_view.dart'; -class DateCategoryWidget extends StatelessWidget { - DateCategoryWidget({super.key, required this.category}){ - print(category.name); - } +class DateCategoryWiew extends StatelessWidget { + PhoneGalleryController controller; + bool singleMedia; + DateCategoryWiew( + {super.key, + required this.category, + required this.controller, + required this.singleMedia}); DateCategory category; int getRowCount() { @@ -31,7 +35,7 @@ class DateCategoryWidget extends StatelessWidget { alignment: Alignment.centerLeft, child: Text( category.name, - style: Get.find().config.textStyle, + style: controller.config.textStyle, ), ), ), @@ -43,7 +47,11 @@ class DateCategoryWidget extends StatelessWidget { crossAxisSpacing: 1.0, children: [ ...category.files.map( - (medium) => MediaView(medium), + (medium) => MediaView( + medium, + controller: controller, + singleMedia: singleMedia, + ), ), ], ), diff --git a/lib/views/album_view/gallery_category_view_page.dart b/lib/views/album_view/gallery_category_view_page.dart deleted file mode 100644 index 9871d87..0000000 --- a/lib/views/album_view/gallery_category_view_page.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import '../../../controller/gallery_controller.dart'; -import '../../../models/gallery_album.dart'; -import '../../models/config.dart'; -import 'gallery_category_view_widget.dart'; - -class GalleryAlbumViewPage extends StatelessWidget { - GalleryAlbumViewPage({super.key, required this.album}); - GalleryAlbum album; - @override - Widget build(BuildContext context) { - return GetBuilder(builder: (controller) { - Config config = controller.config; - return Scaffold( - backgroundColor: config.backgroundColor, - - appBar: AppBar( - elevation: 0, - foregroundColor: config.appbarIconColor, - backgroundColor: config.appbarColor, - leading: TextButton( - onPressed: () { - controller.changeAlbum(null); - }, - child: Icon( - Icons.arrow_back, - color: config.appbarIconColor, - )), - title: Text( - album.album.name!, - style: config.appbarTextStyle, - ), - actions: [ - !Get.find().pickerMode - ? TextButton( - onPressed: () { - Get.find().switchPickerMode(true); - }, - child: Icon( - Icons.check_box_outlined, - color: config.appbarIconColor, - )) - : const SizedBox() - ], - ), - body: GalleryCategoryViewWidget(galleryAlbum: album), - ); - }); - } -} diff --git a/lib/views/album_view/gallery_category_view_widget.dart b/lib/views/album_view/gallery_category_view_widget.dart deleted file mode 100644 index 3c3199a..0000000 --- a/lib/views/album_view/gallery_category_view_widget.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import '/models/gallery_album.dart'; -import '../../../controller/gallery_controller.dart'; -import 'date_category_widget.dart'; -import 'selected_item_viewer_widget.dart'; - -// ignore: must_be_immutable -class GalleryCategoryViewWidget extends StatelessWidget { - GalleryCategoryViewWidget({super.key, required this.galleryAlbum}); - GalleryAlbum galleryAlbum; - @override - Widget build(BuildContext context) { - return Stack(children: [ - ListView( - children: [ - for (var category in galleryAlbum.dateCategories) - DateCategoryWidget( - category: category, - ), - ], - ), - if (Get.find().selectedFiles.isNotEmpty) - Align(alignment: Alignment.bottomCenter, child: SelectedMediaWidget()) - ]); - } -} diff --git a/lib/views/album_view/media_view.dart b/lib/views/album_view/media_view.dart index e5c271f..077cad4 100644 --- a/lib/views/album_view/media_view.dart +++ b/lib/views/album_view/media_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:transparent_image/transparent_image.dart'; -import '/views/ThumbnailMedia.dart'; +import '../../controller/bottom_sheet_controller.dart'; +import '../thumbnailMedia.dart'; import 'package:get/get.dart'; import 'package:photo_gallery/photo_gallery.dart'; import '../../../controller/gallery_controller.dart'; @@ -8,8 +9,10 @@ import '../../../models/media_file.dart'; class MediaView extends StatelessWidget { final MediaFile file; - var controller = Get.find(); - MediaView(this.file, {super.key}); + PhoneGalleryController controller; + bool singleMedia; + MediaView(this.file, + {super.key, required this.controller, required this.singleMedia}); @override Widget build(BuildContext context) { return Stack( @@ -17,31 +20,63 @@ class MediaView extends StatelessWidget { children: [ GestureDetector( onLongPress: () { - file.select(); + if (singleMedia) { + if (controller.heroBuilder != null) { + Navigator.of(context).push( + MaterialPageRoute(builder: (BuildContext context) { + return controller.heroBuilder!(file.medium.id, file, context); + })); + } else { + controller.selectedFiles.add(file); + controller.onSelect(controller.selectedFiles); + controller.updatePickerListener(); + if (GetInstance().isRegistered()) { + Get.find().close(); + } else { + Navigator.pop(context); + controller.disposeController(); + } + } + } else { + file.select(controller: controller); + } }, onTap: () { if (controller.pickerMode) { - file.isSelected ? file.unselect() : file.select(); + file.isSelected(controller: controller)! + ? file.unselect(controller: controller) + : file.select(controller: controller); } else { if (controller.heroBuilder != null) { Navigator.of(context).push( MaterialPageRoute(builder: (BuildContext context) { - return controller.heroBuilder!(file.mediaFile.id, file,context); + return controller.heroBuilder!(file.medium.id, file, context); })); } else { - controller.onSelect([file]); - Navigator.pop(context); - GetInstance().delete(); + controller.selectedFiles.add(file); + controller.onSelect(controller.selectedFiles); + controller.updatePickerListener(); + if (GetInstance().isRegistered()) { + Get.find().close(); + } else { + Navigator.pop(context); + controller.disposeController(); + } } } }, child: ThumbnailMedia( - file: file, failIconColor: controller.config.appbarIconColor), + file: file, + failIconColor: controller.config.appbarIconColor, + config: controller.config, + ), ), - if (file.isSelected) + if (file.isSelected(controller: controller)!) GestureDetector( onTap: () { - file.isSelected ? file.unselect() : file.select(); + file.isSelected(controller: controller)! + ? file.unselect(controller: controller) + : file.select(controller: controller); }, child: Opacity( opacity: 0.5, diff --git a/lib/views/album_view/selected_item_viewer_widget.dart b/lib/views/album_view/selected_medias_view.dart similarity index 64% rename from lib/views/album_view/selected_item_viewer_widget.dart rename to lib/views/album_view/selected_medias_view.dart index 48ee5c5..b165702 100644 --- a/lib/views/album_view/selected_item_viewer_widget.dart +++ b/lib/views/album_view/selected_medias_view.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:photo_gallery/photo_gallery.dart'; +import '../../controller/bottom_sheet_controller.dart'; import '../../controller/gallery_controller.dart'; import '../../models/config.dart'; -class SelectedMediaWidget extends StatelessWidget { - var galleryController = Get.find(); +class SelectedMediasView extends StatelessWidget { + PhoneGalleryController controller; late Config config; - SelectedMediaWidget({super.key}) { - config = galleryController.config; + SelectedMediasView({super.key, required this.controller}) { + config = controller.config; } @override @@ -29,7 +30,7 @@ class SelectedMediaWidget extends StatelessWidget { child: ListView( scrollDirection: Axis.horizontal, children: [ - for (var selectedMedia in galleryController.selectedFiles) + for (var selectedMedia in controller.selectedFiles) Padding( padding: const EdgeInsets.only( top: 3.0, bottom: 3.0, right: 2), @@ -39,9 +40,9 @@ class SelectedMediaWidget extends StatelessWidget { image: DecorationImage( fit: BoxFit.fill, image: ThumbnailProvider( - mediumId: selectedMedia.mediaFile.id, + mediumId: selectedMedia.medium.id, mediumType: - selectedMedia.mediaFile.mediumType, + selectedMedia.medium.mediumType, highQuality: true, ))), child: SizedBox( @@ -55,25 +56,28 @@ class SelectedMediaWidget extends StatelessWidget { ), TextButton( onPressed: () { - if (galleryController.selectedFiles.length == 1 && - galleryController.heroBuilder != null) { + if (controller.selectedFiles.length == 1 && + controller.heroBuilder != null) { Navigator.of(context).push( MaterialPageRoute(builder: (BuildContext context) { - return galleryController.heroBuilder!( - galleryController.selectedFiles[0].mediaFile.id, - galleryController.selectedFiles[0],context); + return controller.heroBuilder!( + controller.selectedFiles[0].medium.id, + controller.selectedFiles[0], + context); })); - } else if (galleryController.multipleMediaBuilder != null) { + } else if (controller.multipleMediasBuilder != null) { Navigator.of(context).push( MaterialPageRoute(builder: (BuildContext context) { - return galleryController - .multipleMediaBuilder!(galleryController.selectedFiles,context); + return controller.multipleMediasBuilder!( + controller.selectedFiles, context); })); } else { - galleryController.onSelect(galleryController.selectedFiles); - Navigator.pop(context); - if (!galleryController.isRecent) { + controller.onSelect(controller.selectedFiles); + if (GetInstance().isRegistered()) { + Get.find().close(); + } else { Navigator.pop(context); + controller.disposeController(); } } }, diff --git a/lib/views/bottom_sheet.dart b/lib/views/bottom_sheet.dart new file mode 100644 index 0000000..d962a26 --- /dev/null +++ b/lib/views/bottom_sheet.dart @@ -0,0 +1,154 @@ +import 'package:bottom_sheet_bar/bottom_sheet_bar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/container.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:gallery_picker/controller/gallery_controller.dart'; +import '/gallery_picker.dart'; +import '/views/gallery_picker_view/gallery_picker_view.dart'; +import 'package:get/get.dart'; +import '../controller/bottom_sheet_controller.dart'; + +class BottomSheetLayout extends StatefulWidget { + final Widget child; + final Config? config; + final List? initSelectedMedias; + final Function(List selectedMedias) onSelect; + final Widget Function(String tag, MediaFile media, BuildContext context)? + heroBuilder; + final Widget Function(List medias, BuildContext context)? + multipleMediasBuilder; + final bool startWithRecent; + BottomSheetLayout( + {super.key, + required this.child, + required this.onSelect, + this.config, + this.heroBuilder, + this.initSelectedMedias, + this.multipleMediasBuilder, + this.startWithRecent = true}){ + if(initSelectedMedias!=null&&GetInstance().isRegistered()){ + Get.find().updateSelectedFiles(initSelectedMedias!); + } + } + + @override + State createState() => _BottomSheetLayoutState(); +} + +class _BottomSheetLayoutState extends State { + BuildContext? collapsedContext; + bool viewCollapsedPicker = false; + BottomSheetBarController bottomSheetBarController = + BottomSheetBarController(); + late BottomSheetController controller; + + @override + void initState() { + controller = Get.put(BottomSheetController(bottomSheetBarController)); + super.initState(); + } + + check() async { + var sheetController = controller.sheetController; + if (collapsedContext != null) { + final RenderBox renderBox = + collapsedContext!.findRenderObject() as RenderBox; + if (renderBox.size.height > 200 && + !sheetController.isExpanded && + !viewCollapsedPicker) { + await Future.delayed(Duration(milliseconds: 100)); + controller.appBarTapping = false; + setState(() { + viewCollapsedPicker = true; + }); + } else if ((renderBox.size.height <= 200 || sheetController.isExpanded)) { + if (viewCollapsedPicker) { + viewCollapsedPicker = false; + controller.appBarTapping = false; + await Future.delayed(Duration(milliseconds: 10)); + setState(() {}); + } + } + } + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GetBuilder(builder: (controller) { + return BottomSheetBar( + willPopScope: true, + color: Colors.transparent, + locked: + (controller.sheetController.isExpanded && !controller.appBarTapping) + ? true + : false, + controller: controller.sheetController, + expandedBuilder: (scrollController) { + check(); + return controller.sheetController.isExpanded + ? GalleryPickerView( + onSelect: widget.onSelect, + config: widget.config, + sheetController: bottomSheetBarController, + heroBuilder: widget.heroBuilder, + multipleMediasBuilder: widget.multipleMediasBuilder, + initSelectedMedias: widget.initSelectedMedias, + startWithRecent: widget.startWithRecent, + ) + : Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + color: Colors.transparent, + ); + }, + body: widget.child, + collapsed: GetBuilder( + builder: (controller) => ViewCollapsed( + picker: GalleryPickerView( + onSelect: widget.onSelect, + config: widget.config, + sheetController: bottomSheetBarController, + heroBuilder: widget.heroBuilder, + multipleMediasBuilder: widget.multipleMediasBuilder, + initSelectedMedias: widget.initSelectedMedias, + startWithRecent: widget.startWithRecent, + ), + viewPicker: controller.isClosing ? false : viewCollapsedPicker, + onBuild: (context) { + collapsedContext = context; + }), + ), + ); + }); + } +} + +class ViewCollapsed extends StatelessWidget { + final GalleryPickerView picker; + final bool viewPicker; + final Function(BuildContext context) onBuild; + ViewCollapsed({ + super.key, + required this.picker, + required this.onBuild, + required this.viewPicker, + }); + + @override + Widget build(BuildContext context) { + onBuild(context); + return Container( + height: 50, + color: Colors.transparent, + child: viewPicker ? picker : null, + ); + } +} diff --git a/lib/views/gallery_picker_view/gallery_picker_view.dart b/lib/views/gallery_picker_view/gallery_picker_view.dart new file mode 100644 index 0000000..42c2076 --- /dev/null +++ b/lib/views/gallery_picker_view/gallery_picker_view.dart @@ -0,0 +1,225 @@ +import 'package:bottom_sheet_bar/bottom_sheet_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../controller/bottom_sheet_controller.dart'; +import '../../controller/gallery_controller.dart'; +import '../../models/config.dart'; +import '../../models/media_file.dart'; +import '../album_categories_view/album_categories_view.dart'; +import '../album_view/album_page.dart'; +import '../album_view/album_medias_view.dart'; +import 'picker_appbar.dart'; +import 'reload_gallery.dart'; + +class GalleryPickerView extends StatefulWidget { + Config? config; + Function(List selectedMedias) onSelect; + Widget Function(String tag, MediaFile media, BuildContext context)? + heroBuilder; + Widget Function(List medias, BuildContext context)? + multipleMediasBuilder; + bool startWithRecent; + BottomSheetBarController? sheetController; + List? initSelectedMedias; + bool singleMedia; + GalleryPickerView( + {super.key, + this.config, + required this.onSelect, + this.initSelectedMedias, + this.singleMedia=false, + this.sheetController, + this.heroBuilder, + this.multipleMediasBuilder, + this.startWithRecent = false}); + + @override + State createState() => _GalleryPickerState(); +} + +class _GalleryPickerState extends State { + late PhoneGalleryController galleryController; + BottomSheetController? bottomSheetController; + late PageController _scrollController; + bool noPhotoSeleceted = true; + late Config config; + @override + void initState() { + _scrollController = + PageController(initialPage: widget.startWithRecent ? 0 : 1); + if (GetInstance().isRegistered()) { + galleryController = Get.find(); + config = galleryController.config; + } else { + galleryController = Get.put(PhoneGalleryController(widget.config, + onSelect: widget.onSelect, + heroBuilder: widget.heroBuilder, + multipleMediasBuilder: widget.multipleMediasBuilder, + initSelectedMedias:widget.initSelectedMedias, + isRecent: widget.startWithRecent)); + config = galleryController.config; + } + + if (widget.sheetController != null) { + if (GetInstance().isRegistered()) { + bottomSheetController = Get.find(); + } else { + bottomSheetController = + Get.put(BottomSheetController(widget.sheetController!)); + } + bottomSheetController!.galleryController = galleryController; + } + if (!galleryController.isInitialized) { + galleryController.initializeAlbums(); + } + if (galleryController.isRecent) { + _scrollController = PageController(initialPage: 0); + } + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + double width = MediaQuery.of(context).size.width; + return GetBuilder(builder: (controller) { + return GetInstance().isRegistered() + ? controller.selectedAlbum == null + ? Scaffold( + backgroundColor: config.backgroundColor, + appBar: PickerAppBar( + controller: controller, + bottomSheetController: bottomSheetController, + ), + body: Column( + children: [ + Container( + width: width, + height: 48, + color: config.appbarColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + decoration: controller.isRecent + ? BoxDecoration( + border: Border( + bottom: BorderSide( + color: config.underlineColor, + width: 3.0, + ), + )) + : null, + height: 48, + width: width / 2, + child: TextButton( + onPressed: () { + _scrollController.animateTo(0, + duration: + const Duration(milliseconds: 50), + curve: Curves.easeIn); + setState(() { + controller.isRecent = true; + controller.switchPickerMode(false); + }); + }, + child: Text(config.recents, + style: controller.isRecent + ? config.selectedMenuStyle + : config.unselectedMenuStyle)), + ), + Container( + decoration: !controller.isRecent + ? BoxDecoration( + border: Border( + bottom: BorderSide( + color: config.underlineColor, + width: 3.0, + ), + )) + : null, + height: 48, + width: width / 2, + child: TextButton( + onPressed: () { + _scrollController.animateTo(width, + duration: + const Duration(milliseconds: 50), + curve: Curves.easeIn); + controller.isRecent = false; + controller.switchPickerMode(false); + }, + child: Text( + config.gallery, + style: controller.isRecent + ? config.unselectedMenuStyle + : config.selectedMenuStyle, + )), + ) + ], + ), + ), + Expanded( + child: PageView( + controller: _scrollController, + onPageChanged: (value) { + if (value == 0) { + controller.isRecent = true; + controller.switchPickerMode(false); + } else { + controller.isRecent = false; + controller.switchPickerMode(false); + } + }, + scrollDirection: Axis.horizontal, + children: [ + controller.isInitialized + ? AlbumMediasView( + galleryAlbum: controller.recent!, + controller: controller, + singleMedia:widget.singleMedia + ) + : const Center( + child: CircularProgressIndicator( + color: Colors.grey, + )), + AlbumCategoriesView(controller) + ]), + ), + ], + ), + ) + : AlbumPage( + controller: controller, + album: controller.selectedAlbum!, + singleMedia: widget.singleMedia, + bottomSheetController: bottomSheetController, + ) + : ReloadGallery( + config, + onpressed: () async { + if (widget.sheetController != null) { + bottomSheetController = + Get.put(BottomSheetController(widget.sheetController!)); + } + galleryController = Get.put(PhoneGalleryController(config, + onSelect: widget.onSelect, + heroBuilder: widget.heroBuilder, + initSelectedMedias:widget.initSelectedMedias, + multipleMediasBuilder: widget.multipleMediasBuilder, + isRecent: widget.startWithRecent)); + await controller.initializeAlbums(); + if (bottomSheetController != null) { + bottomSheetController!.galleryController = galleryController; + } + setState(() {}); + }, + ); + }); + } +} diff --git a/lib/views/gallery_picker_view/picker_appbar.dart b/lib/views/gallery_picker_view/picker_appbar.dart new file mode 100644 index 0000000..eff3437 --- /dev/null +++ b/lib/views/gallery_picker_view/picker_appbar.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/container.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:get/get.dart'; + +import '../../controller/bottom_sheet_controller.dart'; +import '../../controller/gallery_controller.dart'; +import 'tappable_appbar.dart'; + +class PickerAppBar extends StatelessWidget with PreferredSizeWidget { + PhoneGalleryController controller; + BottomSheetController? bottomSheetController; + PickerAppBar( + {super.key, + required this.bottomSheetController, + required this.controller}); + + @override + Widget build(BuildContext context) { + return TappableAppbar( + controller: bottomSheetController, + child: AppBar( + elevation: 0, + backgroundColor: controller.config.appbarColor, + leading: TextButton( + onPressed: () async { + if (GetInstance().isRegistered()) { + bottomSheetController!.close(); + } else { + Navigator.pop(context); + await Future.delayed(Duration(milliseconds: 500)); + controller.disposeController(); + } + }, + child: Icon( + Icons.arrow_back, + color: controller.config.appbarIconColor, + )), + title: getTitle(), + actions: [ + !controller.pickerMode && controller.isRecent + ? TextButton( + onPressed: () { + controller.switchPickerMode(true); + }, + child: Icon( + Icons.check_box_outlined, + color: controller.config.appbarIconColor, + )) + : const SizedBox() + ], + ), + ); + } + + Widget getTitle() { + if (controller.pickerMode && controller.selectedFiles.isEmpty) { + return Text( + controller.config.tapPhotoSelect, + style: controller.config.appbarTextStyle, + ); + } else if (controller.pickerMode && controller.selectedFiles.isNotEmpty) { + return Text( + "${controller.selectedFiles.length} ${controller.config.selected}", + style: controller.config.appbarTextStyle, + ); + } else { + return const SizedBox(); + } + } + + @override + Size get preferredSize => Size.fromHeight(48); +} diff --git a/lib/views/gallery_picker_view/reload_gallery.dart b/lib/views/gallery_picker_view/reload_gallery.dart new file mode 100644 index 0000000..42c7d86 --- /dev/null +++ b/lib/views/gallery_picker_view/reload_gallery.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/container.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import '/gallery_picker.dart'; +import 'package:get/get.dart'; + +import '../../controller/gallery_controller.dart'; + +class ReloadGallery extends StatelessWidget { + late Config config; + Function() onpressed; + ReloadGallery(Config? config, {super.key, required this.onpressed}) { + this.config = config ?? Config(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: config.backgroundColor, + body: Center( + child: TextButton( + style: ButtonStyle( + foregroundColor: MaterialStateProperty.all(Colors.blue), + ), + child: Container( + height: 50, + padding: EdgeInsets.symmetric(horizontal: 30, vertical: 10), + decoration: BoxDecoration( + color: Colors.blue, borderRadius: BorderRadius.circular(10)), + child: const Center( + child: Text( + "Reload Gallery", + style: TextStyle(color: Colors.white), + ), + )), + onPressed: () { + onpressed(); + }, + )), + ); + } +} diff --git a/lib/views/gallery_picker_view/tappable_appbar.dart b/lib/views/gallery_picker_view/tappable_appbar.dart new file mode 100644 index 0000000..7a9eb18 --- /dev/null +++ b/lib/views/gallery_picker_view/tappable_appbar.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/container.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:get/get.dart'; + +import '../../controller/bottom_sheet_controller.dart'; + +class TappableAppbar extends StatelessWidget { + BottomSheetController? controller; + Widget child; + TappableAppbar({super.key, required this.controller,required this.child}); + + @override + Widget build(BuildContext context) { + return GetInstance().isRegistered() + ? GestureDetector( + onLongPressEnd: (a) { + if (GetInstance().isRegistered()) { + controller!.tapingStatus(false); + } + }, + onPanCancel: () {}, + onPanDown: (a) { + if (GetInstance().isRegistered()) { + controller!.tapingStatus(true); + } + }, + child: child, + ) + : child; + } +} diff --git a/lib/views/hero_page.dart b/lib/views/hero_page.dart deleted file mode 100644 index 4165cad..0000000 --- a/lib/views/hero_page.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/src/widgets/container.dart'; -import 'package:flutter/src/widgets/framework.dart'; - -import '../models/media_file.dart'; - -class HeroPage extends StatelessWidget { - List selectedMedias; - String heroTag; - Widget child; - HeroPage( - {super.key, - required this.selectedMedias, - required this.heroTag, - required this.child}); - - @override - Widget build(BuildContext context) { - return child; - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 7b94e6d..8a6f587 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: gallery_picker description: A new Flutter package project. version: 0.0.1 -homepage: +homepage: https://github.com/FlutterWay/gallery_picker environment: sdk: '>=2.18.5 <3.0.0' @@ -14,11 +14,12 @@ dependencies: photo_gallery: ^1.1.1 permission_handler: ^10.2.0 transparent_image: ^2.0.0 + camera: ^0.10.1 video_player: ^2.4.10 get: ^4.6.5 video_thumbnail: ^0.5.3 intl: ^0.18.0 - modal_bottom_sheet: ^2.1.2 + bottom_sheet_bar: ^2.3.8 dev_dependencies: flutter_test: sdk: flutter diff --git a/test/gallery_picker_test.dart b/test/gallery_picker_test.dart index e420195..132d080 100644 --- a/test/gallery_picker_test.dart +++ b/test/gallery_picker_test.dart @@ -1,7 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:gallery_picker/gallery_picker.dart'; - void main() { //test('adds one to input values', () { // final calculator = Calculator();