init
This commit is contained in:
parent
4c936d4061
commit
448913f98c
@ -1,4 +1,9 @@
|
|||||||
# This is a generated file; do not edit or check into version control.
|
# 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=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_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\\
|
permission_handler_apple=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\permission_handler_apple-9.0.7\\
|
||||||
|
@ -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"}
|
{"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"}
|
@ -1,3 +1,3 @@
|
|||||||
## 0.0.1
|
## 0.0.1
|
||||||
|
|
||||||
* TODO: Describe initial release.
|
* Init
|
||||||
|
429
README.md
429
README.md
@ -1,39 +1,422 @@
|
|||||||
<!--
|
## modern_gallery_picker
|
||||||
This README describes the package. If you publish this package to pub.dev,
|
|
||||||
this README's contents appear on the landing page for your package.
|
|
||||||
|
|
||||||
For information about how to write a good package README, see the guide for
|
Gallery Picker is a flutter package that will allow you to pick media file(s), manage and navigate inside your gallery with modern tools and views.
|
||||||
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
|
||||||
|
|
||||||
For general information about developing packages, see the Dart guide for
|
<img src="https://raw.githubusercontent.com/FlutterWay/files/main/galleryPickerSlide.png" width="1200"/>
|
||||||
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
|
||||||
and the Flutter guide for
|
|
||||||
[developing packages and plugins](https://flutter.dev/developing-packages).
|
|
||||||
-->
|
|
||||||
|
|
||||||
TODO: Put a short description of the package here that helps potential users
|
|
||||||
know whether this package might be useful for them.
|
|
||||||
|
|
||||||
## Features
|
## 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
|
||||||
|
|
||||||
|
<div style="text-align: center">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align: center">
|
||||||
|
<img src="https://raw.githubusercontent.com/FlutterWay/files/main/gallery_picker_light.gif" width="200"/>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: center">
|
||||||
|
<img src="https://raw.githubusercontent.com/FlutterWay/files/main/gallery_picker_dark.gif" width="200"/>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: center">
|
||||||
|
<img src="https://raw.githubusercontent.com/FlutterWay/files/main/gallery_picker_destination.gif" width="200" />
|
||||||
|
</td>
|
||||||
|
<td style="text-align: center">
|
||||||
|
<img src="https://raw.githubusercontent.com/FlutterWay/files/main/camera_page.gif" width="200" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
TODO: List prerequisites and provide or point to information on how to
|
1) Update kotlin version to `1.6.0` and `classpath 'com.android.tools.build:gradle:7.0.4'` in your `build.gradle`
|
||||||
start using the package.
|
2) In `android` set the `minSdkVersion` to `25` in your `build.gradle`
|
||||||
|
|
||||||
|
#### Android
|
||||||
|
Add uses-permission `android/app/src/main/AndroidManifest.xml` file
|
||||||
|
```xml
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
```
|
||||||
|
#### Ios
|
||||||
|
Add these configurations to your `ios/Runner/info.plist` file ??????????
|
||||||
|
```xml
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>Privacy - Photo Library Usage Description</string>
|
||||||
|
<key>NSMotionUsageDescription</key>
|
||||||
|
<string>Motion usage description</string>
|
||||||
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
|
<string>NSPhotoLibraryAddUsageDescription</string>
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
TODO: Include short and useful examples for package users. Add longer examples
|
Quick and simple usage example:
|
||||||
to `/example` folder.
|
|
||||||
|
### Pick Single File
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
const like = 'sample';
|
MediaFile? media = await GalleryPicker.pickMedias(context: context,singleMedia: true);
|
||||||
|
```
|
||||||
|
### Pick Multiple Files
|
||||||
|
|
||||||
|
```dart
|
||||||
|
List<MediaFile>? 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
|
```dart
|
||||||
contribute to the package, how to file issues, what response they can expect
|
GalleryMedia? allmedia = await GalleryPicker.collectGallery;
|
||||||
from the package authors, and more.
|
```
|
||||||
|
|
||||||
|
### 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<MediaFile>? 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<MediaFile>? medias = await GalleryPicker.pickMedias(
|
||||||
|
context: context,
|
||||||
|
config: Config(
|
||||||
|
mode: Mode.dark
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Give an initial selected media files
|
||||||
|
|
||||||
|
```dart
|
||||||
|
List<MediaFile>? 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<MediaFile>? 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
|
@ -2,6 +2,7 @@
|
|||||||
package="com.example.gallery_picker_example">
|
package="com.example.gallery_picker_example">
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<application
|
<application
|
||||||
android:label="gallery_picker_example"
|
android:label="gallery_picker_example"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
154
example/lib/examples/bottom_sheet_example.dart
Normal file
154
example/lib/examples/bottom_sheet_example.dart
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
class BottomSheetExample extends StatefulWidget {
|
||||||
|
const BottomSheetExample({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BottomSheetExample> createState() => _BottomSheetExampleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BottomSheetExampleState extends State<BottomSheetExample> {
|
||||||
|
List<MediaFile> selectedMedias = [];
|
||||||
|
int pageIndex = 0;
|
||||||
|
var controller = PageController(initialPage: 0);
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
body: BottomSheetLayout(
|
||||||
|
onSelect: (List<MediaFile> 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: <Widget>[
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
143
example/lib/examples/gallery_picker_example.dart
Normal file
143
example/lib/examples/gallery_picker_example.dart
Normal file
@ -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<GalleryPickerExample> createState() => _GalleryPickerExampleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GalleryPickerExampleState extends State<GalleryPickerExample> {
|
||||||
|
List<MediaFile> 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: <Widget>[
|
||||||
|
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<void> pickMedias() async {
|
||||||
|
List<MediaFile>? medias = await GalleryPicker.pickMedias(
|
||||||
|
context: context,
|
||||||
|
config: Config(mode: Mode.dark),
|
||||||
|
);
|
||||||
|
if (medias != null) {
|
||||||
|
setState(() {
|
||||||
|
selectedMedias += medias;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
example/lib/examples/multiple_medias.dart
Normal file
131
example/lib/examples/multiple_medias.dart
Normal file
@ -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<MediaFile> medias;
|
||||||
|
const MultipleMediasView(this.medias, {super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MultipleMediasView> createState() => _MultipleMediasViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MultipleMediasViewState extends State<MultipleMediasView> {
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
94
example/lib/examples/pick_medias_with_builder.dart
Normal file
94
example/lib/examples/pick_medias_with_builder.dart
Normal file
@ -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<PickMediasWithBuilder> createState() => _PickMediasWithBuilderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PickMediasWithBuilderState extends State<PickMediasWithBuilder> {
|
||||||
|
@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: <Widget>[
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
284
example/lib/examples/whatsapp_pick_photo.dart
Normal file
284
example/lib/examples/whatsapp_pick_photo.dart
Normal file
@ -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<WhatsappPickPhoto> createState() => _WhatsappPickPhotoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WhatsappPickPhotoState extends State<WhatsappPickPhoto> {
|
||||||
|
CameraController? cameraController;
|
||||||
|
GalleryMedia? gallery;
|
||||||
|
List<MediaFile> selectedMedias = [];
|
||||||
|
List<CameraDescription>? cameras;
|
||||||
|
CameraLensDirection cameraLensDirection = CameraLensDirection.front;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
initCamera();
|
||||||
|
fetchMedias();
|
||||||
|
GalleryPicker.listenSelectedFiles.listen((medias) {
|
||||||
|
selectedMedias = medias;
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetchMedias() async {
|
||||||
|
gallery = await GalleryPicker.collectGallery;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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<MediaFile> 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:gallery_picker/gallery_picker.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() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
@ -9,30 +13,27 @@ void main() {
|
|||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
// This widget is the root of your application.
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Demo',
|
title: 'Flutter Demo',
|
||||||
theme: ThemeData(
|
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 {
|
class MyHomePage extends StatefulWidget {
|
||||||
const MyHomePage({super.key, required this.title});
|
final List<MediaFile>? medias;
|
||||||
|
const MyHomePage({super.key, required this.title, this.medias});
|
||||||
// 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 String title;
|
final String title;
|
||||||
|
|
||||||
@ -41,213 +42,191 @@ class MyHomePage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
int _counter = 0;
|
List<MediaFile> selectedMedias = [];
|
||||||
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
if (widget.medias != null) {
|
||||||
|
selectedMedias = widget.medias!;
|
||||||
|
}
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
int pageIndex = 0;
|
||||||
|
var controller = PageController(initialPage: 0);
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Scaffold(
|
||||||
|
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
// Here we take the value from the MyHomePage object that was created by
|
title: Text("Pick medias"),
|
||||||
// the App.build method, and use it to set our appbar title.
|
|
||||||
title: Text(widget.title),
|
|
||||||
),
|
),
|
||||||
body: Stack(
|
body: Center(
|
||||||
children: [
|
child: Column(
|
||||||
Center(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
// Center is a layout widget. It takes a single child and positions it
|
children: <Widget>[
|
||||||
// in the middle of the parent.
|
const Spacer(),
|
||||||
child: Column(
|
const Text(
|
||||||
// Column is also a layout widget. It takes a list of children and
|
'These are your selected medias',
|
||||||
// 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: <Widget>[
|
|
||||||
const Text(
|
|
||||||
'You have pushed the button this many times:',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
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(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: showBottomSheet,
|
onPressed: pickMedias,
|
||||||
tooltip: 'Increment',
|
tooltip: 'Increment',
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> pickMedias() async {
|
||||||
|
List<MediaFile>? 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<void> getGalleryMedia() async {
|
||||||
|
GalleryMedia? allmedia = await GalleryPicker.collectGallery;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,48 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
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:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -36,6 +78,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
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:
|
csslib:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -69,6 +118,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
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:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -135,6 +191,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.5"
|
version: "0.1.5"
|
||||||
|
measure_size:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: measure_size
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -142,13 +205,6 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
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:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -157,7 +213,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.2"
|
version: "1.8.2"
|
||||||
permission_handler:
|
permission_handler:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: permission_handler
|
name: permission_handler
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
@ -205,6 +261,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
|
quiver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: quiver
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -231,6 +294,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
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:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -36,6 +36,7 @@ dependencies:
|
|||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
|
permission_handler: ^10.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -8,23 +8,21 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:gallery_picker_example/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
//testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
// // Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(const MyApp());
|
// await tester.pumpWidget(const MyApp());
|
||||||
|
//
|
||||||
// Verify that our counter starts at 0.
|
// // Verify that our counter starts at 0.
|
||||||
expect(find.text('0'), findsOneWidget);
|
// expect(find.text('0'), findsOneWidget);
|
||||||
expect(find.text('1'), findsNothing);
|
// expect(find.text('1'), findsNothing);
|
||||||
|
//
|
||||||
// Tap the '+' icon and trigger a frame.
|
// // Tap the '+' icon and trigger a frame.
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
// await tester.tap(find.byIcon(Icons.add));
|
||||||
await tester.pump();
|
// await tester.pump();
|
||||||
|
//
|
||||||
// Verify that our counter has incremented.
|
// // Verify that our counter has incremented.
|
||||||
expect(find.text('0'), findsNothing);
|
// expect(find.text('0'), findsNothing);
|
||||||
expect(find.text('1'), findsOneWidget);
|
// expect(find.text('1'), findsOneWidget);
|
||||||
});
|
//});
|
||||||
}
|
}
|
||||||
|
41
lib/controller/bottom_sheet_controller.dart
Normal file
41
lib/controller/bottom_sheet_controller.dart
Normal file
@ -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<void> open() async {
|
||||||
|
await sheetController.expand();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tapingStatus(bool value) {
|
||||||
|
appBarTapping = value;
|
||||||
|
if (galleryController == null) {
|
||||||
|
Get.find<PhoneGalleryController>().update();
|
||||||
|
} else {
|
||||||
|
galleryController!.update();
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> close() async {
|
||||||
|
isClosing = true;
|
||||||
|
update();
|
||||||
|
await sheetController.collapse();
|
||||||
|
isClosing = false;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void disposeController() {
|
||||||
|
isClosing = false;
|
||||||
|
appBarTapping = false;
|
||||||
|
GetInstance().delete<BottomSheetController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
BottomSheetController(this.sheetController);
|
||||||
|
}
|
@ -1,14 +1,16 @@
|
|||||||
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:photo_gallery/photo_gallery.dart';
|
import 'package:photo_gallery/photo_gallery.dart';
|
||||||
import '../models/config.dart';
|
import '../models/config.dart';
|
||||||
import '../views/hero_page.dart';
|
import '../models/gallery_media.dart';
|
||||||
import '/models/gallery_album.dart';
|
import '/models/gallery_album.dart';
|
||||||
import '/models/medium.dart';
|
import '/models/medium.dart';
|
||||||
import '../models/media_file.dart';
|
import '../models/media_file.dart';
|
||||||
|
import 'picker_listener.dart';
|
||||||
|
|
||||||
class PhoneGalleryController extends GetxController {
|
class PhoneGalleryController extends GetxController {
|
||||||
late Config config;
|
late Config config;
|
||||||
@ -17,15 +19,19 @@ class PhoneGalleryController extends GetxController {
|
|||||||
{required this.onSelect,
|
{required this.onSelect,
|
||||||
required this.heroBuilder,
|
required this.heroBuilder,
|
||||||
required this.isRecent,
|
required this.isRecent,
|
||||||
required this.multipleMediaBuilder}) {
|
List<MediaFile>? initSelectedMedias,
|
||||||
|
required this.multipleMediasBuilder}) {
|
||||||
this.config = config ?? Config();
|
this.config = config ?? Config();
|
||||||
|
if (initSelectedMedias != null) {
|
||||||
|
_selectedFiles = initSelectedMedias.map((e) => e).toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bool isRecent;
|
bool isRecent;
|
||||||
Function(List<MediaFile> selectedMedias) onSelect;
|
Function(List<MediaFile> selectedMedias) onSelect;
|
||||||
Widget Function(String tag, MediaFile media, BuildContext context)?
|
Widget Function(String tag, MediaFile media, BuildContext context)?
|
||||||
heroBuilder;
|
heroBuilder;
|
||||||
Widget Function(List<MediaFile> medias, BuildContext context)?
|
Widget Function(List<MediaFile> medias, BuildContext context)?
|
||||||
multipleMediaBuilder;
|
multipleMediasBuilder;
|
||||||
GalleryAlbum? selectedAlbum;
|
GalleryAlbum? selectedAlbum;
|
||||||
List<GalleryAlbum> _galleryAlbums = [];
|
List<GalleryAlbum> _galleryAlbums = [];
|
||||||
List<GalleryAlbum> get galleryAlbums => _galleryAlbums;
|
List<GalleryAlbum> get galleryAlbums => _galleryAlbums;
|
||||||
@ -36,10 +42,19 @@ class PhoneGalleryController extends GetxController {
|
|||||||
bool _pickerMode = false;
|
bool _pickerMode = false;
|
||||||
bool get pickerMode => _pickerMode;
|
bool get pickerMode => _pickerMode;
|
||||||
|
|
||||||
|
void updateSelectedFiles(List<MediaFile> medias) {
|
||||||
|
_selectedFiles = medias;
|
||||||
|
if(selectedFiles.isNotEmpty){
|
||||||
|
_pickerMode=true;
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
void changeAlbum(GalleryAlbum? album) {
|
void changeAlbum(GalleryAlbum? album) {
|
||||||
selectedAlbum = album;
|
selectedAlbum = album;
|
||||||
selectedFiles.clear();
|
_selectedFiles.clear();
|
||||||
update();
|
update();
|
||||||
|
updatePickerListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
void unselectMedia(MediaFile file) {
|
void unselectMedia(MediaFile file) {
|
||||||
@ -48,6 +63,7 @@ class PhoneGalleryController extends GetxController {
|
|||||||
_pickerMode = false;
|
_pickerMode = false;
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
|
updatePickerListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectMedia(MediaFile file) {
|
void selectMedia(MediaFile file) {
|
||||||
@ -58,75 +74,106 @@ class PhoneGalleryController extends GetxController {
|
|||||||
_pickerMode = true;
|
_pickerMode = true;
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
|
updatePickerListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchPickerMode(bool value) {
|
void switchPickerMode(bool value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
selectedFiles.clear();
|
_selectedFiles.clear();
|
||||||
}
|
}
|
||||||
_pickerMode = value;
|
_pickerMode = value;
|
||||||
update();
|
update();
|
||||||
|
updatePickerListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updatePickerListener() {
|
||||||
|
if (GetInstance().isRegistered<PickerListener>()) {
|
||||||
|
print(_selectedFiles.length);
|
||||||
|
Get.find<PickerListener>().updateController(_selectedFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> 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<void> initializeAlbums() async {
|
Future<void> initializeAlbums() async {
|
||||||
List<GalleryAlbum> tempGalleryAlbums = [];
|
GalleryMedia? media = await PhoneGalleryController.collectGallery;
|
||||||
|
if (media != null) {
|
||||||
List<Album> photoAlbums =
|
this._galleryAlbums = media.albums;
|
||||||
await PhotoGallery.listAlbums(mediumType: MediumType.image);
|
|
||||||
List<Album> 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);
|
|
||||||
}
|
}
|
||||||
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;
|
_isInitialized = true;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<GalleryMedia?> get collectGallery async {
|
||||||
|
if (await promptPermissionSetting()) {
|
||||||
|
List<GalleryAlbum> tempGalleryAlbums = [];
|
||||||
|
|
||||||
|
List<Album> photoAlbums =
|
||||||
|
await PhotoGallery.listAlbums(mediumType: MediumType.image);
|
||||||
|
List<Album> 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 {
|
GalleryAlbum? get recent {
|
||||||
return _isInitialized
|
return _isInitialized
|
||||||
? _galleryAlbums.singleWhere((element) => element.album.name == "All")
|
? _galleryAlbums.singleWhere((element) => element.album.name == "All")
|
||||||
@ -147,6 +194,15 @@ class PhoneGalleryController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isSelectedMedia(MediaFile file) {
|
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<PhoneGalleryController>();
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
lib/controller/picker_listener.dart
Normal file
24
lib/controller/picker_listener.dart
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../models/media_file.dart';
|
||||||
|
|
||||||
|
class PickerListener extends GetxController {
|
||||||
|
StreamController<List<MediaFile>> controller =
|
||||||
|
StreamController<List<MediaFile>>();
|
||||||
|
|
||||||
|
Stream<List<MediaFile>> get stream => controller.stream;
|
||||||
|
|
||||||
|
void updateController(List<MediaFile> medias) {
|
||||||
|
print("len:${medias.length}");
|
||||||
|
controller.add(medias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.close();
|
||||||
|
GetInstance().delete<PickerListener>();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -1,246 +1,118 @@
|
|||||||
library gallery_picker;
|
library gallery_picker;
|
||||||
|
|
||||||
export 'models/config.dart';
|
export 'models/config.dart';
|
||||||
|
export 'models/media_file.dart';
|
||||||
import 'dart:io';
|
export 'models/mode.dart';
|
||||||
import 'package:flutter/foundation.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:flutter/material.dart';
|
||||||
|
import 'package:gallery_picker/models/gallery_media.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
|
||||||
import '../../controller/gallery_controller.dart';
|
import '../../controller/gallery_controller.dart';
|
||||||
|
import 'controller/bottom_sheet_controller.dart';
|
||||||
|
import 'controller/picker_listener.dart';
|
||||||
import 'models/config.dart';
|
import 'models/config.dart';
|
||||||
import 'models/media_file.dart';
|
import 'models/media_file.dart';
|
||||||
import 'views/album_category_view/gallery_categories_widget.dart';
|
import 'views/gallery_picker_view/gallery_picker_view.dart';
|
||||||
import 'views/album_view/gallery_category_view_page.dart';
|
|
||||||
import 'views/album_view/gallery_category_view_widget.dart';
|
|
||||||
import 'views/hero_page.dart';
|
|
||||||
|
|
||||||
void disposeGalleryPicker() {
|
class GalleryPicker {
|
||||||
GetInstance().delete<PhoneGalleryController>();
|
static Stream<List<MediaFile>> get listenSelectedFiles {
|
||||||
}
|
var controller = Get.put(PickerListener());
|
||||||
|
return controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
class GalleryPicker extends StatefulWidget {
|
static void disposeSelectedFilesListener() {
|
||||||
Config? config;
|
if (GetInstance().isRegistered<PickerListener>()) {
|
||||||
Function(List<MediaFile> selectedMedias) onSelect;
|
Get.find<PickerListener>().dispose();
|
||||||
Widget Function(String tag, MediaFile media, BuildContext context)?
|
}
|
||||||
heroBuilder;
|
}
|
||||||
Widget Function(List<MediaFile> medias, BuildContext context)?
|
|
||||||
multipleMediaBuilder;
|
|
||||||
bool startWithRecent;
|
|
||||||
GalleryPicker(
|
|
||||||
{super.key,
|
|
||||||
this.config,
|
|
||||||
required this.onSelect,
|
|
||||||
this.heroBuilder,
|
|
||||||
this.multipleMediaBuilder,
|
|
||||||
this.startWithRecent = false});
|
|
||||||
|
|
||||||
@override
|
static void dispose() {
|
||||||
State<GalleryPicker> createState() => _GalleryPickerState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _GalleryPickerState extends State<GalleryPicker> {
|
|
||||||
late PhoneGalleryController galleryController;
|
|
||||||
late PageController _scrollController;
|
|
||||||
bool noPhotoSeleceted = true;
|
|
||||||
late Config config;
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_scrollController =
|
|
||||||
PageController(initialPage: widget.startWithRecent ? 0 : 1);
|
|
||||||
if (GetInstance().isRegistered<PhoneGalleryController>()) {
|
if (GetInstance().isRegistered<PhoneGalleryController>()) {
|
||||||
galleryController = Get.find<PhoneGalleryController>();
|
Get.find<PhoneGalleryController>().disposeController();
|
||||||
config = galleryController.config;
|
}
|
||||||
|
if (GetInstance().isRegistered<BottomSheetController>()) {
|
||||||
|
Get.find<BottomSheetController>().disposeController();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<MediaFile>?> pickMedias(
|
||||||
|
{Config? config,
|
||||||
|
bool startWithRecent = false,
|
||||||
|
List<MediaFile>? initSelectedMedias,
|
||||||
|
required BuildContext context}) async {
|
||||||
|
List<MediaFile>? medias;
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => GalleryPickerView(
|
||||||
|
onSelect: (mediasTmp) {
|
||||||
|
medias = mediasTmp;
|
||||||
|
},
|
||||||
|
config: config,
|
||||||
|
initSelectedMedias: initSelectedMedias,
|
||||||
|
startWithRecent: startWithRecent,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
return medias;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> pickMediasWithBuilder(
|
||||||
|
{Config? config,
|
||||||
|
required Widget Function(List<MediaFile> medias, BuildContext context)?
|
||||||
|
multipleMediasBuilder,
|
||||||
|
Widget Function(String tag, MediaFile media, BuildContext context)?
|
||||||
|
heroBuilder,
|
||||||
|
List<MediaFile>? 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<void> openSheet() async {
|
||||||
|
if (GetInstance().isRegistered<BottomSheetController>()) {
|
||||||
|
await Get.find<BottomSheetController>().open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> closeSheet() async {
|
||||||
|
if (GetInstance().isRegistered<BottomSheetController>()) {
|
||||||
|
await Get.find<BottomSheetController>().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get isSheetOpened {
|
||||||
|
if (GetInstance().isRegistered<BottomSheetController>()) {
|
||||||
|
return Get.find<BottomSheetController>().sheetController.isExpanded;
|
||||||
} else {
|
} else {
|
||||||
galleryController = Get.put(PhoneGalleryController(widget.config,
|
return false;
|
||||||
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<void> initializeGallery() async {
|
|
||||||
if (await _promptPermissionSetting()) {
|
|
||||||
print("granted");
|
|
||||||
await galleryController.initializeAlbums();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _promptPermissionSetting() async {
|
static Future<GalleryMedia?> get collectGallery async {
|
||||||
if (Platform.isIOS &&
|
return await PhoneGalleryController.collectGallery;
|
||||||
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<PhoneGalleryController>(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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ class Config {
|
|||||||
unselectedMenuStyle;
|
unselectedMenuStyle;
|
||||||
String recents, gallery, lastMonth, lastWeek, tapPhotoSelect, selected;
|
String recents, gallery, lastMonth, lastWeek, tapPhotoSelect, selected;
|
||||||
List<String> months;
|
List<String> months;
|
||||||
Mode? mode;
|
Mode mode;
|
||||||
|
|
||||||
Config(
|
Config(
|
||||||
{Color? backgroundColor,
|
{Color? backgroundColor,
|
||||||
@ -47,7 +47,7 @@ class Config {
|
|||||||
"November",
|
"November",
|
||||||
"December"
|
"December"
|
||||||
],
|
],
|
||||||
this.mode = Mode.dark,
|
this.mode = Mode.light,
|
||||||
Widget? selectIcon}) {
|
Widget? selectIcon}) {
|
||||||
if (backgroundColor == null) {
|
if (backgroundColor == null) {
|
||||||
this.backgroundColor = mode == Mode.dark
|
this.backgroundColor = mode == Mode.dark
|
||||||
|
@ -14,6 +14,12 @@ class GalleryAlbum {
|
|||||||
dateCategories.expand((element) => element.files).toList().length;
|
dateCategories.expand((element) => element.files).toList().length;
|
||||||
String? get name => album.name;
|
String? get name => album.name;
|
||||||
|
|
||||||
|
List<MediaFile> get medias {
|
||||||
|
return dateCategories
|
||||||
|
.expand<MediaFile>((element) => element.files)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
late AlbumType type;
|
late AlbumType type;
|
||||||
|
|
||||||
set setType(AlbumType type) {
|
set setType(AlbumType type) {
|
||||||
@ -37,9 +43,9 @@ class GalleryAlbum {
|
|||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
List<DateCategory> dateCategory = [];
|
List<DateCategory> dateCategory = [];
|
||||||
for (var file in sortAlbumMediaDates((await album.listMedia()).items)) {
|
for (var medium in sortAlbumMediaDates((await album.listMedia()).items)) {
|
||||||
MediaFile mediaFile = MediaFile(mediaFile: file);
|
MediaFile mediaFile = MediaFile(medium: medium);
|
||||||
String name = getDateCategory(file);
|
String name = getDateCategory(medium);
|
||||||
if (dateCategory.any((element) => element.name == name)) {
|
if (dateCategory.any((element) => element.name == name)) {
|
||||||
dateCategory
|
dateCategory
|
||||||
.singleWhere((element) => element.name == name)
|
.singleWhere((element) => element.name == name)
|
||||||
@ -59,7 +65,7 @@ class GalleryAlbum {
|
|||||||
|
|
||||||
DateTime? get lastDate {
|
DateTime? get lastDate {
|
||||||
if (dateCategories.isNotEmpty) {
|
if (dateCategories.isNotEmpty) {
|
||||||
return dateCategories.first.files.first.mediaFile.lastDate;
|
return dateCategories.first.files.first.medium.lastDate;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -69,9 +75,6 @@ class GalleryAlbum {
|
|||||||
dateCategories.expand((element) => element.files).toList();
|
dateCategories.expand((element) => element.files).toList();
|
||||||
|
|
||||||
String getDateCategory(Medium mediaFile) {
|
String getDateCategory(Medium mediaFile) {
|
||||||
//print("Creation Date: " + mediaFile.creationDateae.toString());
|
|
||||||
//print("Modified Date: " + mediaFile.modifiedDate!.toString());
|
|
||||||
//print("-------------------------------------------------------");
|
|
||||||
if (daysBetween(mediaFile.lastDate!) <= 3) {
|
if (daysBetween(mediaFile.lastDate!) <= 3) {
|
||||||
return "Recent";
|
return "Recent";
|
||||||
} else if (daysBetween(mediaFile.lastDate!) > 3 &&
|
} else if (daysBetween(mediaFile.lastDate!) > 3 &&
|
||||||
@ -112,19 +115,19 @@ class GalleryAlbum {
|
|||||||
|
|
||||||
for (var category in dateCategories) {
|
for (var category in dateCategories) {
|
||||||
category.files.sort((a, b) {
|
category.files.sort((a, b) {
|
||||||
if (a.mediaFile.lastDate == null) {
|
if (a.medium.lastDate == null) {
|
||||||
return 1;
|
return 1;
|
||||||
} else if (b.mediaFile.lastDate == null) {
|
} else if (b.medium.lastDate == null) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return b.mediaFile.lastDate!.compareTo(a.mediaFile.lastDate!);
|
return b.medium.lastDate!.compareTo(a.medium.lastDate!);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addFile(MediaFile file) {
|
void addFile(MediaFile file) {
|
||||||
String name = getDateCategory(file.mediaFile);
|
String name = getDateCategory(file.medium);
|
||||||
if (dateCategories.any((element) => element.name == name)) {
|
if (dateCategories.any((element) => element.name == name)) {
|
||||||
dateCategories
|
dateCategories
|
||||||
.singleWhere((element) => element.name == name)
|
.singleWhere((element) => element.name == name)
|
||||||
|
19
lib/models/gallery_media.dart
Normal file
19
lib/models/gallery_media.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import 'gallery_album.dart';
|
||||||
|
|
||||||
|
class GalleryMedia {
|
||||||
|
List<GalleryAlbum> 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);
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -6,32 +7,68 @@ import 'package:video_thumbnail/video_thumbnail.dart';
|
|||||||
import '../controller/gallery_controller.dart';
|
import '../controller/gallery_controller.dart';
|
||||||
|
|
||||||
class MediaFile {
|
class MediaFile {
|
||||||
Medium mediaFile;
|
Medium medium;
|
||||||
MediumType? type;
|
MediumType? type;
|
||||||
Uint8List? thumbnail;
|
Uint8List? thumbnail;
|
||||||
bool thumbnailFailed=false;
|
Uint8List? data;
|
||||||
|
late String id;
|
||||||
MediaFile({required this.mediaFile}) {
|
bool thumbnailFailed = false;
|
||||||
type = mediaFile.mediumType;
|
File? file;
|
||||||
|
MediaFile({required this.medium}) {
|
||||||
|
type = medium.mediumType;
|
||||||
|
id = medium.id;
|
||||||
}
|
}
|
||||||
Future<void> getThumbnail() async {
|
Future<void> getThumbnail() async {
|
||||||
print("zooooortlamaca");
|
|
||||||
try {
|
try {
|
||||||
thumbnail =
|
thumbnail =
|
||||||
Uint8List.fromList(await mediaFile.getThumbnail(highQuality: true));
|
Uint8List.fromList(await medium.getThumbnail(highQuality: true));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
thumbnailFailed = true;
|
thumbnailFailed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void unselect() {
|
Future<File> getFile() async {
|
||||||
Get.find<PhoneGalleryController>().unselectMedia(this);
|
file = await medium.getFile();
|
||||||
|
return file!;
|
||||||
}
|
}
|
||||||
|
|
||||||
void select() {
|
Future<Uint8List> getData() async {
|
||||||
Get.find<PhoneGalleryController>().selectMedia(this);
|
if (file == null) {
|
||||||
|
await getFile();
|
||||||
|
}
|
||||||
|
data = await file!.readAsBytes();
|
||||||
|
return data!;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isSelected =>
|
void unselect({PhoneGalleryController? controller}) {
|
||||||
Get.find<PhoneGalleryController>().isSelectedMedia(this);
|
if (controller != null) {
|
||||||
|
controller.unselectMedia(this);
|
||||||
|
} else {
|
||||||
|
if (GetInstance().isRegistered<PhoneGalleryController>()) {
|
||||||
|
Get.find<PhoneGalleryController>().unselectMedia(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void select({PhoneGalleryController? controller}) {
|
||||||
|
if (controller != null) {
|
||||||
|
controller.selectMedia(this);
|
||||||
|
} else {
|
||||||
|
if (GetInstance().isRegistered<PhoneGalleryController>()) {
|
||||||
|
Get.find<PhoneGalleryController>().selectMedia(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool? isSelected({PhoneGalleryController? controller}) {
|
||||||
|
if (controller != null) {
|
||||||
|
return controller.isSelectedMedia(this);
|
||||||
|
} else {
|
||||||
|
if (GetInstance().isRegistered<PhoneGalleryController>()) {
|
||||||
|
return Get.find<PhoneGalleryController>().isSelectedMedia(this);
|
||||||
|
} else {
|
||||||
|
return 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<StatefulWidget> createState() => AlbumPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class AlbumPageState extends State<AlbumPage> {
|
|
||||||
List<Medium>? _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: <Widget>[
|
|
||||||
...?_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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<VideoProvider> {
|
|
||||||
VideoPlayerController? _controller;
|
|
||||||
File? _file;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
initAsync();
|
|
||||||
});
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> 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: <Widget>[
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
74
lib/user_widgets/album_categories_view.dart
Normal file
74
lib/user_widgets/album_categories_view.dart
Normal file
@ -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<GalleryAlbum> 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: <Widget>[
|
||||||
|
...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),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
28
lib/user_widgets/album_medias.dart
Normal file
28
lib/user_widgets/album_medias.dart
Normal file
@ -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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
64
lib/user_widgets/date_category_view.dart
Normal file
64
lib/user_widgets/date_category_view.dart
Normal file
@ -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: <Widget>[
|
||||||
|
...category.files.map(
|
||||||
|
(medium) => ThumbnailMedia(
|
||||||
|
media: medium,
|
||||||
|
onErrorBuilder: onMediaErrorBuilder,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
24
lib/user_widgets/files_stream_builder.dart
Normal file
24
lib/user_widgets/files_stream_builder.dart
Normal file
@ -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<MediaFile>? medias, BuildContext context) builder;
|
||||||
|
FilesStreamBuilder({super.key, required this.builder}) {
|
||||||
|
Get.put(PickerListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StreamBuilder(
|
||||||
|
stream: Get.find<PickerListener>().stream,
|
||||||
|
builder: ((context, snapshot) {
|
||||||
|
print("snapshot:${snapshot.data}");
|
||||||
|
return builder(snapshot.data, context);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
26
lib/user_widgets/media_provider.dart
Normal file
26
lib/user_widgets/media_provider.dart
Normal file
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
67
lib/user_widgets/photo_provider.dart
Normal file
67
lib/user_widgets/photo_provider.dart
Normal file
@ -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<PhotoProvider> {
|
||||||
|
VideoPlayerController? _controller;
|
||||||
|
late MediaFile media;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
media = widget.media;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
initMedia();
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,22 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gallery_picker/models/gallery_album.dart';
|
import '/models/gallery_album.dart';
|
||||||
import 'package:get/get.dart';
|
|
||||||
import '../controller/gallery_controller.dart';
|
|
||||||
import '../models/config.dart';
|
import '../models/config.dart';
|
||||||
import '../models/mode.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 {
|
class ThumbnailAlbum extends StatelessWidget {
|
||||||
final GalleryAlbum album;
|
final GalleryAlbum album;
|
||||||
final Color failIconColor;
|
final Color failIconColor, backgroundColor;
|
||||||
final Config config = Get.find<PhoneGalleryController>().config;
|
final Mode mode;
|
||||||
ThumbnailAlbum({super.key, required this.album, required this.failIconColor});
|
ThumbnailAlbum({super.key, required this.album, required this.failIconColor,required this.mode,required this.backgroundColor});
|
||||||
|
|
||||||
Color adjustFailedBgColor() {
|
Color adjustFailedBgColor() {
|
||||||
if (config.mode == Mode.dark) {
|
if (mode == Mode.dark) {
|
||||||
return lighten(
|
return lighten(
|
||||||
config.backgroundColor,
|
backgroundColor,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return darken(config.backgroundColor);
|
return darken(backgroundColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,26 +53,12 @@ class ThumbnailAlbum extends StatelessWidget {
|
|||||||
color: failIconColor,
|
color: failIconColor,
|
||||||
))
|
))
|
||||||
else if (album.thumbnail != null)
|
else if (album.thumbnail != null)
|
||||||
FadeInImage(
|
Image.memory(
|
||||||
fadeInDuration: const Duration(milliseconds: 200),
|
Uint8List.fromList(album.thumbnail!),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
placeholder: MemoryImage(kTransparentImage),
|
|
||||||
image: MemoryImage(Uint8List.fromList(album.thumbnail!)),
|
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
const SizedBox(),
|
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(
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
child: Container(
|
child: Container(
|
57
lib/user_widgets/thumbnail_media.dart
Normal file
57
lib/user_widgets/thumbnail_media.dart
Normal file
@ -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,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
110
lib/user_widgets/video_provider.dart
Normal file
110
lib/user_widgets/video_provider.dart
Normal file
@ -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<VideoProvider> {
|
||||||
|
VideoPlayerController? _controller;
|
||||||
|
File? _file;
|
||||||
|
late MediaFile media;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
media = widget.media;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
initMedia();
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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: <Widget>[
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -10,8 +10,8 @@ import 'package:transparent_image/transparent_image.dart';
|
|||||||
class ThumbnailMedia extends StatelessWidget {
|
class ThumbnailMedia extends StatelessWidget {
|
||||||
final MediaFile file;
|
final MediaFile file;
|
||||||
final Color failIconColor;
|
final Color failIconColor;
|
||||||
final Config config = Get.find<PhoneGalleryController>().config;
|
final Config config;
|
||||||
ThumbnailMedia({super.key, required this.file, required this.failIconColor});
|
ThumbnailMedia({super.key, required this.file, required this.failIconColor,required this.config});
|
||||||
|
|
||||||
Color adjustFailedBgColor() {
|
Color adjustFailedBgColor() {
|
||||||
if (config.mode == Mode.dark) {
|
if (config.mode == Mode.dark) {
|
||||||
@ -58,7 +58,7 @@ class ThumbnailMedia extends StatelessWidget {
|
|||||||
))
|
))
|
||||||
else if (file.thumbnail != null)
|
else if (file.thumbnail != null)
|
||||||
Hero(
|
Hero(
|
||||||
tag: file.mediaFile.id,
|
tag: file.medium.id,
|
||||||
child: FadeInImage(
|
child: FadeInImage(
|
||||||
fadeInDuration: const Duration(milliseconds: 200),
|
fadeInDuration: const Duration(milliseconds: 200),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@ -73,7 +73,7 @@ class ThumbnailMedia extends StatelessWidget {
|
|||||||
bottom: 10,
|
bottom: 10,
|
||||||
left: 10,
|
left: 10,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
file.mediaFile.mediumType == MediumType.video
|
file.medium.mediumType == MediumType.video
|
||||||
? Icons.video_camera_back
|
? Icons.video_camera_back
|
||||||
: null,
|
: null,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
43
lib/views/album_categories_view/album_categories_view.dart
Normal file
43
lib/views/album_categories_view/album_categories_view.dart
Normal file
@ -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: <Widget>[
|
||||||
|
...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,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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<PhoneGalleryController>();
|
|
||||||
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: <Widget>[
|
|
||||||
...galleryController.galleryAlbums.map(
|
|
||||||
(album) => GestureDetector(
|
|
||||||
onTap: () => galleryController.changeAlbum(album),
|
|
||||||
child: Stack(fit: StackFit.passthrough, children: [
|
|
||||||
ThumbnailAlbum(album: album, failIconColor: galleryController.config.appbarIconColor),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
77
lib/views/album_view/album_appbar.dart
Normal file
77
lib/views/album_view/album_appbar.dart
Normal file
@ -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);
|
||||||
|
}
|
40
lib/views/album_view/album_medias_view.dart
Normal file
40
lib/views/album_view/album_medias_view.dart
Normal file
@ -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,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
39
lib/views/album_view/album_page.dart
Normal file
39
lib/views/album_view/album_page.dart
Normal file
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,14 @@ import '../gridview_static.dart';
|
|||||||
import '/models/gallery_album.dart';
|
import '/models/gallery_album.dart';
|
||||||
import 'media_view.dart';
|
import 'media_view.dart';
|
||||||
|
|
||||||
class DateCategoryWidget extends StatelessWidget {
|
class DateCategoryWiew extends StatelessWidget {
|
||||||
DateCategoryWidget({super.key, required this.category}){
|
PhoneGalleryController controller;
|
||||||
print(category.name);
|
bool singleMedia;
|
||||||
}
|
DateCategoryWiew(
|
||||||
|
{super.key,
|
||||||
|
required this.category,
|
||||||
|
required this.controller,
|
||||||
|
required this.singleMedia});
|
||||||
DateCategory category;
|
DateCategory category;
|
||||||
|
|
||||||
int getRowCount() {
|
int getRowCount() {
|
||||||
@ -31,7 +35,7 @@ class DateCategoryWidget extends StatelessWidget {
|
|||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Text(
|
child: Text(
|
||||||
category.name,
|
category.name,
|
||||||
style: Get.find<PhoneGalleryController>().config.textStyle,
|
style: controller.config.textStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -43,7 +47,11 @@ class DateCategoryWidget extends StatelessWidget {
|
|||||||
crossAxisSpacing: 1.0,
|
crossAxisSpacing: 1.0,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
...category.files.map(
|
...category.files.map(
|
||||||
(medium) => MediaView(medium),
|
(medium) => MediaView(
|
||||||
|
medium,
|
||||||
|
controller: controller,
|
||||||
|
singleMedia: singleMedia,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
@ -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<PhoneGalleryController>(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<PhoneGalleryController>().pickerMode
|
|
||||||
? TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Get.find<PhoneGalleryController>().switchPickerMode(true);
|
|
||||||
},
|
|
||||||
child: Icon(
|
|
||||||
Icons.check_box_outlined,
|
|
||||||
color: config.appbarIconColor,
|
|
||||||
))
|
|
||||||
: const SizedBox()
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: GalleryCategoryViewWidget(galleryAlbum: album),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<PhoneGalleryController>().selectedFiles.isNotEmpty)
|
|
||||||
Align(alignment: Alignment.bottomCenter, child: SelectedMediaWidget())
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:transparent_image/transparent_image.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:get/get.dart';
|
||||||
import 'package:photo_gallery/photo_gallery.dart';
|
import 'package:photo_gallery/photo_gallery.dart';
|
||||||
import '../../../controller/gallery_controller.dart';
|
import '../../../controller/gallery_controller.dart';
|
||||||
@ -8,8 +9,10 @@ import '../../../models/media_file.dart';
|
|||||||
|
|
||||||
class MediaView extends StatelessWidget {
|
class MediaView extends StatelessWidget {
|
||||||
final MediaFile file;
|
final MediaFile file;
|
||||||
var controller = Get.find<PhoneGalleryController>();
|
PhoneGalleryController controller;
|
||||||
MediaView(this.file, {super.key});
|
bool singleMedia;
|
||||||
|
MediaView(this.file,
|
||||||
|
{super.key, required this.controller, required this.singleMedia});
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
@ -17,31 +20,63 @@ class MediaView extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
file.select();
|
if (singleMedia) {
|
||||||
|
if (controller.heroBuilder != null) {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute<void>(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<BottomSheetController>()) {
|
||||||
|
Get.find<BottomSheetController>().close();
|
||||||
|
} else {
|
||||||
|
Navigator.pop(context);
|
||||||
|
controller.disposeController();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file.select(controller: controller);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (controller.pickerMode) {
|
if (controller.pickerMode) {
|
||||||
file.isSelected ? file.unselect() : file.select();
|
file.isSelected(controller: controller)!
|
||||||
|
? file.unselect(controller: controller)
|
||||||
|
: file.select(controller: controller);
|
||||||
} else {
|
} else {
|
||||||
if (controller.heroBuilder != null) {
|
if (controller.heroBuilder != null) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(builder: (BuildContext context) {
|
MaterialPageRoute<void>(builder: (BuildContext context) {
|
||||||
return controller.heroBuilder!(file.mediaFile.id, file,context);
|
return controller.heroBuilder!(file.medium.id, file, context);
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
controller.onSelect([file]);
|
controller.selectedFiles.add(file);
|
||||||
Navigator.pop(context);
|
controller.onSelect(controller.selectedFiles);
|
||||||
GetInstance().delete<PhoneGalleryController>();
|
controller.updatePickerListener();
|
||||||
|
if (GetInstance().isRegistered<BottomSheetController>()) {
|
||||||
|
Get.find<BottomSheetController>().close();
|
||||||
|
} else {
|
||||||
|
Navigator.pop(context);
|
||||||
|
controller.disposeController();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: ThumbnailMedia(
|
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(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
file.isSelected ? file.unselect() : file.select();
|
file.isSelected(controller: controller)!
|
||||||
|
? file.unselect(controller: controller)
|
||||||
|
: file.select(controller: controller);
|
||||||
},
|
},
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:photo_gallery/photo_gallery.dart';
|
import 'package:photo_gallery/photo_gallery.dart';
|
||||||
|
import '../../controller/bottom_sheet_controller.dart';
|
||||||
import '../../controller/gallery_controller.dart';
|
import '../../controller/gallery_controller.dart';
|
||||||
import '../../models/config.dart';
|
import '../../models/config.dart';
|
||||||
|
|
||||||
class SelectedMediaWidget extends StatelessWidget {
|
class SelectedMediasView extends StatelessWidget {
|
||||||
var galleryController = Get.find<PhoneGalleryController>();
|
PhoneGalleryController controller;
|
||||||
late Config config;
|
late Config config;
|
||||||
SelectedMediaWidget({super.key}) {
|
SelectedMediasView({super.key, required this.controller}) {
|
||||||
config = galleryController.config;
|
config = controller.config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -29,7 +30,7 @@ class SelectedMediaWidget extends StatelessWidget {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
children: [
|
children: [
|
||||||
for (var selectedMedia in galleryController.selectedFiles)
|
for (var selectedMedia in controller.selectedFiles)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
top: 3.0, bottom: 3.0, right: 2),
|
top: 3.0, bottom: 3.0, right: 2),
|
||||||
@ -39,9 +40,9 @@ class SelectedMediaWidget extends StatelessWidget {
|
|||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
image: ThumbnailProvider(
|
image: ThumbnailProvider(
|
||||||
mediumId: selectedMedia.mediaFile.id,
|
mediumId: selectedMedia.medium.id,
|
||||||
mediumType:
|
mediumType:
|
||||||
selectedMedia.mediaFile.mediumType,
|
selectedMedia.medium.mediumType,
|
||||||
highQuality: true,
|
highQuality: true,
|
||||||
))),
|
))),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
@ -55,25 +56,28 @@ class SelectedMediaWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (galleryController.selectedFiles.length == 1 &&
|
if (controller.selectedFiles.length == 1 &&
|
||||||
galleryController.heroBuilder != null) {
|
controller.heroBuilder != null) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(builder: (BuildContext context) {
|
MaterialPageRoute<void>(builder: (BuildContext context) {
|
||||||
return galleryController.heroBuilder!(
|
return controller.heroBuilder!(
|
||||||
galleryController.selectedFiles[0].mediaFile.id,
|
controller.selectedFiles[0].medium.id,
|
||||||
galleryController.selectedFiles[0],context);
|
controller.selectedFiles[0],
|
||||||
|
context);
|
||||||
}));
|
}));
|
||||||
} else if (galleryController.multipleMediaBuilder != null) {
|
} else if (controller.multipleMediasBuilder != null) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(builder: (BuildContext context) {
|
MaterialPageRoute<void>(builder: (BuildContext context) {
|
||||||
return galleryController
|
return controller.multipleMediasBuilder!(
|
||||||
.multipleMediaBuilder!(galleryController.selectedFiles,context);
|
controller.selectedFiles, context);
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
galleryController.onSelect(galleryController.selectedFiles);
|
controller.onSelect(controller.selectedFiles);
|
||||||
Navigator.pop(context);
|
if (GetInstance().isRegistered<BottomSheetController>()) {
|
||||||
if (!galleryController.isRecent) {
|
Get.find<BottomSheetController>().close();
|
||||||
|
} else {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
controller.disposeController();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
154
lib/views/bottom_sheet.dart
Normal file
154
lib/views/bottom_sheet.dart
Normal file
@ -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<MediaFile>? initSelectedMedias;
|
||||||
|
final Function(List<MediaFile> selectedMedias) onSelect;
|
||||||
|
final Widget Function(String tag, MediaFile media, BuildContext context)?
|
||||||
|
heroBuilder;
|
||||||
|
final Widget Function(List<MediaFile> 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<PhoneGalleryController>()){
|
||||||
|
Get.find<PhoneGalleryController>().updateSelectedFiles(initSelectedMedias!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BottomSheetLayout> createState() => _BottomSheetLayoutState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BottomSheetLayoutState extends State<BottomSheetLayout> {
|
||||||
|
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<BottomSheetController>(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<BottomSheetController>(
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
225
lib/views/gallery_picker_view/gallery_picker_view.dart
Normal file
225
lib/views/gallery_picker_view/gallery_picker_view.dart
Normal file
@ -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<MediaFile> selectedMedias) onSelect;
|
||||||
|
Widget Function(String tag, MediaFile media, BuildContext context)?
|
||||||
|
heroBuilder;
|
||||||
|
Widget Function(List<MediaFile> medias, BuildContext context)?
|
||||||
|
multipleMediasBuilder;
|
||||||
|
bool startWithRecent;
|
||||||
|
BottomSheetBarController? sheetController;
|
||||||
|
List<MediaFile>? 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<GalleryPickerView> createState() => _GalleryPickerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GalleryPickerState extends State<GalleryPickerView> {
|
||||||
|
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<PhoneGalleryController>()) {
|
||||||
|
galleryController = Get.find<PhoneGalleryController>();
|
||||||
|
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>()) {
|
||||||
|
bottomSheetController = Get.find<BottomSheetController>();
|
||||||
|
} 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<PhoneGalleryController>(builder: (controller) {
|
||||||
|
return GetInstance().isRegistered<PhoneGalleryController>()
|
||||||
|
? 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(() {});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
74
lib/views/gallery_picker_view/picker_appbar.dart
Normal file
74
lib/views/gallery_picker_view/picker_appbar.dart
Normal file
@ -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>()) {
|
||||||
|
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);
|
||||||
|
}
|
42
lib/views/gallery_picker_view/reload_gallery.dart
Normal file
42
lib/views/gallery_picker_view/reload_gallery.dart
Normal file
@ -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<Color>(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();
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
32
lib/views/gallery_picker_view/tappable_appbar.dart
Normal file
32
lib/views/gallery_picker_view/tappable_appbar.dart
Normal file
@ -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<BottomSheetController>()
|
||||||
|
? GestureDetector(
|
||||||
|
onLongPressEnd: (a) {
|
||||||
|
if (GetInstance().isRegistered<BottomSheetController>()) {
|
||||||
|
controller!.tapingStatus(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPanCancel: () {},
|
||||||
|
onPanDown: (a) {
|
||||||
|
if (GetInstance().isRegistered<BottomSheetController>()) {
|
||||||
|
controller!.tapingStatus(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
)
|
||||||
|
: child;
|
||||||
|
}
|
||||||
|
}
|
@ -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<MediaFile> selectedMedias;
|
|
||||||
String heroTag;
|
|
||||||
Widget child;
|
|
||||||
HeroPage(
|
|
||||||
{super.key,
|
|
||||||
required this.selectedMedias,
|
|
||||||
required this.heroTag,
|
|
||||||
required this.child});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
name: gallery_picker
|
name: gallery_picker
|
||||||
description: A new Flutter package project.
|
description: A new Flutter package project.
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
homepage:
|
homepage: https://github.com/FlutterWay/gallery_picker
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.18.5 <3.0.0'
|
sdk: '>=2.18.5 <3.0.0'
|
||||||
@ -14,11 +14,12 @@ dependencies:
|
|||||||
photo_gallery: ^1.1.1
|
photo_gallery: ^1.1.1
|
||||||
permission_handler: ^10.2.0
|
permission_handler: ^10.2.0
|
||||||
transparent_image: ^2.0.0
|
transparent_image: ^2.0.0
|
||||||
|
camera: ^0.10.1
|
||||||
video_player: ^2.4.10
|
video_player: ^2.4.10
|
||||||
get: ^4.6.5
|
get: ^4.6.5
|
||||||
video_thumbnail: ^0.5.3
|
video_thumbnail: ^0.5.3
|
||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
modal_bottom_sheet: ^2.1.2
|
bottom_sheet_bar: ^2.3.8
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:gallery_picker/gallery_picker.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
//test('adds one to input values', () {
|
//test('adds one to input values', () {
|
||||||
// final calculator = Calculator();
|
// final calculator = Calculator();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user