This commit is contained in:
Furkan 2022-12-29 08:45:28 +03:00
parent 4c936d4061
commit 448913f98c
53 changed files with 3145 additions and 964 deletions

View File

@ -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\\

View File

@ -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"}

View File

@ -1,3 +1,3 @@
## 0.0.1 ## 0.0.1
* TODO: Describe initial release. * Init

429
README.md
View File

@ -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

View File

@ -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}"

View 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,
),
],
),
),
),
);
}
}

View 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;
});
}
}
}

View 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,
),
),
);
}
}

View 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);
}
}

View 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,
),
),
),
],
),
))
],
),
),
);
}
}

View File

@ -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() { @override
Navigator.push( void initState() {
context, if (widget.medias != null) {
MaterialPageRoute( selectedMedias = widget.medias!;
builder: (context) => GalleryPicker( }
onSelect: (selectedMedias) { super.initState();
print(selectedMedias.length); }
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,
initSelectedMedias: selectedMedias,
startWithRecent: true);
if (medias != null) {
setState(() {
this.selectedMedias += medias;
});
}
}
pickMediasWithBuilder() {
GalleryPicker.pickMediasWithBuilder(
multipleMediasBuilder: ((medias, context) {
return MultipleMediasView(medias);
}),
heroBuilder: (tag, media, context) { heroBuilder: (tag, media, context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Flippers Page'), title: const Text('Hero Page'),
), ),
body: Container( body: Center(
// The blue background emphasizes that it's a new route.
color: Colors.lightBlueAccent,
padding: const EdgeInsets.all(16.0),
alignment: Alignment.topLeft,
child: Hero( child: Hero(
tag: tag, tag: tag,
child: Image.memory(media.thumbnail!), child: MediaProvider(
), media: media,
width: MediaQuery.of(context).size.width - 50,
height: 300,
), ),
)),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
backgroundColor: Colors.blue,
onPressed: () { onPressed: () {
GalleryPicker.dispose();
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => MyHomePage( builder: (context) => MyHomePage(
title: "messages", title: "Selected Medias",
medias: [media],
)), )),
); );
disposeGalleryPicker();
}, },
child: Icon(Icons.send), child: const Icon(
),
);
},
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, Icons.send,
color: Colors.white, color: Colors.white,
), ),
), ),
); );
}, },
multipleMediaBuilder: (medias, context) { context: 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 Future<void> getGalleryMedia() async {
Widget build(BuildContext context) { GalleryMedia? allmedia = await GalleryPicker.collectGallery;
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Stack(
children: [
Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
],
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: showBottomSheet,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
} }
} }

View File

@ -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:

View File

@ -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:

View File

@ -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);
}); //});
} }

View 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);
}

View File

