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.
camera=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\camera-0.10.1\\
camera_android=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\camera_android-0.10.2\\
camera_avfoundation=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\camera_avfoundation-0.9.10\\
camera_web=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\camera_web-0.3.1\\
flutter_plugin_android_lifecycle=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\flutter_plugin_android_lifecycle-2.0.7\\
permission_handler=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\permission_handler-10.2.0\\
permission_handler_android=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\permission_handler_android-10.2.0\\
permission_handler_apple=C:\\src\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\permission_handler_apple-9.0.7\\

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
* TODO: Describe initial release.
* Init

429
README.md
View File

@ -1,39 +1,422 @@
<!--
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.
## modern_gallery_picker
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
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.
For general information about developing packages, see the Dart guide for
[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.
<img src="https://raw.githubusercontent.com/FlutterWay/files/main/galleryPickerSlide.png" width="1200"/>
## 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
TODO: List prerequisites and provide or point to information on how to
start using the package.
1) Update kotlin version to `1.6.0` and `classpath 'com.android.tools.build:gradle:7.0.4'` in your `build.gradle`
2) In `android` set the `minSdkVersion` to `25` in your `build.gradle`
#### Android
Add uses-permission `android/app/src/main/AndroidManifest.xml` file
```xml
<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
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
Quick and simple usage example:
### Pick Single File
```dart
const like = 'sample';
MediaFile? media = await GalleryPicker.pickMedias(context: context,singleMedia: true);
```
### Pick Multiple Files
```dart
List<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
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.
```dart
GalleryMedia? allmedia = await GalleryPicker.collectGallery;
```
### Listen selected files inside gallery picker
```dart
Stream stream = GalleryPicker.listenSelectedFiles;
```
Dispose listener
```dart
GalleryPicker.disposeSelectedFilesListener();
```
### BottomSheetLayout
Gallery Picker could also work as a bottom sheet. Wrap your scaffold's body with BottomSheetLayout
There is an example at `example/lib/examples/bottom_sheet_example.dart` to see how it could be done.
```dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: BottomSheetLayout(
onSelect: (medias) {},
child: Column(
children: [
```
### Customizable destination page
Within the Gallery Picker you can design a page that will be redirected after selecting any image(s).
Note: There are two builder called multipleMediasBuilder and heroBuilder. If you designed both of them, multipleMediasBuilder will be shown after picking multiple media files, heroBuilder will be shown after picking a single media. Use given hero tag to view your Hero image. You can see a simple example below.
There is an example at `example/lib/examples/pick_medias_with_builder.dart` to see how it could be done.
```dart
GalleryPicker.pickMediasWithBuilder(
multipleMediasBuilder: ((medias, context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flippers Page'),
),
body: GridView.count(
crossAxisCount: 3,
mainAxisSpacing: 5,
crossAxisSpacing: 5,
children: [
for (var media in medias)
ThumbnailMedia(
media: media,
)
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyHomePage(
title: "Selected Medias",
medias: medias,
)),
);
GalleryPicker.dispose();
},
child: const Icon(
Icons.send,
color: Colors.white,
),
),
);
}),
heroBuilder: (tag, media, context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flippers Page'),
),
body: Container(
color: Colors.lightBlueAccent,
padding: const EdgeInsets.all(16.0),
alignment: Alignment.topLeft,
child: Hero(
tag: tag,
child: Image.memory(media.thumbnail!),
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.orange,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyHomePage(
title: "Selected Medias",
medias: [media],
)),
);
GalleryPicker.dispose();
},
child: const Icon(
Icons.send,
color: Colors.white,
),
),
);
},
context: context);
```
### Dispose Gallery picker
```dart
GalleryPicker.dispose();
```
## Customize your gallery picker
A Config class is provided to user to customize your gallery picker. You can customize any feature you want and select appearance mode.
#### Customizable appereance features
```dart
List<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">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<application
android:label="gallery_picker_example"
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/services.dart';
import 'package:gallery_picker/gallery_picker.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:gallery_picker_example/examples/pick_medias_with_builder.dart';
import 'examples/gallery_picker_example.dart';
import 'examples/multiple_medias.dart';
import 'examples/whatsapp_pick_photo.dart';
void main() {
runApp(const MyApp());
@ -9,30 +13,27 @@ void main() {
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
/* light theme settings */
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
darkTheme: ThemeData(
brightness: Brightness.dark,
/* dark theme settings */
),
themeMode: ThemeMode.dark,
home: const GalleryPickerExample(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final List<MediaFile>? medias;
const MyHomePage({super.key, required this.title, this.medias});
final String title;
@ -41,213 +42,191 @@ class MyHomePage extends StatefulWidget {
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
List<MediaFile> selectedMedias = [];
void viewGalleryPicker() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GalleryPicker(
onSelect: (selectedMedias) {
print(selectedMedias.length);
@override
void initState() {
if (widget.medias != null) {
selectedMedias = widget.medias!;
}
super.initState();
}
int pageIndex = 0;
var controller = PageController(initialPage: 0);
@override
Widget build(BuildContext context) {
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) {
return Scaffold(
appBar: AppBar(
title: const Text('Flippers Page'),
title: const Text('Hero 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,
body: Center(
child: Hero(
tag: tag,
child: Image.memory(media.thumbnail!),
),
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: "messages",
title: "Selected Medias",
medias: [media],
)),
);
disposeGalleryPicker();
},
child: Icon(Icons.send),
),
);
},
multipleMediaBuilder: (medias, context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flippers Page'),
),
body: GridView.count(
crossAxisCount: 3,
mainAxisSpacing: 5,
crossAxisSpacing: 5,
children: [
for (var media in medias) Image.memory(media.thumbnail!)
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyHomePage(
title: "messages",
)),
);
disposeGalleryPicker();
},
child: Icon(Icons.send),
),
);
},
)),
);
}
showBottomSheet() {
showMaterialModalBottomSheet(
context: context,
builder: (context) => SingleChildScrollView(
controller: ModalScrollController.of(context),
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: GalleryPicker(
onSelect: (selectedMedias) {
print(selectedMedias.length);
},
heroBuilder: (heroId, media, context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flippers Page'),
),
body: Container(
// The blue background emphasizes that it's a new route.
color: Colors.lightBlueAccent,
padding: const EdgeInsets.all(16.0),
alignment: Alignment.topLeft,
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: Hero(
tag: heroId,
child: Image.memory(media.thumbnail!),
),
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.orange,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyHomePage(
title: "messages",
)),
);
disposeGalleryPicker();
},
child: Icon(
child: const Icon(
Icons.send,
color: Colors.white,
),
),
);
},
multipleMediaBuilder: (medias, context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flippers Page'),
),
body: GridView.count(
crossAxisCount: 3,
mainAxisSpacing: 5,
crossAxisSpacing: 5,
children: [
for (var media in medias) Image.memory(media.thumbnail!)
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyHomePage(
title: "messages",
)),
);
disposeGalleryPicker();
},
child: Icon(
Icons.send,
color: Colors.white,
),
),
);
},
),
),
),
);
context: context);
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
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.
);
Future<void> getGalleryMedia() async {
GalleryMedia? allmedia = await GalleryPicker.collectGallery;
}
}

View File

@ -15,6 +15,48 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
bottom_sheet_bar:
dependency: transitive
description:
name: bottom_sheet_bar
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.8"
camera:
dependency: transitive
description:
name: camera
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.1"
camera_android:
dependency: transitive
description:
name: camera_android
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.2"
camera_avfoundation:
dependency: transitive
description:
name: camera_avfoundation
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.10"
camera_platform_interface:
dependency: transitive
description:
name: camera_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.2"
camera_web:
dependency: transitive
description:
name: camera_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.1"
characters:
dependency: transitive
description:
@ -36,6 +78,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.16.0"
cross_file:
dependency: transitive
description:
name: cross_file
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.3+2"
csslib:
dependency: transitive
description:
@ -69,6 +118,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.7"
flutter_test:
dependency: "direct dev"
description: flutter
@ -135,6 +191,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.5"
measure_size:
dependency: transitive
description:
name: measure_size
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
meta:
dependency: transitive
description:
@ -142,13 +205,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
modal_bottom_sheet:
dependency: transitive
description:
name: modal_bottom_sheet
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
path:
dependency: transitive
description:
@ -157,7 +213,7 @@ packages:
source: hosted
version: "1.8.2"
permission_handler:
dependency: transitive
dependency: "direct main"
description:
name: permission_handler
url: "https://pub.dartlang.org"
@ -205,6 +261,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.1"
sky_engine:
dependency: transitive
description: flutter
@ -231,6 +294,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
stream_transform:
dependency: transitive
description:
name: stream_transform
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:

View File

@ -36,6 +36,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
permission_handler: ^10.2.0
dev_dependencies:
flutter_test:

View File

@ -8,23 +8,21 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gallery_picker_example/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
//testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// // Build our app and trigger a frame.
// await tester.pumpWidget(const MyApp());
//
// // Verify that our counter starts at 0.
// expect(find.text('0'), findsOneWidget);
// expect(find.text('1'), findsNothing);
//
// // Tap the '+' icon and trigger a frame.
// await tester.tap(find.byIcon(Icons.add));
// await tester.pump();
//
// // Verify that our counter has incremented.
// expect(find.text('0'), findsNothing);
// expect(find.text('1'), findsOneWidget);
//});
}

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 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:photo_gallery/photo_gallery.dart';
import '../models/config.dart';
import '../views/hero_page.dart';
import '../models/gallery_media.dart';
import '/models/gallery_album.dart';
import '/models/medium.dart';
import '../models/media_file.dart';
import 'picker_listener.dart';
class PhoneGalleryController extends GetxController {
late Config config;
@ -17,15 +19,19 @@ class PhoneGalleryController extends GetxController {
{required this.onSelect,
required this.heroBuilder,
required this.isRecent,
required this.multipleMediaBuilder}) {
List<MediaFile>? initSelectedMedias,
required this.multipleMediasBuilder}) {
this.config = config ?? Config();
if (initSelectedMedias != null) {
_selectedFiles = initSelectedMedias.map((e) => e).toList();
}
}
bool isRecent;
Function(List<MediaFile> selectedMedias) onSelect;
Widget Function(String tag, MediaFile media, BuildContext context)?
heroBuilder;
Widget Function(List<MediaFile> medias, BuildContext context)?
multipleMediaBuilder;
multipleMediasBuilder;
GalleryAlbum? selectedAlbum;
List<GalleryAlbum> _galleryAlbums = [];
List<GalleryAlbum> get galleryAlbums => _galleryAlbums;
@ -36,10 +42,19 @@ class PhoneGalleryController extends GetxController {
bool _pickerMode = false;
bool get pickerMode => _pickerMode;
void updateSelectedFiles(List<MediaFile> medias) {
_selectedFiles = medias;
if(selectedFiles.isNotEmpty){
_pickerMode=true;
}
update();
}
void changeAlbum(GalleryAlbum? album) {
selectedAlbum = album;
selectedFiles.clear();
_selectedFiles.clear();
update();
updatePickerListener();
}
void unselectMedia(MediaFile file) {
@ -48,6 +63,7 @@ class PhoneGalleryController extends GetxController {
_pickerMode = false;
}
update();
updatePickerListener();
}
void selectMedia(MediaFile file) {
@ -58,17 +74,46 @@ class PhoneGalleryController extends GetxController {
_pickerMode = true;
}
update();
updatePickerListener();
}
void switchPickerMode(bool value) {
if (!value) {
selectedFiles.clear();
_selectedFiles.clear();
}
_pickerMode = value;
update();
updatePickerListener();
}
void updatePickerListener() {
if (GetInstance().isRegistered<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 {
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<Album> photoAlbums =
@ -90,15 +135,17 @@ class PhoneGalleryController extends GetxController {
if (lastPhotoDate == null) {
try {
entireGalleryAlbum.thumbnail = await videoAlbum.getThumbnail(highQuality: true);
entireGalleryAlbum.thumbnail =
await videoAlbum.getThumbnail(highQuality: true);
} catch (e) {
print(e);
}
} else if (lastVideoDate == null) {
} else {
if (lastVideoDate.isBefore(lastPhotoDate)) {
if (lastVideoDate.isAfter(lastPhotoDate)) {
try {
entireGalleryAlbum.thumbnail = await videoAlbum.getThumbnail(highQuality: true);
entireGalleryAlbum.thumbnail =
await videoAlbum.getThumbnail(highQuality: true);
} catch (e) {
entireGalleryAlbum.thumbnail = null;
print(e);
@ -115,16 +162,16 @@ class PhoneGalleryController extends GetxController {
tempGalleryAlbums.add(entireGalleryAlbum);
}
for (var videoAlbum in videoAlbums) {
print(videoAlbum.name);
GalleryAlbum galleryVideoAlbum = GalleryAlbum(album: videoAlbum);
await galleryVideoAlbum.initialize();
galleryVideoAlbum.setType = AlbumType.video;
tempGalleryAlbums.add(galleryVideoAlbum);
}
_galleryAlbums = tempGalleryAlbums;
_isInitialized = true;
update();
return GalleryMedia(tempGalleryAlbums);
} else {
return null;
}
}
GalleryAlbum? get recent {
@ -147,6 +194,15 @@ class PhoneGalleryController extends GetxController {
}
bool isSelectedMedia(MediaFile file) {
return _selectedFiles.any((element) => element == file);
return _selectedFiles.any((element) => element.medium.id == file.medium.id);
}
void disposeController() {
_galleryAlbums = [];
_selectedFiles = [];
_isInitialized = false;
selectedAlbum = null;
Get.delete<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;
export 'models/config.dart';
import 'dart:io';
import 'package:flutter/foundation.dart';
export 'models/media_file.dart';
export 'models/mode.dart';
export 'models/medium.dart';
export 'models/gallery_media.dart';
export 'models/gallery_album.dart';
export 'user_widgets/thumbnail_media.dart';
export 'user_widgets/album_categories_view.dart';
export 'user_widgets/album_medias.dart';
export 'user_widgets/date_category_view.dart';
export 'user_widgets/thumbnailAlbum.dart';
export 'user_widgets/files_stream_builder.dart';
export 'user_widgets/photo_provider.dart';
export 'user_widgets/video_provider.dart';
export 'user_widgets/media_provider.dart';
export 'views/bottom_sheet.dart';
export 'views/gallery_picker_view/gallery_picker_view.dart';
import 'package:flutter/material.dart';
import 'package:gallery_picker/models/gallery_media.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../controller/gallery_controller.dart';
import 'controller/bottom_sheet_controller.dart';
import 'controller/picker_listener.dart';
import 'models/config.dart';
import 'models/media_file.dart';
import 'views/album_category_view/gallery_categories_widget.dart';
import 'views/album_view/gallery_category_view_page.dart';
import 'views/album_view/gallery_category_view_widget.dart';
import 'views/hero_page.dart';
import 'views/gallery_picker_view/gallery_picker_view.dart';
void disposeGalleryPicker() {
GetInstance().delete<PhoneGalleryController>();
class GalleryPicker {
static Stream<List<MediaFile>> get listenSelectedFiles {
var controller = Get.put(PickerListener());
return controller.stream;
}
class GalleryPicker 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)?
multipleMediaBuilder;
bool startWithRecent;
GalleryPicker(
{super.key,
this.config,
required this.onSelect,
this.heroBuilder,
this.multipleMediaBuilder,
this.startWithRecent = false});
@override
State<GalleryPicker> createState() => _GalleryPickerState();
static void disposeSelectedFilesListener() {
if (GetInstance().isRegistered<PickerListener>()) {
Get.find<PickerListener>().dispose();
}
}
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);
static void dispose() {
if (GetInstance().isRegistered<PhoneGalleryController>()) {
galleryController = Get.find<PhoneGalleryController>();
config = galleryController.config;
Get.find<PhoneGalleryController>().disposeController();
}
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 {
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;
}
@override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height - 119;
return GetBuilder<PhoneGalleryController>(builder: (galleryController) {
return galleryController.selectedAlbum == null
? Scaffold(
backgroundColor: config.backgroundColor,
appBar: AppBar(
elevation: 0,
backgroundColor: config.appbarColor,
leading: TextButton(
onPressed: () {
galleryController.switchPickerMode(false);
},
child: Icon(
Icons.arrow_back,
color: config.appbarIconColor,
)),
title: getTitle(),
actions: [
!galleryController.pickerMode && galleryController.isRecent
? TextButton(
onPressed: () {
galleryController.switchPickerMode(true);
},
child: Icon(
Icons.check_box_outlined,
color: config.appbarIconColor,
))
: const SizedBox()
],
),
body: Column(
children: [
Container(
width: width,
height: 48,
color: config.appbarColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
decoration: galleryController.isRecent
? BoxDecoration(
border: Border(
bottom: BorderSide(
color: config.underlineColor,
width: 3.0,
),
))
: null,
height: 48,
width: width / 2,
child: TextButton(
onPressed: () {
_scrollController.animateTo(0,
duration: const Duration(milliseconds: 50),
curve: Curves.easeIn);
setState(() {
galleryController.isRecent = true;
galleryController.switchPickerMode(false);
});
},
child: Text(config.recents,
style: galleryController.isRecent
? config.selectedMenuStyle
: config.unselectedMenuStyle)),
),
Container(
decoration: !galleryController.isRecent
? BoxDecoration(
border: Border(
bottom: BorderSide(
color: config.underlineColor,
width: 3.0,
),
))
: null,
height: 48,
width: width / 2,
child: TextButton(
onPressed: () {
_scrollController.animateTo(width,
duration: const Duration(milliseconds: 50),
curve: Curves.easeIn);
galleryController.isRecent = false;
galleryController.switchPickerMode(false);
},
child: Text(
config.gallery,
style: galleryController.isRecent
? config.unselectedMenuStyle
: config.selectedMenuStyle,
)),
)
],
),
),
Expanded(
child: PageView(
controller: _scrollController,
onPageChanged: (value) {
if (value == 0) {
galleryController.isRecent = true;
galleryController.switchPickerMode(false);
} else {
galleryController.isRecent = false;
galleryController.switchPickerMode(false);
}
},
scrollDirection: Axis.horizontal,
children: [
SizedBox(
width: width,
height: height - 48,
child: galleryController.isInitialized
? GalleryCategoryViewWidget(
galleryAlbum: galleryController.recent!,
)
: const Center(
child: CircularProgressIndicator(
color: Colors.grey,
))),
SizedBox(
width: width,
height: height - 48,
child: GalleryCategoriesWidget())
]),
),
],
),
)
: GalleryAlbumViewPage(
album: galleryController.selectedAlbum!,
);
});
}
Widget getTitle() {
if (galleryController.pickerMode &&
galleryController.selectedFiles.isEmpty) {
return Text(
config.tapPhotoSelect,
style: config.appbarTextStyle,
);
} else if (galleryController.pickerMode &&
galleryController.selectedFiles.isNotEmpty) {
return Text(
"${galleryController.selectedFiles.length} ${config.selected}",
style: config.appbarTextStyle,
);
} else {
return const SizedBox();
}
static Future<GalleryMedia?> get collectGallery async {
return await PhoneGalleryController.collectGallery;
}
}

View File

@ -15,7 +15,7 @@ class Config {
unselectedMenuStyle;
String recents, gallery, lastMonth, lastWeek, tapPhotoSelect, selected;
List<String> months;
Mode? mode;
Mode mode;
Config(
{Color? backgroundColor,
@ -47,7 +47,7 @@ class Config {
"November",
"December"
],
this.mode = Mode.dark,
this.mode = Mode.light,
Widget? selectIcon}) {
if (backgroundColor == null) {
this.backgroundColor = mode == Mode.dark

View File

@ -14,6 +14,12 @@ class GalleryAlbum {
dateCategories.expand((element) => element.files).toList().length;
String? get name => album.name;
List<MediaFile> get medias {
return dateCategories
.expand<MediaFile>((element) => element.files)
.toList();
}
late AlbumType type;
set setType(AlbumType type) {
@ -37,9 +43,9 @@ class GalleryAlbum {
Future<void> initialize() async {
List<DateCategory> dateCategory = [];
for (var file in sortAlbumMediaDates((await album.listMedia()).items)) {
MediaFile mediaFile = MediaFile(mediaFile: file);
String name = getDateCategory(file);
for (var medium in sortAlbumMediaDates((await album.listMedia()).items)) {
MediaFile mediaFile = MediaFile(medium: medium);
String name = getDateCategory(medium);
if (dateCategory.any((element) => element.name == name)) {
dateCategory
.singleWhere((element) => element.name == name)
@ -59,7 +65,7 @@ class GalleryAlbum {
DateTime? get lastDate {
if (dateCategories.isNotEmpty) {
return dateCategories.first.files.first.mediaFile.lastDate;
return dateCategories.first.files.first.medium.lastDate;
} else {
return null;
}
@ -69,9 +75,6 @@ class GalleryAlbum {
dateCategories.expand((element) => element.files).toList();
String getDateCategory(Medium mediaFile) {
//print("Creation Date: " + mediaFile.creationDateae.toString());
//print("Modified Date: " + mediaFile.modifiedDate!.toString());
//print("-------------------------------------------------------");
if (daysBetween(mediaFile.lastDate!) <= 3) {
return "Recent";
} else if (daysBetween(mediaFile.lastDate!) > 3 &&
@ -112,19 +115,19 @@ class GalleryAlbum {
for (var category in dateCategories) {
category.files.sort((a, b) {
if (a.mediaFile.lastDate == null) {
if (a.medium.lastDate == null) {
return 1;
} else if (b.mediaFile.lastDate == null) {
} else if (b.medium.lastDate == null) {
return -1;
} else {
return b.mediaFile.lastDate!.compareTo(a.mediaFile.lastDate!);
return b.medium.lastDate!.compareTo(a.medium.lastDate!);
}
});
}
}
void addFile(MediaFile file) {
String name = getDateCategory(file.mediaFile);
String name = getDateCategory(file.medium);
if (dateCategories.any((element) => element.name == name)) {
dateCategories
.singleWhere((element) => element.name == name)

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 'package:flutter/foundation.dart';
import 'package:get/get.dart';
@ -6,32 +7,68 @@ import 'package:video_thumbnail/video_thumbnail.dart';
import '../controller/gallery_controller.dart';
class MediaFile {
Medium mediaFile;
Medium medium;
MediumType? type;
Uint8List? thumbnail;
Uint8List? data;
late String id;
bool thumbnailFailed = false;
MediaFile({required this.mediaFile}) {
type = mediaFile.mediumType;
File? file;
MediaFile({required this.medium}) {
type = medium.mediumType;
id = medium.id;
}
Future<void> getThumbnail() async {
print("zooooortlamaca");
try {
thumbnail =
Uint8List.fromList(await mediaFile.getThumbnail(highQuality: true));
Uint8List.fromList(await medium.getThumbnail(highQuality: true));
} catch (e) {
thumbnailFailed = true;
}
}
void unselect() {
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);
}
}
}
void select() {
void select({PhoneGalleryController? controller}) {
if (controller != null) {
controller.selectMedia(this);
} else {
if (GetInstance().isRegistered<PhoneGalleryController>()) {
Get.find<PhoneGalleryController>().selectMedia(this);
}
bool get isSelected =>
Get.find<PhoneGalleryController>().isSelectedMedia(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/material.dart';
import 'package:gallery_picker/models/gallery_album.dart';
import 'package:get/get.dart';
import '../controller/gallery_controller.dart';
import '/models/gallery_album.dart';
import '../models/config.dart';
import '../models/mode.dart';
import '/models/media_file.dart';
import 'package:photo_gallery/photo_gallery.dart';
import 'package:transparent_image/transparent_image.dart';
class ThumbnailAlbum extends StatelessWidget {
final GalleryAlbum album;
final Color failIconColor;
final Config config = Get.find<PhoneGalleryController>().config;
ThumbnailAlbum({super.key, required this.album, required this.failIconColor});
final Color failIconColor, backgroundColor;
final Mode mode;
ThumbnailAlbum({super.key, required this.album, required this.failIconColor,required this.mode,required this.backgroundColor});
Color adjustFailedBgColor() {
if (config.mode == Mode.dark) {
if (mode == Mode.dark) {
return lighten(
config.backgroundColor,
backgroundColor,
);
} else {
return darken(config.backgroundColor);
return darken(backgroundColor);
}
}
@ -58,26 +53,12 @@ class ThumbnailAlbum extends StatelessWidget {
color: failIconColor,
))
else if (album.thumbnail != null)
FadeInImage(
fadeInDuration: const Duration(milliseconds: 200),
Image.memory(
Uint8List.fromList(album.thumbnail!),
fit: BoxFit.cover,
placeholder: MemoryImage(kTransparentImage),
image: MemoryImage(Uint8List.fromList(album.thumbnail!)),
)
else
const SizedBox(),
Positioned(
bottom: 10,
left: 10,
child: Icon(
album.type == AlbumType.video
? Icons.video_camera_back
: album.type == AlbumType.image
? Icons.image
: Icons.folder,
color: Colors.white,
size: 20,
)),
Opacity(
opacity: 0.5,
child: Container(

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 {
final MediaFile file;
final Color failIconColor;
final Config config = Get.find<PhoneGalleryController>().config;
ThumbnailMedia({super.key, required this.file, required this.failIconColor});
final Config config;
ThumbnailMedia({super.key, required this.file, required this.failIconColor,required this.config});
Color adjustFailedBgColor() {
if (config.mode == Mode.dark) {
@ -58,7 +58,7 @@ class ThumbnailMedia extends StatelessWidget {
))
else if (file.thumbnail != null)
Hero(
tag: file.mediaFile.id,
tag: file.medium.id,
child: FadeInImage(
fadeInDuration: const Duration(milliseconds: 200),
fit: BoxFit.cover,
@ -73,7 +73,7 @@ class ThumbnailMedia extends StatelessWidget {
bottom: 10,
left: 10,
child: Icon(
file.mediaFile.mediumType == MediumType.video
file.medium.mediumType == MediumType.video
? Icons.video_camera_back
: null,
color: Colors.white,

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 'media_view.dart';
class DateCategoryWidget extends StatelessWidget {
DateCategoryWidget({super.key, required this.category}){
print(category.name);
}
class DateCategoryWiew extends StatelessWidget {
PhoneGalleryController controller;
bool singleMedia;
DateCategoryWiew(
{super.key,
required this.category,
required this.controller,
required this.singleMedia});
DateCategory category;
int getRowCount() {
@ -31,7 +35,7 @@ class DateCategoryWidget extends StatelessWidget {
alignment: Alignment.centerLeft,
child: Text(
category.name,
style: Get.find<PhoneGalleryController>().config.textStyle,
style: controller.config.textStyle,
),
),
),
@ -43,7 +47,11 @@ class DateCategoryWidget extends StatelessWidget {
crossAxisSpacing: 1.0,
children: <Widget>[
...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:transparent_image/transparent_image.dart';
import '/views/ThumbnailMedia.dart';
import '../../controller/bottom_sheet_controller.dart';
import '../thumbnailMedia.dart';
import 'package:get/get.dart';
import 'package:photo_gallery/photo_gallery.dart';
import '../../../controller/gallery_controller.dart';
@ -8,8 +9,10 @@ import '../../../models/media_file.dart';
class MediaView extends StatelessWidget {
final MediaFile file;
var controller = Get.find<PhoneGalleryController>();
MediaView(this.file, {super.key});
PhoneGalleryController controller;
bool singleMedia;
MediaView(this.file,
{super.key, required this.controller, required this.singleMedia});
@override
Widget build(BuildContext context) {
return Stack(
@ -17,31 +20,63 @@ class MediaView extends StatelessWidget {
children: [
GestureDetector(
onLongPress: () {
file.select();
if (singleMedia) {
if (controller.heroBuilder != null) {
Navigator.of(context).push(
MaterialPageRoute<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: () {
if (controller.pickerMode) {
file.isSelected ? file.unselect() : file.select();
file.isSelected(controller: controller)!
? file.unselect(controller: controller)
: file.select(controller: controller);
} else {
if (controller.heroBuilder != null) {
Navigator.of(context).push(
MaterialPageRoute<void>(builder: (BuildContext context) {
return controller.heroBuilder!(file.mediaFile.id, file,context);
return controller.heroBuilder!(file.medium.id, file, context);
}));
} 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);
GetInstance().delete<PhoneGalleryController>();
controller.disposeController();
}
}
}
},
child: ThumbnailMedia(
file: file, failIconColor: controller.config.appbarIconColor),
file: file,
failIconColor: controller.config.appbarIconColor,
config: controller.config,
),
if (file.isSelected)
),
if (file.isSelected(controller: controller)!)
GestureDetector(
onTap: () {
file.isSelected ? file.unselect() : file.select();
file.isSelected(controller: controller)!
? file.unselect(controller: controller)
: file.select(controller: controller);
},
child: Opacity(
opacity: 0.5,

View File

@ -1,14 +1,15 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:photo_gallery/photo_gallery.dart';
import '../../controller/bottom_sheet_controller.dart';
import '../../controller/gallery_controller.dart';
import '../../models/config.dart';
class SelectedMediaWidget extends StatelessWidget {
var galleryController = Get.find<PhoneGalleryController>();
class SelectedMediasView extends StatelessWidget {
PhoneGalleryController controller;
late Config config;
SelectedMediaWidget({super.key}) {
config = galleryController.config;
SelectedMediasView({super.key, required this.controller}) {
config = controller.config;
}
@override
@ -29,7 +30,7 @@ class SelectedMediaWidget extends StatelessWidget {
child: ListView(
scrollDirection: Axis.horizontal,
children: [
for (var selectedMedia in galleryController.selectedFiles)
for (var selectedMedia in controller.selectedFiles)
Padding(
padding: const EdgeInsets.only(
top: 3.0, bottom: 3.0, right: 2),
@ -39,9 +40,9 @@ class SelectedMediaWidget extends StatelessWidget {
image: DecorationImage(
fit: BoxFit.fill,
image: ThumbnailProvider(
mediumId: selectedMedia.mediaFile.id,
mediumId: selectedMedia.medium.id,
mediumType:
selectedMedia.mediaFile.mediumType,
selectedMedia.medium.mediumType,
highQuality: true,
))),
child: SizedBox(
@ -55,25 +56,28 @@ class SelectedMediaWidget extends StatelessWidget {
),
TextButton(
onPressed: () {
if (galleryController.selectedFiles.length == 1 &&
galleryController.heroBuilder != null) {
if (controller.selectedFiles.length == 1 &&
controller.heroBuilder != null) {
Navigator.of(context).push(
MaterialPageRoute<void>(builder: (BuildContext context) {
return galleryController.heroBuilder!(
galleryController.selectedFiles[0].mediaFile.id,
galleryController.selectedFiles[0],context);
return controller.heroBuilder!(
controller.selectedFiles[0].medium.id,
controller.selectedFiles[0],
context);
}));
} else if (galleryController.multipleMediaBuilder != null) {
} else if (controller.multipleMediasBuilder != null) {
Navigator.of(context).push(
MaterialPageRoute<void>(builder: (BuildContext context) {
return galleryController
.multipleMediaBuilder!(galleryController.selectedFiles,context);
return controller.multipleMediasBuilder!(
controller.selectedFiles, context);
}));
} else {
galleryController.onSelect(galleryController.selectedFiles);
Navigator.pop(context);
if (!galleryController.isRecent) {
controller.onSelect(controller.selectedFiles);
if (GetInstance().isRegistered<BottomSheetController>()) {
Get.find<BottomSheetController>().close();
} else {
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
description: A new Flutter package project.
version: 0.0.1
homepage:
homepage: https://github.com/FlutterWay/gallery_picker
environment:
sdk: '>=2.18.5 <3.0.0'
@ -14,11 +14,12 @@ dependencies:
photo_gallery: ^1.1.1
permission_handler: ^10.2.0
transparent_image: ^2.0.0
camera: ^0.10.1
video_player: ^2.4.10
get: ^4.6.5
video_thumbnail: ^0.5.3
intl: ^0.18.0
modal_bottom_sheet: ^2.1.2
bottom_sheet_bar: ^2.3.8
dev_dependencies:
flutter_test:
sdk: flutter

View File

@ -1,7 +1,5 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:gallery_picker/gallery_picker.dart';
void main() {
//test('adds one to input values', () {
// final calculator = Calculator();