@ -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,17 +74,46 @@ 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 {
GalleryMedia? media = await PhoneGalleryController.collectGallery;
if (media != null) {
this._galleryAlbums = media.albums;
}
_isInitialized = true;
update();
}
static Future<GalleryMedia?> get collectGallery async {
if (await promptPermissionSetting()) {
List<GalleryAlbum> tempGalleryAlbums = []; List<GalleryAlbum> tempGalleryAlbums = [];
List<Album> photoAlbums = List<Album> photoAlbums =
@ -90,15 +135,17 @@ class PhoneGalleryController extends GetxController {
if (lastPhotoDate == null) { if (lastPhotoDate == null) {
try { try {
entireGalleryAlbum.thumbnail = await videoAlbum.getThumbnail(highQuality: true); entireGalleryAlbum.thumbnail =
await videoAlbum.getThumbnail(highQuality: true);
} catch (e) { } catch (e) {
print(e); print(e);
} }
} else if (lastVideoDate == null) { } else if (lastVideoDate == null) {
} else { } else {
if (lastVideoDate.isBefore(lastPhotoDate)) { if (lastVideoDate.isAfter(lastPhotoDate)) {
try { try {
entireGalleryAlbum.thumbnail = await videoAlbum.getThumbnail(highQuality: true); entireGalleryAlbum.thumbnail =
await videoAlbum.getThumbnail(highQuality: true);
} catch (e) { } catch (e) {
entireGalleryAlbum.thumbnail = null; entireGalleryAlbum.thumbnail = null;
print(e); print(e);
@ -115,16 +162,16 @@ class PhoneGalleryController extends GetxController {
tempGalleryAlbums.add(entireGalleryAlbum); tempGalleryAlbums.add(entireGalleryAlbum);
} }
for (var videoAlbum in videoAlbums) { for (var videoAlbum in videoAlbums) {
print(videoAlbum.name);
GalleryAlbum galleryVideoAlbum = GalleryAlbum(album: videoAlbum); GalleryAlbum galleryVideoAlbum = GalleryAlbum(album: videoAlbum);
await galleryVideoAlbum.initialize(); await galleryVideoAlbum.initialize();
galleryVideoAlbum.setType = AlbumType.video; galleryVideoAlbum.setType = AlbumType.video;
tempGalleryAlbums.add(galleryVideoAlbum); tempGalleryAlbums.add(galleryVideoAlbum);
} }
_galleryAlbums = tempGalleryAlbums; return GalleryMedia(tempGalleryAlbums);
_isInitialized = true; } else {
update(); return null;
}
} }
GalleryAlbum? get recent { GalleryAlbum? get recent {
@ -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();
} }
} }

View 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();
}
}

View File

@ -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,
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 {
if (Platform.isIOS &&
await Permission.storage.request().isGranted &&
await Permission.photos.request().isGranted ||
Platform.isAndroid && await Permission.storage.request().isGranted) {
return true;
}
return false; 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() { static Future<GalleryMedia?> get collectGallery async {
if (galleryController.pickerMode && return await PhoneGalleryController.collectGallery;
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();
}
} }
} }

View File

@ -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

View File

@ -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)

View 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);
}

View File

@ -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 {
file = await medium.getFile();
return file!;
}
Future<Uint8List> getData() async {
if (file == null) {
await getFile();
}
data = await file!.readAsBytes();
return data!;
}
void unselect({PhoneGalleryController? controller}) {
if (controller != null) {
controller.unselectMedia(this);
} else {
if (GetInstance().isRegistered<PhoneGalleryController>()) {
Get.find<PhoneGalleryController>().unselectMedia(this); Get.find<PhoneGalleryController>().unselectMedia(this);
} }
}
void select() {
Get.find<PhoneGalleryController>().selectMedia(this);
} }
bool get isSelected => void select({PhoneGalleryController? controller}) {
Get.find<PhoneGalleryController>().isSelectedMedia(this); 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;
}
}
}
} }

View File

@ -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,
),
),
),
),
),
],
),
),
);
}
}

View File

@ -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,
),
),
],
);
}
}

View File

@ -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,
),
),
),
);
}
}

View 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),
]),
),
),
],
);
},
);
}
}

View 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,
),
],
),
],
);
}
}

View 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,
),
),
],
),
],
);
},
);
}
}

View 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);
}));
}
}

View 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,
);
}
}

View 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,
);
}
}

View File

@ -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(

View 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,
)),
],
);
});
}
}

View 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,
),
),
),
]),
),
],
),
);
}
}

View File

@ -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,

View 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,
),
]),
),
),
],
);
},
);
}
}

View File

@ -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),
]),
),
),
],
);
},
),
);
}
}

View 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);
}

View 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,
))
],
);
}
}

View 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,
),
);
}
}

View File

@ -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,
),
), ),
], ],
), ),

View File

@ -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),
);
});
}
}

View File

@ -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())
]);
}
}

View File

@ -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);
controller.onSelect(controller.selectedFiles);
controller.updatePickerListener();
if (GetInstance().isRegistered<BottomSheetController>()) {
Get.find<BottomSheetController>().close();
} else {
Navigator.pop(context); Navigator.pop(context);
GetInstance().delete<PhoneGalleryController>(); 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,

View File

@ -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
View 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,
);
}
}

View 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(() {});
},
);
});
}
}

View 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);
}

View 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();
},
)),
);
}
}

View 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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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();