upgrading package to 0.3.0

This commit is contained in:
Furkan 2023-01-20 09:58:21 +03:00
parent 24acdf8401
commit 3d84689c77
32 changed files with 1393 additions and 1226 deletions

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":"2023-01-06 16:12:29.754813","version":"3.3.10"} {"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":"2023-01-20 09:57:50.239997","version":"3.3.10"}

View File

@ -93,4 +93,10 @@
## 0.2.3 ## 0.2.3
* thumbnail's resolotion upgraded * thumbnail's resolotion upgraded
## 0.3.0
* Package performance has been improved
* BottomSheetLayout changed into PickerScaffold
* PermissionDeniedPage added

View File

@ -2,7 +2,7 @@
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. 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.
<img src="https://raw.githubusercontent.com/FlutterWay/files/main/galleryPickerSlide.png" width="1200"/> <img src="https://raw.githubusercontent.com/FlutterWay/files/main/gallery_picker_views/gallery_picker_poster.png" width="1200"/>
## Features ## Features
@ -32,6 +32,8 @@ Gallery Picker is a flutter package that will allow you to pick media file(s), m
[✔] Examples provided (example/lib/examples) [✔] Examples provided (example/lib/examples)
[✔] Permission requests handled within the library
[✔] Null-safety [✔] Null-safety
You could find the code samples of the given gifs below in `/example/lib/examples` folder. You could find the code samples of the given gifs below in `/example/lib/examples` folder.
@ -40,16 +42,16 @@ You could find the code samples of the given gifs below in `/example/lib/example
<table> <table>
<tr> <tr>
<td style="text-align: center"> <td style="text-align: center">
<img src="https://raw.githubusercontent.com/FlutterWay/files/main/gallery_picker_light.gif" width="200"/> <img src="https://raw.githubusercontent.com/FlutterWay/files/main/gallery_picker_views/gallery_picker_light.gif" width="200"/>
</td> </td>
<td style="text-align: center"> <td style="text-align: center">
<img src="https://raw.githubusercontent.com/FlutterWay/files/main/gallery_picker_dark.gif" width="200"/> <img src="https://raw.githubusercontent.com/FlutterWay/files/main/gallery_picker_views/gallery_picker_dark.gif" width="200"/>
</td> </td>
<td style="text-align: center"> <td style="text-align: center">
<img src="https://raw.githubusercontent.com/FlutterWay/files/main/gallery_picker_destination.gif" width="200" /> <img src="https://raw.githubusercontent.com/FlutterWay/files/main/gallery_picker_views/gallery_picker_destination.gif" width="200" />
</td> </td>
<td style="text-align: center"> <td style="text-align: center">
<img src="https://raw.githubusercontent.com/FlutterWay/files/main/camera_page.gif" width="200" /> <img src="https://raw.githubusercontent.com/FlutterWay/files/main/gallery_picker_views/camera_page.gif" width="200" />
</td> </td>
</tr> </tr>
</table> </table>
@ -107,24 +109,23 @@ Dispose listener
GalleryPicker.disposeSelectedFilesListener(); GalleryPicker.disposeSelectedFilesListener();
``` ```
### BottomSheetLayout ### PickerScaffold
Gallery Picker could also work as a bottom sheet. Wrap your scaffold's body with BottomSheetLayout Gallery Picker could also work as a bottom sheet. Use PickerScaffold instead your Scaffold.
There is an example at `example/lib/examples/bottom_sheet_example.dart` to see how it could be done. There is an example at `example/lib/examples/bottom_sheet_example.dart` to see how it could be done.
```dart ```dart
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return PickerScaffold(
appBar: AppBar( backgroundColor: Colors.transparent,
title: Text(widget.title), onSelect: (media) {},
), initSelectedMedia: initMedia,
body: BottomSheetLayout( config: Config(mode: Mode.dark),
config: Config() body: Container(),
onSelect: (media) {}, )
child: Column( }
children: [
``` ```
### Customizable destination page ### Customizable destination page
@ -225,6 +226,7 @@ List<MediaFile>? media = await GalleryPicker.pickMedia(
context: context, context: context,
config: Config( config: Config(
backgroundColor: Colors.white, backgroundColor: Colors.white,
permissionDeniedPage:PermissionDeniedPage(),
appbarColor: Colors.white, appbarColor: Colors.white,
bottomSheetColor: const Color.fromARGB(255, 247, 248, 250), bottomSheetColor: const Color.fromARGB(255, 247, 248, 250),
appbarIconColor: const Color.fromARGB(255, 130, 141, 148), appbarIconColor: const Color.fromARGB(255, 130, 141, 148),
@ -326,6 +328,19 @@ GalleryPicker returns MediaFile list. You can reach out features below.
[✔] getData function [✔] getData function
[✔] Check if the file selected in gallery picker [✔] Check if the file selected in gallery picker
## Permission
Required permissions will be requested when gallery picker is launched. In case of user's rejection of request, the problem will be handled within gallery picker package.
<img src="https://raw.githubusercontent.com/FlutterWay/files/main/gallery_picker_views/permission_denied.gif" width="200" />
### Customizing Permission Denied Page
```dart
Config(
permissionDeniedPage: PermissionDeniedPage(),
)
```
## Ready-to-use widgets ## Ready-to-use widgets
### ThumbnailMedia ### ThumbnailMedia
@ -413,6 +428,44 @@ AlbumCategoriesView(
) )
``` ```
## Breaking Changes From 0.2.3
### BottomSheetLayout changed into PickerScaffold
Before:
```dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: BottomSheetLayout(
config: Config()
onSelect: (media) {},
child: Column(
children: [
```
Now:
```dart
@override
Widget build(BuildContext context) {
return PickerScaffold(
backgroundColor: Colors.transparent,
onSelect: (media) {},
initSelectedMedia: initMedia,
config: Config(mode: Mode.dark),
body: Container(),
)
}
```
## Examples ## Examples
Check out our examples! Check out our examples!
### Standart Gallery Picker ### Standart Gallery Picker

View File

@ -15,136 +15,135 @@ class _BottomSheetExampleState extends State<BottomSheetExample> {
var controller = PageController(initialPage: 0); var controller = PageController(initialPage: 0);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return PickerScaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
body: BottomSheetLayout( onSelect: (List<MediaFile> selectedMedias) {
onSelect: (List<MediaFile> selectedMedias) { this.selectedMedias = selectedMedias;
this.selectedMedias = selectedMedias; pageIndex = 0;
pageIndex = 0; if (this.selectedMedias.isNotEmpty) {
if (this.selectedMedias.isNotEmpty) { Future.delayed(const Duration(milliseconds: 500)).then((value) {
Future.delayed(const Duration(milliseconds: 500)).then((value) { controller.animateToPage(0,
controller.animateToPage(0, duration: const Duration(milliseconds: 500),
duration: const Duration(milliseconds: 500), curve: Curves.easeIn);
curve: Curves.easeIn); });
}); }
} setState(() {});
setState(() {}); },
}, config: Config(mode: Mode.dark),
config: Config(mode: Mode.dark), body: Center(
child: Center( child: Column(
child: Column( mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
children: <Widget>[ const Spacer(),
const Spacer(), const Text(
const Text( 'These are your selected medias',
'These are your selected medias', style: TextStyle(color: Colors.black),
), ),
const Divider(), const Divider(),
Expanded( Expanded(
flex: 5, flex: 5,
child: Stack(children: [ child: Stack(children: [
if (selectedMedias.isNotEmpty) if (selectedMedias.isNotEmpty)
PageView( PageView(
controller: controller, controller: controller,
children: [ children: [
for (var media in selectedMedias) for (var media in selectedMedias)
Center( Center(
child: MediaProvider( child: MediaProvider(
media: media, media: media,
), ),
) )
], ],
), ),
if (selectedMedias.isNotEmpty) if (selectedMedias.isNotEmpty)
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: TextButton( child: TextButton(
onPressed: () { onPressed: () {
if (pageIndex < selectedMedias.length - 1) { if (pageIndex < selectedMedias.length - 1) {
pageIndex++; 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, controller.animateToPage(pageIndex,
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
curve: Curves.easeIn); curve: Curves.easeIn);
setState(() {}); setState(() {});
}, }
child: Container( },
width: 65, child: const Icon(
height: 50, Icons.chevron_right,
decoration: BoxDecoration( size: 100,
border: Border.all( color: Colors.red,
width: 2, )),
color: pageIndex == i ),
? Colors.red if (selectedMedias.isNotEmpty)
: Colors.black)), Align(
child: ThumbnailMedia( alignment: Alignment.centerLeft,
media: selectedMedias[i], 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: 1, const Spacer(
flex: 1,
),
TextButton(
onPressed: () {
GalleryPicker.openSheet();
},
child: const Icon(
Icons.open_in_new,
size: 40,
), ),
TextButton( ),
onPressed: () { const Spacer(
GalleryPicker.openSheet(); flex: 1,
}, ),
child: const Icon( ],
Icons.open_in_new,
size: 40,
),
),
const Spacer(
flex: 1,
),
],
),
), ),
), ),
); );

View File

@ -17,20 +17,22 @@ class _WhatsappPickPhotoState extends State<WhatsappPickPhoto> {
GalleryMedia? gallery; GalleryMedia? gallery;
List<MediaFile> selectedMedias = []; List<MediaFile> selectedMedias = [];
List<CameraDescription>? cameras; List<CameraDescription>? cameras;
CameraLensDirection cameraLensDirection = CameraLensDirection.front; CameraLensDirection cameraLensDirection = CameraLensDirection.back;
@override @override
void initState() { void initState() {
initCamera(); initCamera();
fetchMedias(); fetchMedias();
GalleryPicker.listenSelectedFiles.listen((medias) { GalleryPicker.listenSelectedFiles.listen((medias) {
selectedMedias = medias; selectedMedias = medias;
setState(() {}); if (mounted) {
setState(() {});
}
}); });
super.initState(); super.initState();
} }
Future<void> fetchMedias() async { Future<void> fetchMedias() async {
gallery = await GalleryPicker.collectGallery; gallery = await GalleryPicker.initializeGallery;
setState(() {}); setState(() {});
} }
@ -72,211 +74,207 @@ class _WhatsappPickPhotoState extends State<WhatsappPickPhoto> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return PickerScaffold(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
body: BottomSheetLayout( onSelect: (List<MediaFile> selectedMedias) {
onSelect: (List<MediaFile> selectedMedias) { this.selectedMedias = selectedMedias;
this.selectedMedias = selectedMedias; if (mounted) {
setState(() {}); setState(() {});
}, }
initSelectedMedia: selectedMedias, },
config: Config(mode: Mode.dark), initSelectedMedia: selectedMedias,
child: Stack( config: Config(mode: Mode.dark),
children: [ body: Stack(
if (cameraController != null && children: [
cameraController!.value.isInitialized) if (cameraController != null && cameraController!.value.isInitialized)
SizedBox( SizedBox(
height: MediaQuery.of(context).size.height, height: MediaQuery.of(context).size.height,
child: CameraPreview( child: CameraPreview(
cameraController!, cameraController!,
),
), ),
if (gallery != null && gallery!.recent != null) ),
Positioned( if (gallery != null && gallery!.recent != null)
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( Positioned(
bottom: 20, bottom: 100,
child: SizedBox( child: SizedBox(
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
child: Row( height: 65,
mainAxisAlignment: MainAxisAlignment.spaceAround, child: ListView(
children: [ scrollDirection: Axis.horizontal,
TextButton( children: [
onPressed: () { for (var media in gallery!.recent!.files)
GalleryPicker.openSheet(); Padding(
}, padding: const EdgeInsets.symmetric(horizontal: 5),
child: Container( child: GestureDetector(
decoration: BoxDecoration( onTap: () {
color: Colors.black.withOpacity(0.5), if (selectedMedias.isEmpty) {
shape: BoxShape.circle), Navigator.push(
padding: const EdgeInsets.all(8), context,
child: const Icon( MaterialPageRoute(
Icons.image, builder: (context) =>
size: 20, MultipleMediasView([media])),
color: Colors.white, );
), } else {
), selectedMedias
), .any((element) => element.id == media.id)
GestureDetector( ? selectedMedias.removeWhere(
onTap: () async { (element) => element.id == media.id)
setState(() { : selectedMedias.add(media);
anyProcess = true; setState(() {});
}); }
Future.delayed(const Duration(milliseconds: 100)) },
.then((value) => setState(() { onLongPress: () {
anyProcess = false; if (selectedMedias
})); .any((element) => element.id == media.id)) {
}, selectedMedias.removeWhere(
onLongPressStart: (value) { (element) => element.id == media.id);
setState(() { } else {
isRecording = true; selectedMedias.add(media);
anyProcess = true; }
}); setState(() {});
}, },
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( child: Container(
decoration: BoxDecoration( width: 65,
color: height: 65,
anyProcess ? Colors.red : Colors.transparent, decoration: BoxDecoration(
shape: BoxShape.circle, 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,
), ),
), ),
TextButton( ),
onPressed: () { GestureDetector(
if (cameraLensDirection == onTap: () async {
CameraLensDirection.front) { setState(() {
cameraLensDirection = CameraLensDirection.back; anyProcess = true;
} else { });
cameraLensDirection = CameraLensDirection.front; Future.delayed(const Duration(milliseconds: 100))
} .then((value) => setState(() {
initCamera(); 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( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withOpacity(0.5), color: anyProcess ? Colors.red : Colors.transparent,
shape: BoxShape.circle), shape: BoxShape.circle,
padding: const EdgeInsets.all(8),
child: const Icon(
Icons.cameraswitch,
size: 20,
color: Colors.white,
), ),
), ),
), ),
], ),
), 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

@ -15,13 +15,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
bottom_sheet_bar: bottom_sheet_scaffold:
dependency: transitive dependency: transitive
description: description:
name: bottom_sheet_bar name: bottom_sheet_scaffold
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.8" version: "0.1.1"
camera: camera:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -141,7 +141,7 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "0.2.3" version: "0.3.0"
get: get:
dependency: transitive dependency: transitive
description: description:
@ -191,13 +191,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.5" version: "0.1.5"
measure_size:
dependency: transitive
description:
name: measure_size
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -205,6 +198,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.0"
page_transition:
dependency: transitive
description:
name: page_transition
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.9"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -379,5 +379,5 @@ packages:
source: hosted source: hosted
version: "0.5.3" version: "0.5.3"
sdks: sdks:
dart: ">=2.18.5 <3.0.0" dart: ">=2.18.6 <3.0.0"
flutter: ">=3.0.0" flutter: ">=3.0.0"

View File

@ -1,41 +0,0 @@
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,3 +1,4 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -15,13 +16,22 @@ import 'picker_listener.dart';
class PhoneGalleryController extends GetxController { class PhoneGalleryController extends GetxController {
late Config config; late Config config;
PhoneGalleryController(Config? config, void configuration(Config? config,
{required this.onSelect, {required dynamic Function(List<MediaFile>) onSelect,
required this.heroBuilder, required Widget Function(String, MediaFile, BuildContext)? heroBuilder,
required this.isRecent, required bool isRecent,
required bool startWithRecent,
required List<MediaFile>? initSelectedMedias, required List<MediaFile>? initSelectedMedias,
required List<MediaFile>? extraRecentMedia, required List<MediaFile>? extraRecentMedia,
required this.multipleMediasBuilder}) { required Widget Function(List<MediaFile>, BuildContext)?
multipleMediasBuilder}) {
this.onSelect = onSelect;
this.heroBuilder = heroBuilder;
this.isRecent = isRecent;
this.startWithRecent = startWithRecent;
this.multipleMediasBuilder = multipleMediasBuilder;
pageController = PageController();
pickerPageController = PageController(initialPage: startWithRecent ? 0 : 1);
this.config = config ?? Config(); this.config = config ?? Config();
if (initSelectedMedias != null) { if (initSelectedMedias != null) {
_selectedFiles = initSelectedMedias.map((e) => e).toList(); _selectedFiles = initSelectedMedias.map((e) => e).toList();
@ -32,16 +42,21 @@ class PhoneGalleryController extends GetxController {
if (selectedFiles.isNotEmpty) { if (selectedFiles.isNotEmpty) {
_pickerMode = true; _pickerMode = true;
} }
configurationCompleted = true;
} }
bool isRecent;
Function(List<MediaFile> selectedMedias) onSelect; late bool startWithRecent;
late bool isRecent;
bool permissionGranted = false;
bool configurationCompleted = false;
late Function(List<MediaFile> selectedMedias) onSelect;
Widget Function(String tag, MediaFile media, BuildContext context)? Widget Function(String tag, MediaFile media, BuildContext context)?
heroBuilder; heroBuilder;
Widget Function(List<MediaFile> medias, BuildContext context)? Widget Function(List<MediaFile> medias, BuildContext context)?
multipleMediasBuilder; multipleMediasBuilder;
GalleryAlbum? selectedAlbum; GalleryMedia? _media;
List<GalleryAlbum> _galleryAlbums = []; GalleryMedia? get media => _media;
List<GalleryAlbum> get galleryAlbums => _galleryAlbums; List<GalleryAlbum> get galleryAlbums => _media == null ? [] : _media!.albums;
List<MediaFile> _selectedFiles = []; List<MediaFile> _selectedFiles = [];
List<MediaFile>? _extraRecentMedia; List<MediaFile>? _extraRecentMedia;
List<MediaFile> get selectedFiles => _selectedFiles; List<MediaFile> get selectedFiles => _selectedFiles;
@ -50,6 +65,26 @@ class PhoneGalleryController extends GetxController {
List<MediaFile>? get extraRecentMedia => _extraRecentMedia; List<MediaFile>? get extraRecentMedia => _extraRecentMedia;
bool _pickerMode = false; bool _pickerMode = false;
bool get pickerMode => _pickerMode; bool get pickerMode => _pickerMode;
late PageController pageController;
late PageController pickerPageController;
GalleryAlbum? selectedAlbum;
void resetBottomSheetView() {
if (permissionGranted) {
isRecent = true;
if (selectedAlbum == null) {
pickerPageController.jumpToPage(0);
} else {
pageController.jumpToPage(0);
}
selectedAlbum = null;
update();
}
}
void updateConfig(Config? config) {
this.config = config ?? Config();
}
void updateSelectedFiles(List<MediaFile> media) { void updateSelectedFiles(List<MediaFile> media) {
_selectedFiles = media.map((e) => e).toList(); _selectedFiles = media.map((e) => e).toList();
@ -69,11 +104,26 @@ class PhoneGalleryController extends GetxController {
update(); update();
} }
void changeAlbum(GalleryAlbum? album) { Future<void> changeAlbum(
selectedAlbum = album; {required GalleryAlbum album,
required BuildContext context,
required PhoneGalleryController controller,
required bool singleMedia,
required bool isBottomSheet}) async {
_selectedFiles.clear(); _selectedFiles.clear();
update(); selectedAlbum = album;
updatePickerListener(); updatePickerListener();
await pageController.animateToPage(1,
duration: const Duration(milliseconds: 500), curve: Curves.easeIn);
}
Future<void> backToPicker() async {
_selectedFiles.clear();
_pickerMode = false;
pickerPageController = PageController(initialPage: 1);
await pageController.animateToPage(0,
duration: const Duration(milliseconds: 500), curve: Curves.easeIn);
selectedAlbum = null;
} }
void unselectMedia(MediaFile file) { void unselectMedia(MediaFile file) {
@ -99,10 +149,10 @@ class PhoneGalleryController extends GetxController {
void switchPickerMode(bool value) { void switchPickerMode(bool value) {
if (!value) { if (!value) {
_selectedFiles.clear(); _selectedFiles.clear();
updatePickerListener();
} }
_pickerMode = value; _pickerMode = value;
update(); update();
updatePickerListener();
} }
void updatePickerListener() { void updatePickerListener() {
@ -112,19 +162,33 @@ class PhoneGalleryController extends GetxController {
} }
static Future<bool> promptPermissionSetting() async { static Future<bool> promptPermissionSetting() async {
await PhoneGalleryController.requestStatus(Permission.storage);
if (Platform.isIOS) {
await PhoneGalleryController.requestStatus(Permission.photos);
}
if (Platform.isIOS && if (Platform.isIOS &&
await Permission.storage.request().isGranted && await Permission.storage.isGranted &&
await Permission.photos.request().isGranted || await Permission.photos.isGranted ||
Platform.isAndroid && await Permission.storage.request().isGranted) { Platform.isAndroid && await Permission.storage.isGranted) {
return true; return true;
} }
return false; return false;
} }
static Future<void> requestStatus(Permission permission) async {
while (true) {
try {
await permission.request();
break;
} catch (e) {
await Future.delayed(const Duration(milliseconds: 500), () {});
}
}
}
Future<void> initializeAlbums() async { Future<void> initializeAlbums() async {
GalleryMedia? media = await PhoneGalleryController.collectGallery; _media = await PhoneGalleryController.collectGallery;
if (media != null) { if (_media != null) {
_galleryAlbums = media.albums;
if (_extraRecentMedia != null) { if (_extraRecentMedia != null) {
GalleryAlbum? recentTmp = recent; GalleryAlbum? recentTmp = recent;
if (recentTmp != null) { if (recentTmp != null) {
@ -132,12 +196,24 @@ class PhoneGalleryController extends GetxController {
recentTmp.files.any((file) => element.id == file.id)); recentTmp.files.any((file) => element.id == file.id));
} }
} }
permissionGranted = true;
_isInitialized = true;
} else {
permissionGranted = false;
permissionListener();
} }
_isInitialized = true;
update(); update();
} }
void permissionListener() {
Timer.periodic(const Duration(seconds: 1), (timer) async {
if (await Permission.storage.isGranted) {
initializeAlbums();
timer.cancel();
}
});
}
static Future<GalleryMedia?> get collectGallery async { static Future<GalleryMedia?> get collectGallery async {
if (await promptPermissionSetting()) { if (await promptPermissionSetting()) {
List<GalleryAlbum> tempGalleryAlbums = []; List<GalleryAlbum> tempGalleryAlbums = [];
@ -205,8 +281,8 @@ class PhoneGalleryController extends GetxController {
} }
GalleryAlbum? get recent { GalleryAlbum? get recent {
return _galleryAlbums.isNotEmpty return galleryAlbums.isNotEmpty
? _galleryAlbums.singleWhere((element) => element.album.name == "All") ? galleryAlbums.singleWhere((element) => element.album.name == "All")
: null; : null;
} }
//GalleryAlbum? get recent { //GalleryAlbum? get recent {
@ -245,14 +321,13 @@ class PhoneGalleryController extends GetxController {
} }
bool isSelectedMedia(MediaFile file) { bool isSelectedMedia(MediaFile file) {
return _selectedFiles.any((element) => element.medium.id == file.medium.id); return _selectedFiles.any((element) => element.id == file.id);
} }
void disposeController() { void disposeController() {
_galleryAlbums = []; _media = null;
_selectedFiles = []; _selectedFiles = [];
_isInitialized = false; _isInitialized = false;
selectedAlbum = null;
Get.delete<PhoneGalleryController>(); Get.delete<PhoneGalleryController>();
update(); update();
} }

15
lib/functions/color.dart Normal file
View File

@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
Color darken(Color color, [double amount = .03]) {
assert(amount >= 0 && amount <= 1);
final hsl = HSLColor.fromColor(color);
final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0));
return hslDark.toColor();
}
Color lighten(Color color, [double amount = .07]) {
assert(amount >= 0 && amount <= 1);
final hsl = HSLColor.fromColor(color);
final hslLight = hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0));
return hslLight.toColor();
}

View File

@ -15,13 +15,13 @@ export 'user_widgets/gallery_picker_builder.dart';
export 'user_widgets/photo_provider.dart'; export 'user_widgets/photo_provider.dart';
export 'user_widgets/video_provider.dart'; export 'user_widgets/video_provider.dart';
export 'user_widgets/media_provider.dart'; export 'user_widgets/media_provider.dart';
export 'views/bottom_sheet.dart'; export 'views/picker_scaffold.dart';
export 'views/gallery_picker_view/gallery_picker_view.dart'; export 'views/gallery_picker_view/gallery_picker_view.dart';
import 'package:bottom_sheet_scaffold/bottom_sheet_scaffold.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gallery_picker/models/gallery_media.dart'; import 'package:gallery_picker/models/gallery_media.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../controller/gallery_controller.dart'; import '../../controller/gallery_controller.dart';
import 'controller/bottom_sheet_controller.dart';
import 'controller/picker_listener.dart'; import 'controller/picker_listener.dart';
import 'models/config.dart'; import 'models/config.dart';
import 'models/media_file.dart'; import 'models/media_file.dart';
@ -43,9 +43,6 @@ class GalleryPicker {
if (GetInstance().isRegistered<PhoneGalleryController>()) { if (GetInstance().isRegistered<PhoneGalleryController>()) {
Get.find<PhoneGalleryController>().disposeController(); Get.find<PhoneGalleryController>().disposeController();
} }
if (GetInstance().isRegistered<BottomSheetController>()) {
Get.find<BottomSheetController>().disposeController();
}
} }
static Future<List<MediaFile>?> pickMedia( static Future<List<MediaFile>?> pickMedia(
@ -101,26 +98,35 @@ class GalleryPicker {
} }
static Future<void> openSheet() async { static Future<void> openSheet() async {
if (GetInstance().isRegistered<BottomSheetController>()) { BottomSheetPanel.open();
await Get.find<BottomSheetController>().open();
}
} }
static Future<void> closeSheet() async { static Future<void> closeSheet() async {
if (GetInstance().isRegistered<BottomSheetController>()) { BottomSheetPanel.close();
await Get.find<BottomSheetController>().close(); if (GetInstance().isRegistered<PhoneGalleryController>()) {
Get.find<PhoneGalleryController>().resetBottomSheetView();
} }
} }
static bool get isSheetOpened { static bool get isSheetOpened {
if (GetInstance().isRegistered<BottomSheetController>()) { return BottomSheetPanel.isOpen;
return Get.find<BottomSheetController>().sheetController.isExpanded; }
} else {
return false; static bool get isSheetExpanded {
} return BottomSheetPanel.isExpanded;
}
static bool get isSheetCollapsed {
return BottomSheetPanel.isCollapsed;
} }
static Future<GalleryMedia?> get collectGallery async { static Future<GalleryMedia?> get collectGallery async {
return await PhoneGalleryController.collectGallery; return await PhoneGalleryController.collectGallery;
} }
static Future<GalleryMedia?> get initializeGallery async {
final controller = Get.put(PhoneGalleryController());
await controller.initializeAlbums();
return controller.media;
}
} }

View File

@ -4,6 +4,7 @@ import 'mode.dart';
class Config { class Config {
late Widget selectIcon; late Widget selectIcon;
Widget? permissionDeniedPage;
late Color backgroundColor, late Color backgroundColor,
appbarColor, appbarColor,
appbarIconColor, appbarIconColor,
@ -33,6 +34,7 @@ class Config {
TextStyle? unselectedMenuStyle, TextStyle? unselectedMenuStyle,
TextStyle? textStyle, TextStyle? textStyle,
TextStyle? appbarTextStyle, TextStyle? appbarTextStyle,
this.permissionDeniedPage,
this.recents = "RECENTS", this.recents = "RECENTS",
this.recent = "Recent", this.recent = "Recent",
this.gallery = "GALLERY", this.gallery = "GALLERY",

View File

@ -10,7 +10,7 @@ import 'config.dart';
class GalleryAlbum { class GalleryAlbum {
late Album album; late Album album;
late List<int>? thumbnail; List<int>? thumbnail;
List<DateCategory> dateCategories = []; List<DateCategory> dateCategories = [];
late AlbumType type; late AlbumType type;
int get count => int get count =>
@ -49,8 +49,8 @@ class GalleryAlbum {
Future<void> initialize() async { Future<void> initialize() async {
List<DateCategory> dateCategory = []; List<DateCategory> dateCategory = [];
for (var medium in sortAlbumMediaDates((await album.listMedia()).items)) { for (var medium in sortAlbumMediaDates((await album.listMedia()).items)) {
MediaFile mediaFile = MediaFile(medium: medium); MediaFile mediaFile = MediaFile.medium(medium);
String name = getDateCategory(medium); String name = getDateCategory(mediaFile);
if (dateCategory.any((element) => element.name == name)) { if (dateCategory.any((element) => element.name == name)) {
dateCategory dateCategory
.singleWhere((element) => element.name == name) .singleWhere((element) => element.name == name)
@ -71,8 +71,9 @@ class GalleryAlbum {
} }
DateTime? get lastDate { DateTime? get lastDate {
if (dateCategories.isNotEmpty) { if (dateCategories.isNotEmpty &&
return dateCategories.first.files.first.medium.lastDate; dateCategories.first.files.first.medium != null) {
return dateCategories.first.files.first.medium!.lastDate;
} else { } else {
return null; return null;
} }
@ -81,23 +82,22 @@ class GalleryAlbum {
List<MediaFile> get files => List<MediaFile> get files =>
dateCategories.expand((element) => element.files).toList(); dateCategories.expand((element) => element.files).toList();
String getDateCategory(Medium mediaFile) { String getDateCategory(MediaFile media) {
Config config = GetInstance().isRegistered<PhoneGalleryController>() Config config = GetInstance().isRegistered<PhoneGalleryController>()
? Get.find<PhoneGalleryController>().config ? Get.find<PhoneGalleryController>().config
: Config(); : Config();
if (daysBetween(mediaFile.lastDate!) <= 3) { DateTime? lastDate = media.lastModified;
lastDate = lastDate ?? DateTime.now();
if (daysBetween(lastDate) <= 3) {
return config.recent; return config.recent;
} else if (daysBetween(mediaFile.lastDate!) > 3 && } else if (daysBetween(lastDate) > 3 && daysBetween(lastDate) <= 7) {
daysBetween(mediaFile.lastDate!) <= 7) {
return config.lastWeek; return config.lastWeek;
} else if (daysBetween(mediaFile.lastDate!) > 7 && } else if (daysBetween(lastDate) > 7 && daysBetween(lastDate) <= 31) {
daysBetween(mediaFile.lastDate!) <= 31) {
return config.lastMonth; return config.lastMonth;
} else if (daysBetween(mediaFile.lastDate!) > 31 && } else if (daysBetween(lastDate) > 31 && daysBetween(lastDate) <= 365) {
daysBetween(mediaFile.lastDate!) <= 365) { return DateFormat.MMMM().format(lastDate).toString();
return DateFormat.MMMM().format(mediaFile.lastDate!).toString();
} else { } else {
return DateFormat.y().format(mediaFile.lastDate!).toString(); return DateFormat.y().format(lastDate).toString();
} }
} }
@ -113,7 +113,7 @@ class GalleryAlbum {
} else if (b.lastDate == null) { } else if (b.lastDate == null) {
return -1; return -1;
} else { } else {
return a.lastDate!.compareTo(b.lastDate!); return b.lastDate!.compareTo(a.lastDate!);
} }
}); });
return mediumList; return mediumList;
@ -125,19 +125,19 @@ class GalleryAlbum {
for (var category in dateCategories) { for (var category in dateCategories) {
category.files.sort((a, b) { category.files.sort((a, b) {
if (a.medium.lastDate == null) { if (a.medium == null) {
return 1; return 1;
} else if (b.medium.lastDate == null) { } else if (b.medium == null) {
return -1; return -1;
} else { } else {
return b.medium.lastDate!.compareTo(a.medium.lastDate!); return b.medium!.lastDate!.compareTo(a.medium!.lastDate!);
} }
}); });
} }
} }
void addFile(MediaFile file) { void addFile(MediaFile file) {
String name = getDateCategory(file.medium); String name = getDateCategory(file);
if (dateCategories.any((element) => element.name == name)) { if (dateCategories.any((element) => element.name == name)) {
dateCategories dateCategories
.singleWhere((element) => element.name == name) .singleWhere((element) => element.name == name)

View File

@ -1,108 +1,66 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:photo_gallery/photo_gallery.dart'; import 'package:photo_gallery/photo_gallery.dart';
import 'package:video_thumbnail/video_thumbnail.dart'; import 'package:video_thumbnail/video_thumbnail.dart';
import '../controller/gallery_controller.dart';
enum MediaType { image, video } enum MediaType { image, video }
class MediaFile { class MediaFile {
late Medium medium; Medium? _medium;
late MediaType type; File? _file;
Uint8List? thumbnail; Uint8List? thumbnail;
Uint8List? data; late MediaType _type;
late String id; late String _id;
bool thumbnailFailed = false; bool get isVideo => _type == MediaType.video;
File? file; bool get isImage => _type == MediaType.image;
bool _noMedium = false; Medium? get medium => _medium;
bool get isVideo => type == MediaType.video; MediaType get type => _type;
bool get isImage => type == MediaType.image; String get id => _id;
File? get file => _file;
DateTime? get lastModified =>
_medium != null ? _medium!.modifiedDate : _file!.lastModifiedSync();
MediaFile({required this.medium}) { MediaFile.medium(Medium medium) {
type = medium.mediumType == MediumType.video _medium = medium;
_type = _medium!.mediumType == MediumType.video
? MediaType.video ? MediaType.video
: MediaType.image; : MediaType.image;
id = medium.id; _id = _medium!.id;
} }
MediaFile.file({required this.id, required this.file, required this.type}) { MediaFile.file(
_noMedium = true; {required String id, required File file, required MediaType type}) {
medium = Medium( _file = file;
id: id, _id = id;
mediumType: _type = type;
type == MediaType.image ? MediumType.image : MediumType.video);
} }
Future<Uint8List?> getThumbnail() async { Future<Uint8List> getThumbnail({bool highQuality = true}) async {
if (thumbnail == null) { if (_medium == null) {
try { thumbnail = isVideo
if (_noMedium) { ? (await VideoThumbnail.thumbnailData(
thumbnail = isVideo video: _file!.path,
? await VideoThumbnail.thumbnailData( imageFormat: ImageFormat.JPEG,
video: file!.path, quality: highQuality ? 100 : 20,
imageFormat: ImageFormat.JPEG, ))!
quality: 100, : await getData();
) } else {
: await getData(); thumbnail = Uint8List.fromList(
} else { await _medium!.getThumbnail(highQuality: highQuality));
thumbnail =
Uint8List.fromList(await medium.getThumbnail(highQuality: true));
}
} catch (e) {
thumbnailFailed = true;
}
} }
return thumbnail; return thumbnail!;
} }
Future<File> getFile() async { Future<File> getFile() async {
if (file == null) { if (_medium != null) {
file = await medium.getFile(); return await _medium!.getFile();
return file!;
} else { } else {
return file!; return _file!;
} }
} }
Future<Uint8List> getData() async { Future<Uint8List> getData() async {
if (file == null) { _file ??= await getFile();
await getFile(); return _file!.readAsBytesSync();
}
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({PhoneGalleryController? controller}) {
if (controller != null) {
controller.selectMedia(this);
} else {
if (GetInstance().isRegistered<PhoneGalleryController>()) {
Get.find<PhoneGalleryController>().selectMedia(this);
}
}
}
bool? isSelected({PhoneGalleryController? controller}) {
if (controller != null) {
return controller.isSelectedMedia(this);
} else {
if (GetInstance().isRegistered<PhoneGalleryController>()) {
return Get.find<PhoneGalleryController>().isSelectedMedia(this);
} else {
return null;
}
}
} }
} }

View File

@ -1,59 +1,45 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.dart';
import '../models/media_file.dart'; import '../models/media_file.dart';
class PhotoProvider extends StatefulWidget { class PhotoProvider extends StatelessWidget {
final MediaFile media; final MediaFile media;
final BoxFit fit; final BoxFit fit;
final double? width, height; final double? width, height;
final Color? backgroundColor;
final Widget Function(BuildContext context)? onFailBuilder;
const PhotoProvider({ const PhotoProvider({
super.key, super.key,
required this.media, required this.media,
this.onFailBuilder,
this.fit = BoxFit.contain, this.fit = BoxFit.contain,
this.backgroundColor,
this.width, this.width,
this.height, this.height,
}); });
@override
State<PhotoProvider> createState() => _PhotoProviderState();
}
class _PhotoProviderState extends State<PhotoProvider> {
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(() {});
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_media != widget.media) { return FutureBuilder(
_media = widget.media; future: media.getData(),
if (_media.data == null) { builder: ((context, snapshot) {
initMedia(); return Container(
} color: backgroundColor,
} width: width,
return _media.data == null height: height,
? SizedBox( child: (snapshot.hasError && onFailBuilder != null)
width: widget.width, ? onFailBuilder!(context)
height: widget.height, : (snapshot.hasData)
) ? FadeInImage(
: Image.memory( fadeInDuration: const Duration(milliseconds: 200),
_media.data!, fit: BoxFit.cover,
width: widget.width, placeholder: MemoryImage(kTransparentImage),
height: widget.height, image: MemoryImage(snapshot.data!),
fit: widget.fit, )
); : const SizedBox(),
);
}),
);
} }
} }

View File

@ -1,6 +1,7 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.dart';
import '/models/gallery_album.dart'; import '/models/gallery_album.dart';
import '../models/mode.dart'; import '../models/mode.dart';
@ -58,9 +59,11 @@ class ThumbnailAlbum extends StatelessWidget {
color: failIconColor, color: failIconColor,
)) ))
else if (album.thumbnail != null) else if (album.thumbnail != null)
Image.memory( FadeInImage(
Uint8List.fromList(album.thumbnail!), image: MemoryImage(Uint8List.fromList(album.thumbnail!)),
fadeInDuration: const Duration(milliseconds: 200),
fit: BoxFit.cover, fit: BoxFit.cover,
placeholder: MemoryImage(kTransparentImage),
) )
else else
const SizedBox(), const SizedBox(),

View File

@ -5,49 +5,83 @@ import 'package:transparent_image/transparent_image.dart';
class ThumbnailMedia extends StatelessWidget { class ThumbnailMedia extends StatelessWidget {
final MediaFile media; final MediaFile media;
final bool noIcon; final bool noIcon;
final double? width, height;
final Color? backgroundColor;
final BoxFit fit;
final bool highQuality;
final double radius, borderWidth;
final Color borderColor;
final Widget Function(MediaFile media, BuildContext context)? onErrorBuilder; final Widget Function(MediaFile media, BuildContext context)? onErrorBuilder;
const ThumbnailMedia( const ThumbnailMedia(
{super.key, {super.key,
required this.media, required this.media,
this.fit = BoxFit.cover,
this.onErrorBuilder, this.onErrorBuilder,
this.radius = 0,
this.highQuality = true,
this.borderColor = Colors.transparent,
this.borderWidth = 0,
this.width,
this.height,
this.backgroundColor,
this.noIcon = false}); this.noIcon = false});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder( return FutureBuilder(
future: media.thumbnail == null ? media.getThumbnail() : null, future: media.thumbnail == null
? media.getThumbnail(highQuality: highQuality)
: null,
builder: (context, snapshot) { builder: (context, snapshot) {
return Stack( return Container(
fit: StackFit.passthrough, width: width,
children: [ height: height,
if (media.thumbnailFailed && onErrorBuilder == null) decoration: BoxDecoration(
Icon( color: backgroundColor,
media.isImage borderRadius: BorderRadius.circular(radius),
? Icons.image_not_supported border: Border.all(color: borderColor, width: borderWidth)),
: Icons.videocam_off_rounded, child: ClipRRect(
color: Colors.grey, borderRadius: BorderRadius.circular(radius),
) child: Stack(
else if (media.thumbnailFailed && onErrorBuilder == null) fit: StackFit.passthrough,
onErrorBuilder!(media, context) children: [
else if (media.thumbnail != null) if (snapshot.hasError && onErrorBuilder == null)
FadeInImage( Center(
fadeInDuration: const Duration(milliseconds: 200), child: Icon(
fit: BoxFit.cover, media.isImage
placeholder: MemoryImage(kTransparentImage), ? Icons.image_not_supported
image: MemoryImage(media.thumbnail!), : Icons.videocam_off_rounded,
) color: Colors.grey,
else ),
const SizedBox(), )
if (media.thumbnail != null && !media.thumbnailFailed && !noIcon) else if (snapshot.hasError && onErrorBuilder == null)
Positioned( onErrorBuilder!(media, context)
bottom: 10, else if (media.thumbnail != null)
left: 10, FadeInImage(
child: Icon( width: width,
media.isVideo ? Icons.video_camera_back : null, height: height,
color: Colors.white, fadeInDuration: const Duration(milliseconds: 200),
size: 20, fit: fit,
)), placeholder: MemoryImage(kTransparentImage),
], image: MemoryImage(media.thumbnail!),
)
else
SizedBox(
width: width,
height: height,
),
if (media.thumbnail != null && !noIcon)
Positioned(
bottom: 10,
left: 10,
child: Icon(
media.isVideo ? Icons.video_camera_back : null,
color: Colors.white,
size: 20,
)),
],
),
),
); );
}); });
} }

View File

@ -6,7 +6,14 @@ import '../../../controller/gallery_controller.dart';
class AlbumCategoriesView extends StatelessWidget { class AlbumCategoriesView extends StatelessWidget {
final PhoneGalleryController controller; final PhoneGalleryController controller;
final Config config; final Config config;
AlbumCategoriesView(this.controller, {super.key}) final bool isBottomSheet;
final bool singleMedia;
AlbumCategoriesView(
{super.key,
required this.controller,
required this.isBottomSheet,
required this.singleMedia})
: config = controller.config; : config = controller.config;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -19,7 +26,12 @@ class AlbumCategoriesView extends StatelessWidget {
children: <Widget>[ children: <Widget>[
...controller.galleryAlbums.map( ...controller.galleryAlbums.map(
(album) => GestureDetector( (album) => GestureDetector(
onTap: () => controller.changeAlbum(album), onTap: () => controller.changeAlbum(
album: album,
isBottomSheet: isBottomSheet,
controller: controller,
singleMedia: singleMedia,
context: context),
child: Stack(fit: StackFit.passthrough, children: [ child: Stack(fit: StackFit.passthrough, children: [
ThumbnailAlbum( ThumbnailAlbum(
album: album, album: album,

View File

@ -1,56 +1,51 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gallery_picker/models/gallery_album.dart'; import 'package:gallery_picker/models/gallery_album.dart';
import '../../controller/bottom_sheet_controller.dart';
import '../../controller/gallery_controller.dart'; import '../../controller/gallery_controller.dart';
import '../gallery_picker_view/tappable_appbar.dart';
class AlbumAppBar extends StatelessWidget with PreferredSizeWidget { class AlbumAppBar extends StatelessWidget with PreferredSizeWidget {
final PhoneGalleryController controller; final PhoneGalleryController controller;
final BottomSheetController? bottomSheetController;
final GalleryAlbum album; final GalleryAlbum album;
final bool isBottomSheet;
const AlbumAppBar( const AlbumAppBar(
{super.key, {super.key,
required this.bottomSheetController,
required this.album, required this.album,
required this.controller}); required this.controller,
required this.isBottomSheet});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TappableAppbar( return AppBar(
controller: bottomSheetController, elevation: 0,
child: AppBar( foregroundColor: controller.config.appbarIconColor,
elevation: 0, backgroundColor: controller.config.appbarColor,
foregroundColor: controller.config.appbarIconColor, leading: TextButton(
backgroundColor: controller.config.appbarColor, onPressed: () async {
leading: TextButton( controller.backToPicker();
onPressed: () { },
controller.changeAlbum(null); child: Icon(
}, Icons.arrow_back,
child: Icon( color: controller.config.appbarIconColor,
Icons.arrow_back, )),
color: controller.config.appbarIconColor, title: getTitle(),
)), actions: [
title: getTitle(), !controller.pickerMode
actions: [ ? TextButton(
!controller.pickerMode onPressed: () {
? TextButton( controller.switchPickerMode(true);
onPressed: () { },
controller.switchPickerMode(true); child: Icon(
}, Icons.check_box_outlined,
child: Icon( color: controller.config.appbarIconColor,
Icons.check_box_outlined, ))
color: controller.config.appbarIconColor, : const SizedBox()
)) ],
: const SizedBox()
],
),
); );
} }
Widget getTitle() { Widget getTitle() {
if (!controller.pickerMode && controller.selectedFiles.isEmpty) { if (!controller.pickerMode && controller.selectedFiles.isEmpty) {
return Text( return Text(
album.name!, album.name ?? "Unnamed Album",
style: controller.config.appbarTextStyle, style: controller.config.appbarTextStyle,
); );
} else if (controller.pickerMode && controller.selectedFiles.isEmpty) { } else if (controller.pickerMode && controller.selectedFiles.isEmpty) {

View File

@ -7,14 +7,15 @@ import 'selected_medias_view.dart';
class AlbumMediasView extends StatelessWidget { class AlbumMediasView extends StatelessWidget {
final PhoneGalleryController controller; final PhoneGalleryController controller;
final bool singleMedia; final bool singleMedia;
final bool isCollapsedSheet; final bool isBottomSheet;
const AlbumMediasView( const AlbumMediasView(
{super.key, {super.key,
required this.galleryAlbum, required this.galleryAlbum,
required this.controller, required this.controller,
required this.isCollapsedSheet, required this.isBottomSheet,
required this.singleMedia}); required this.singleMedia});
final GalleryAlbum galleryAlbum; final GalleryAlbum galleryAlbum;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(
@ -26,7 +27,7 @@ class AlbumMediasView extends StatelessWidget {
category: category, category: category,
controller: controller, controller: controller,
singleMedia: singleMedia, singleMedia: singleMedia,
isCollapsedSheet: isCollapsedSheet, isBottomSheet: isBottomSheet,
), ),
], ],
), ),
@ -35,6 +36,7 @@ class AlbumMediasView extends StatelessWidget {
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: SelectedMediasView( child: SelectedMediasView(
controller: controller, controller: controller,
isBottomSheet: isBottomSheet,
)) ))
], ],
); );

View File

@ -2,38 +2,47 @@ import 'package:flutter/material.dart';
import 'package:gallery_picker/views/album_view/album_appbar.dart'; import 'package:gallery_picker/views/album_view/album_appbar.dart';
import '../../../controller/gallery_controller.dart'; import '../../../controller/gallery_controller.dart';
import '../../../models/gallery_album.dart'; import '../../../models/gallery_album.dart';
import '../../controller/bottom_sheet_controller.dart';
import '../../models/config.dart';
import 'album_medias_view.dart'; import 'album_medias_view.dart';
class AlbumPage extends StatelessWidget { class AlbumPage extends StatelessWidget {
final bool singleMedia; final bool singleMedia;
final PhoneGalleryController controller; final PhoneGalleryController controller;
final BottomSheetController? bottomSheetController; final GalleryAlbum? album;
final GalleryAlbum album; final bool isBottomSheet;
final bool isCollapsedSheet;
const AlbumPage( const AlbumPage(
{super.key, {super.key,
required this.album, required this.album,
required this.controller, required this.controller,
required this.singleMedia, required this.singleMedia,
required this.isCollapsedSheet, required this.isBottomSheet});
required this.bottomSheetController});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Config config = controller.config; return WillPopScope(
return Scaffold( child: Scaffold(
backgroundColor: config.backgroundColor, backgroundColor: controller.config.backgroundColor,
appBar: AlbumAppBar( appBar: album != null
bottomSheetController: bottomSheetController, ? AlbumAppBar(
album: album, album: album!,
controller: controller), controller: controller,
body: AlbumMediasView( isBottomSheet: isBottomSheet,
galleryAlbum: album, )
controller: controller, : null,
isCollapsedSheet: isCollapsedSheet, body: album != null
singleMedia: singleMedia, ? AlbumMediasView(
), galleryAlbum: album!,
); controller: controller,
isBottomSheet: isBottomSheet,
singleMedia: singleMedia,
)
: Center(
child: Text(
"No Album Found",
style: controller.config.textStyle,
)),
),
onWillPop: () async {
controller.backToPicker();
return false;
});
} }
} }

View File

@ -8,13 +8,13 @@ class DateCategoryWiew extends StatelessWidget {
final PhoneGalleryController controller; final PhoneGalleryController controller;
final bool singleMedia; final bool singleMedia;
final DateCategory category; final DateCategory category;
final bool isCollapsedSheet; final bool isBottomSheet;
const DateCategoryWiew( const DateCategoryWiew(
{super.key, {super.key,
required this.category, required this.category,
required this.controller, required this.controller,
required this.isCollapsedSheet, required this.isBottomSheet,
required this.singleMedia}); required this.singleMedia});
int getRowCount() { int getRowCount() {
@ -45,14 +45,14 @@ class DateCategoryWiew extends StatelessWidget {
size: MediaQuery.of(context).size.width, size: MediaQuery.of(context).size.width,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
crossAxisCount: 4, crossAxisCount: 4,
mainAxisSpacing: 1.0, mainAxisSpacing: 3.0,
crossAxisSpacing: 1.0, crossAxisSpacing: 3.0,
children: <Widget>[ children: <Widget>[
...category.files.map( ...category.files.map(
(medium) => MediaView(medium, (medium) => MediaView(medium,
controller: controller, controller: controller,
singleMedia: singleMedia, singleMedia: singleMedia,
isCollapsedSheet: isCollapsedSheet), isBottomSheet: isBottomSheet),
), ),
], ],
), ),

View File

@ -1,6 +1,5 @@
import 'package:bottom_sheet_scaffold/bottom_sheet_scaffold.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../controller/bottom_sheet_controller.dart';
import 'package:get/get.dart';
import '../../../controller/gallery_controller.dart'; import '../../../controller/gallery_controller.dart';
import '../../../models/media_file.dart'; import '../../../models/media_file.dart';
import '../thumbnail_media_file.dart'; import '../thumbnail_media_file.dart';
@ -9,89 +8,69 @@ class MediaView extends StatelessWidget {
final MediaFile file; final MediaFile file;
final PhoneGalleryController controller; final PhoneGalleryController controller;
final bool singleMedia; final bool singleMedia;
final bool isCollapsedSheet; final bool isBottomSheet;
const MediaView(this.file, const MediaView(this.file,
{super.key, {super.key,
required this.controller, required this.controller,
required this.singleMedia, required this.singleMedia,
required this.isCollapsedSheet}); required this.isBottomSheet});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
GestureDetector( ThumbnailMediaFile(
onLongPress: () { onLongPress: () {
if (singleMedia) { if (singleMedia) {
if (controller.heroBuilder != null) { if (controller.heroBuilder != null) {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute<void>(builder: (BuildContext context) { MaterialPageRoute<void>(builder: (BuildContext context) {
return controller.heroBuilder!(file.medium.id, file, context); return controller.heroBuilder!(file.id, file, context);
})); }));
} else {
controller.selectedFiles.add(file);
controller.onSelect(controller.selectedFiles);
controller.updatePickerListener();
if (GetInstance().isRegistered<BottomSheetController>()) {
Get.find<BottomSheetController>().close();
} else { } else {
Navigator.pop(context); controller.selectedFiles.add(file);
controller.disposeController(); controller.onSelect(controller.selectedFiles);
controller.updatePickerListener();
if (isBottomSheet) {
BottomSheetPanel.close();
} else {
Navigator.pop(context);
controller.disposeController();
}
} }
}
} else {
file.select(controller: controller);
}
},
onTap: () {
if (controller.pickerMode) {
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.medium.id, file, context);
}));
} else { } else {
controller.selectedFiles.add(file); controller.selectMedia(file);
controller.onSelect(controller.selectedFiles);
controller.updatePickerListener();
if (GetInstance().isRegistered<BottomSheetController>()) {
Get.find<BottomSheetController>().close();
} else {
Navigator.pop(context);
controller.disposeController();
}
} }
}
},
child: ThumbnailMediaFile(
file: file,
failIconColor: controller.config.appbarIconColor,
controller: controller,
isCollapsedSheet: isCollapsedSheet),
),
if (file.isSelected(controller: controller)!)
GestureDetector(
onTap: () {
file.isSelected(controller: controller)!
? file.unselect(controller: controller)
: file.select(controller: controller);
}, },
child: Opacity( onTap: () {
opacity: 0.5, if (controller.pickerMode) {
child: Container( if (controller.isSelectedMedia(file)) {
color: Colors.black, controller.unselectMedia(file);
child: const Icon( } else {
Icons.check, controller.selectMedia(file);
color: Colors.white, }
size: 45, } else {
), if (controller.heroBuilder != null) {
), Navigator.of(context).push(
), MaterialPageRoute<void>(builder: (BuildContext context) {
), return controller.heroBuilder!(file.id, file, context);
}));
} else {
controller.selectedFiles.add(file);
controller.onSelect(controller.selectedFiles);
controller.updatePickerListener();
if (isBottomSheet) {
BottomSheetPanel.close();
} else {
Navigator.pop(context);
controller.disposeController();
}
}
}
},
file: file,
failIconColor: controller.config.appbarIconColor,
controller: controller),
], ],
); );
} }

View File

@ -0,0 +1,62 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.dart';
import '../../controller/gallery_controller.dart';
import '../../functions/color.dart';
import '../../models/mode.dart';
class SelectedMediaThumbnail extends StatelessWidget {
final Uint8List? data;
final double? width, height;
final BoxFit fit;
final PhoneGalleryController controller;
final Color failIconColor;
const SelectedMediaThumbnail({
super.key,
required this.failIconColor,
required this.controller,
required this.data,
required this.width,
required this.height,
this.fit = BoxFit.cover,
});
Color adjustFailedBgColor() {
if (controller.config.mode == Mode.dark) {
return lighten(
controller.config.backgroundColor,
);
} else {
return darken(controller.config.backgroundColor);
}
}
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
decoration: BoxDecoration(
color: adjustFailedBgColor(),
borderRadius: BorderRadius.circular(5),
),
child: data != null
? FadeInImage(
width: width,
height: height,
fadeInDuration: const Duration(milliseconds: 200),
fit: fit,
placeholder: MemoryImage(kTransparentImage),
image: MemoryImage(data!),
)
: Center(
child: Icon(
Icons.browser_not_supported,
size: 50,
color: failIconColor,
),
),
);
}
}

View File

@ -1,13 +1,15 @@
import 'package:bottom_sheet_scaffold/bottom_sheet_scaffold.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:gallery_picker/views/thumbnail_media_file.dart';
import '../../controller/bottom_sheet_controller.dart';
import '../../controller/gallery_controller.dart'; import '../../controller/gallery_controller.dart';
import '../../models/config.dart'; import '../../models/config.dart';
class SelectedMediasView extends StatelessWidget { class SelectedMediasView extends StatelessWidget {
final PhoneGalleryController controller; final PhoneGalleryController controller;
final Config config; final Config config;
SelectedMediasView({super.key, required this.controller}) final bool isBottomSheet;
SelectedMediasView(
{super.key, required this.controller, required this.isBottomSheet})
: config = controller.config; : config = controller.config;
@override @override
@ -27,37 +29,19 @@ class SelectedMediasView extends StatelessWidget {
child: ListView( child: ListView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
children: [ children: [
for (var selectedMedia in controller.selectedFiles) for (var mediaFile in controller.selectedFiles)
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 3.0, bottom: 3.0, right: 2), top: 3.0, bottom: 3.0, right: 2),
child: !selectedMedia.thumbnailFailed child: ThumbnailMediaFile(
? Container( file: mediaFile,
decoration: BoxDecoration( width: 50,
borderRadius: BorderRadius.circular(5), height: 55,
image: DecorationImage( radius: 5,
fit: BoxFit.fill, noIcon: true,
image: Image.memory( noSelectedIcon: true,
selectedMedia.thumbnail!, failIconColor: controller.config.appbarIconColor,
fit: BoxFit.fill, controller: controller),
).image),
),
child: const SizedBox(
width: 47,
height: 47,
),
)
: Container(
width: 47,
height: 47,
alignment: Alignment.center,
child: Icon(
selectedMedia.isImage
? Icons.image_not_supported
: Icons.videocam_off_rounded,
color: Colors.grey,
),
),
), ),
], ],
), ),
@ -69,7 +53,7 @@ class SelectedMediasView extends StatelessWidget {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute<void>(builder: (BuildContext context) { MaterialPageRoute<void>(builder: (BuildContext context) {
return controller.heroBuilder!( return controller.heroBuilder!(
controller.selectedFiles[0].medium.id, controller.selectedFiles[0].id,
controller.selectedFiles[0], controller.selectedFiles[0],
context); context);
})); }));
@ -81,8 +65,8 @@ class SelectedMediasView extends StatelessWidget {
})); }));
} else { } else {
controller.onSelect(controller.selectedFiles); controller.onSelect(controller.selectedFiles);
if (GetInstance().isRegistered<BottomSheetController>()) { if (isBottomSheet) {
Get.find<BottomSheetController>().close(); BottomSheetPanel.close();
} else { } else {
Navigator.pop(context); Navigator.pop(context);
controller.disposeController(); controller.disposeController();

View File

@ -1,167 +0,0 @@
import 'package:bottom_sheet_bar/bottom_sheet_bar.dart';
import 'package:flutter/material.dart';
import 'package:gallery_picker/controller/gallery_controller.dart';
import '/gallery_picker.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>? initSelectedMedia;
final List<MediaFile>? extraRecentMedia;
final bool singleMedia;
final Function(List<MediaFile> selectedMedia) onSelect;
final Widget Function(String tag, MediaFile media, BuildContext context)?
heroBuilder;
final Widget Function(List<MediaFile> media, BuildContext context)?
multipleMediaBuilder;
final bool startWithRecent;
BottomSheetLayout(
{super.key,
required this.child,
required this.onSelect,
this.config,
this.heroBuilder,
this.initSelectedMedia,
this.extraRecentMedia,
this.singleMedia = false,
this.multipleMediaBuilder,
this.startWithRecent = true}) {
if (GetInstance().isRegistered<PhoneGalleryController>()) {
if (initSelectedMedia != null) {
Get.find<PhoneGalleryController>()
.updateSelectedFiles(initSelectedMedia!);
}
if (extraRecentMedia != null) {
Get.find<PhoneGalleryController>()
.updateExtraRecentMedia(extraRecentMedia!);
}
}
}
@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(const 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(const Duration(milliseconds: 10));
setState(() {});
}
}
}
}
@override
void dispose() {
controller.disposeController();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GetBuilder<BottomSheetController>(builder: (controller) {
return BottomSheetBar(
willPopScope: true,
height: 0,
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,
multipleMediaBuilder: widget.multipleMediaBuilder,
singleMedia: widget.singleMedia,
initSelectedMedia: widget.initSelectedMedia,
extraRecentMedia: widget.extraRecentMedia,
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,
singleMedia: widget.singleMedia,
multipleMediaBuilder: widget.multipleMediaBuilder,
initSelectedMedia: widget.initSelectedMedia,
isCollapsedSheet: true,
extraRecentMedia: widget.extraRecentMedia,
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;
const 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

@ -1,7 +1,5 @@
import 'package:bottom_sheet_bar/bottom_sheet_bar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../controller/bottom_sheet_controller.dart';
import '../../controller/gallery_controller.dart'; import '../../controller/gallery_controller.dart';
import '../../models/config.dart'; import '../../models/config.dart';
import '../../models/gallery_album.dart'; import '../../models/gallery_album.dart';
@ -9,6 +7,7 @@ import '../../models/media_file.dart';
import '../album_categories_view/album_categories_view.dart'; import '../album_categories_view/album_categories_view.dart';
import '../album_view/album_page.dart'; import '../album_view/album_page.dart';
import '../album_view/album_medias_view.dart'; import '../album_view/album_medias_view.dart';
import 'permission_denied_view.dart';
import 'picker_appbar.dart'; import 'picker_appbar.dart';
import 'reload_gallery.dart'; import 'reload_gallery.dart';
@ -20,11 +19,10 @@ class GalleryPickerView extends StatefulWidget {
final Widget Function(List<MediaFile> media, BuildContext context)? final Widget Function(List<MediaFile> media, BuildContext context)?
multipleMediaBuilder; multipleMediaBuilder;
final bool startWithRecent; final bool startWithRecent;
final BottomSheetBarController? sheetController; final bool isBottomSheet;
final List<MediaFile>? initSelectedMedia; final List<MediaFile>? initSelectedMedia;
final List<MediaFile>? extraRecentMedia; final List<MediaFile>? extraRecentMedia;
final bool singleMedia; final bool singleMedia;
final bool isCollapsedSheet;
const GalleryPickerView( const GalleryPickerView(
{super.key, {super.key,
this.config, this.config,
@ -32,8 +30,7 @@ class GalleryPickerView extends StatefulWidget {
this.initSelectedMedia, this.initSelectedMedia,
this.extraRecentMedia, this.extraRecentMedia,
this.singleMedia = false, this.singleMedia = false,
this.isCollapsedSheet = false, this.isBottomSheet = false,
this.sheetController,
this.heroBuilder, this.heroBuilder,
this.multipleMediaBuilder, this.multipleMediaBuilder,
this.startWithRecent = false}); this.startWithRecent = false});
@ -44,43 +41,39 @@ class GalleryPickerView extends StatefulWidget {
class _GalleryPickerState extends State<GalleryPickerView> { class _GalleryPickerState extends State<GalleryPickerView> {
late PhoneGalleryController galleryController; late PhoneGalleryController galleryController;
BottomSheetController? bottomSheetController;
late PageController _scrollController;
bool noPhotoSeleceted = true; bool noPhotoSeleceted = true;
late Config config; late Config config;
@override @override
void initState() { void initState() {
_scrollController =
PageController(initialPage: widget.startWithRecent ? 0 : 1);
if (GetInstance().isRegistered<PhoneGalleryController>()) { if (GetInstance().isRegistered<PhoneGalleryController>()) {
galleryController = Get.find<PhoneGalleryController>(); galleryController = Get.find<PhoneGalleryController>();
config = galleryController.config; if (galleryController.configurationCompleted) {
galleryController.updateConfig(widget.config);
} else {
galleryController.configuration(widget.config,
onSelect: widget.onSelect,
startWithRecent: widget.startWithRecent,
heroBuilder: widget.heroBuilder,
multipleMediasBuilder: widget.multipleMediaBuilder,
initSelectedMedias: widget.initSelectedMedia,
extraRecentMedia: widget.extraRecentMedia,
isRecent: widget.startWithRecent);
}
} else { } else {
galleryController = Get.put(PhoneGalleryController(widget.config, galleryController = Get.put(PhoneGalleryController());
galleryController.configuration(widget.config,
onSelect: widget.onSelect, onSelect: widget.onSelect,
startWithRecent: widget.startWithRecent,
heroBuilder: widget.heroBuilder, heroBuilder: widget.heroBuilder,
multipleMediasBuilder: widget.multipleMediaBuilder, multipleMediasBuilder: widget.multipleMediaBuilder,
initSelectedMedias: widget.initSelectedMedia, initSelectedMedias: widget.initSelectedMedia,
extraRecentMedia: widget.extraRecentMedia, extraRecentMedia: widget.extraRecentMedia,
isRecent: widget.startWithRecent)); 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;
} }
config = galleryController.config;
if (!galleryController.isInitialized) { if (!galleryController.isInitialized) {
galleryController.initializeAlbums(); galleryController.initializeAlbums();
} }
if (galleryController.isRecent) {
_scrollController = PageController(initialPage: 0);
}
super.initState(); super.initState();
} }
@ -90,144 +83,153 @@ class _GalleryPickerState extends State<GalleryPickerView> {
} }
GalleryAlbum? selectedAlbum; GalleryAlbum? selectedAlbum;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width; double width = MediaQuery.of(context).size.width;
return GetBuilder<PhoneGalleryController>(builder: (controller) { return GetBuilder<PhoneGalleryController>(builder: (controller) {
if (controller.selectedAlbum == null && selectedAlbum != null) {
_scrollController = PageController(initialPage: 1);
}
selectedAlbum = controller.selectedAlbum;
return GetInstance().isRegistered<PhoneGalleryController>() return GetInstance().isRegistered<PhoneGalleryController>()
? controller.selectedAlbum == null ? controller.permissionGranted
? Scaffold( ? PageView(
backgroundColor: config.backgroundColor, controller: controller.pageController,
appBar: PickerAppBar( physics: const NeverScrollableScrollPhysics(),
controller: controller, children: [
bottomSheetController: bottomSheetController, Scaffold(
), backgroundColor: config.backgroundColor,
body: Column( appBar: PickerAppBar(
children: [ controller: controller,
Container( isBottomSheet: widget.isBottomSheet,
width: width, ),
height: 48, body: Column(
color: config.appbarColor, children: [
child: Row( Container(
mainAxisAlignment: MainAxisAlignment.spaceAround, width: width,
children: [ height: 48,
Container( color: config.appbarColor,
decoration: controller.isRecent child: Row(
? BoxDecoration( mainAxisAlignment: MainAxisAlignment.spaceAround,
border: Border( children: [
bottom: BorderSide( Container(
color: config.underlineColor, decoration: controller.isRecent
width: 3.0, ? BoxDecoration(
), border: Border(
)) bottom: BorderSide(
: null, color: config.underlineColor,
height: 48, width: 3.0,
width: width / 2, ),
child: TextButton( ))
onPressed: () { : null,
_scrollController.animateTo(0, height: 48,
duration: width: width / 2,
const Duration(milliseconds: 50), child: TextButton(
curve: Curves.easeIn); onPressed: () {
setState(() { controller.pickerPageController
controller.isRecent = true; .animateToPage(0,
controller.switchPickerMode(false); duration: const Duration(
}); milliseconds: 50),
}, curve: Curves.easeIn);
child: Text(config.recents, setState(() {
style: controller.isRecent controller.isRecent = true;
? config.selectedMenuStyle controller.switchPickerMode(false);
: config.unselectedMenuStyle)), });
},
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: () {
controller.pickerPageController
.animateToPage(1,
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,
)),
)
],
), ),
Container( ),
decoration: !controller.isRecent Expanded(
? BoxDecoration( child: PageView(
border: Border( controller: controller.pickerPageController,
bottom: BorderSide( onPageChanged: (value) {
color: config.underlineColor, if (value == 0) {
width: 3.0, controller.isRecent = true;
), controller.switchPickerMode(false);
)) } else {
: null,
height: 48,
width: width / 2,
child: TextButton(
onPressed: () {
_scrollController.animateTo(width,
duration:
const Duration(milliseconds: 50),
curve: Curves.easeIn);
controller.isRecent = false; controller.isRecent = false;
controller.switchPickerMode(false); controller.switchPickerMode(false);
}, }
child: Text( },
config.gallery, scrollDirection: Axis.horizontal,
style: controller.isRecent children: [
? config.unselectedMenuStyle controller.isInitialized &&
: config.selectedMenuStyle, controller.recent != null
)), ? AlbumMediasView(
) galleryAlbum: controller.recent!,
], controller: controller,
), isBottomSheet: widget.isBottomSheet,
singleMedia: widget.singleMedia)
: const Center(
child: CircularProgressIndicator(
color: Colors.grey,
)),
AlbumCategoriesView(
controller: controller,
isBottomSheet: widget.isBottomSheet,
singleMedia: widget.singleMedia,
)
]),
),
],
), ),
Expanded( ),
child: PageView( AlbumPage(
controller: _scrollController, album: controller.selectedAlbum,
onPageChanged: (value) { controller: controller,
if (value == 0) { singleMedia: widget.singleMedia,
controller.isRecent = true; isBottomSheet: widget.isBottomSheet)
controller.switchPickerMode(false); ],
} else {
controller.isRecent = false;
controller.switchPickerMode(false);
}
},
scrollDirection: Axis.horizontal,
children: [
controller.isInitialized
? AlbumMediasView(
galleryAlbum: controller.recent!,
controller: controller,
isCollapsedSheet: widget.isCollapsedSheet,
singleMedia: widget.singleMedia)
: const Center(
child: CircularProgressIndicator(
color: Colors.grey,
)),
AlbumCategoriesView(controller)
]),
),
],
),
)
: AlbumPage(
controller: controller,
album: controller.selectedAlbum!,
singleMedia: widget.singleMedia,
isCollapsedSheet: widget.isCollapsedSheet,
bottomSheetController: bottomSheetController,
) )
: controller.config.permissionDeniedPage ??
PermissionDeniedView(
config: controller.config,
)
: ReloadGallery( : ReloadGallery(
config, config,
onpressed: () async { onpressed: () async {
if (widget.sheetController != null) { galleryController = Get.put(PhoneGalleryController());
bottomSheetController = galleryController.configuration(widget.config,
Get.put(BottomSheetController(widget.sheetController!));
}
galleryController = Get.put(PhoneGalleryController(config,
onSelect: widget.onSelect, onSelect: widget.onSelect,
startWithRecent: widget.startWithRecent,
heroBuilder: widget.heroBuilder, heroBuilder: widget.heroBuilder,
multipleMediasBuilder: widget.multipleMediaBuilder,
initSelectedMedias: widget.initSelectedMedia, initSelectedMedias: widget.initSelectedMedia,
extraRecentMedia: widget.extraRecentMedia, extraRecentMedia: widget.extraRecentMedia,
multipleMediasBuilder: widget.multipleMediaBuilder, isRecent: widget.startWithRecent);
isRecent: widget.startWithRecent)); if (!controller.isInitialized) {
await controller.initializeAlbums(); await controller.initializeAlbums();
if (bottomSheetController != null) {
bottomSheetController!.galleryController = galleryController;
} }
setState(() {}); setState(() {});
}, },

View File

@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:gallery_picker/gallery_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class PermissionDeniedView extends StatelessWidget {
final Config config;
const PermissionDeniedView({super.key, required this.config});
@override
Widget build(BuildContext context) {
return Container(
color: config.backgroundColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 50,
),
Text(
"Please allow access to your photos",
style: TextStyle(
color: config.textStyle.color,
fontSize: 20,
fontWeight: FontWeight.w500),
),
const SizedBox(
height: 10,
),
Text(
"This lets access your photos and videos from your library.",
style: TextStyle(
color: config.textStyle.color,
),
),
const SizedBox(
height: 10,
),
TextButton(
onPressed: () async {
await openAppSettings();
},
child: const Text("Enable library access"),
)
],
),
);
}
}

View File

@ -1,52 +1,45 @@
import 'package:bottom_sheet_scaffold/bottom_sheet_scaffold.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../controller/bottom_sheet_controller.dart';
import '../../controller/gallery_controller.dart'; import '../../controller/gallery_controller.dart';
import 'tappable_appbar.dart';
class PickerAppBar extends StatelessWidget with PreferredSizeWidget { class PickerAppBar extends StatelessWidget with PreferredSizeWidget {
final PhoneGalleryController controller; final PhoneGalleryController controller;
final BottomSheetController? bottomSheetController; final bool isBottomSheet;
const PickerAppBar( const PickerAppBar(
{super.key, {super.key, required this.isBottomSheet, required this.controller});
required this.bottomSheetController,
required this.controller});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TappableAppbar( return AppBar(
controller: bottomSheetController, elevation: 0,
child: AppBar( backgroundColor: controller.config.appbarColor,
elevation: 0, leading: TextButton(
backgroundColor: controller.config.appbarColor, onPressed: () async {
leading: TextButton( if (isBottomSheet) {
onPressed: () async { BottomSheetPanel.close();
if (GetInstance().isRegistered<BottomSheetController>()) { } else {
bottomSheetController!.close(); Navigator.pop(context);
} else { await Future.delayed(const Duration(milliseconds: 500));
Navigator.pop(context); controller.disposeController();
await Future.delayed(const Duration(milliseconds: 500)); }
controller.disposeController(); },
} child: Icon(
}, Icons.arrow_back,
child: Icon( color: controller.config.appbarIconColor,
Icons.arrow_back, )),
color: controller.config.appbarIconColor, title: getTitle(),
)), actions: [
title: getTitle(), !controller.pickerMode && controller.isRecent
actions: [ ? TextButton(
!controller.pickerMode && controller.isRecent onPressed: () {
? TextButton( controller.switchPickerMode(true);
onPressed: () { },
controller.switchPickerMode(true); child: Icon(
}, Icons.check_box_outlined,
child: Icon( color: controller.config.appbarIconColor,
Icons.check_box_outlined, ))
color: controller.config.appbarIconColor, : const SizedBox()
)) ],
: const SizedBox()
],
),
); );
} }

View File

@ -1,30 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../controller/bottom_sheet_controller.dart';
class TappableAppbar extends StatelessWidget {
final BottomSheetController? controller;
final Widget child;
const 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

@ -0,0 +1,136 @@
import 'package:bottom_sheet_scaffold/bottom_sheet_scaffold.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:gallery_picker/controller/gallery_controller.dart';
import '/gallery_picker.dart';
import 'package:get/get.dart';
class PickerScaffold extends StatelessWidget {
PickerScaffold(
{super.key,
required this.onSelect,
this.body,
this.appBar,
this.floatingActionButton,
this.floatingActionButtonLocation,
this.floatingActionButtonAnimator,
this.persistentFooterButtons,
this.persistentFooterAlignment = AlignmentDirectional.centerEnd,
this.drawer,
this.onDrawerChanged,
this.endDrawer,
this.onEndDrawerChanged,
this.bottomNavigationBar,
this.backgroundColor,
this.resizeToAvoidBottomInset,
this.primary = true,
this.drawerDragStartBehavior = DragStartBehavior.start,
this.extendBody = false,
this.extendBodyBehindAppBar = false,
this.drawerScrimColor,
this.drawerEdgeDragWidth,
this.drawerEnableOpenDragGesture = true,
this.endDrawerEnableOpenDragGesture = true,
this.restorationId,
this.config,
this.heroBuilder,
this.initSelectedMedia,
this.extraRecentMedia,
this.singleMedia = false,
this.multipleMediaBuilder,}) {
if (GetInstance().isRegistered<PhoneGalleryController>()) {
if (initSelectedMedia != null) {
Get.find<PhoneGalleryController>()
.updateSelectedFiles(initSelectedMedia!);
}
if (extraRecentMedia != null) {
Get.find<PhoneGalleryController>()
.updateExtraRecentMedia(extraRecentMedia!);
}
}
}
final Widget? body;
final bool extendBody;
final bool extendBodyBehindAppBar;
final PreferredSizeWidget? appBar;
final Widget? floatingActionButton;
final FloatingActionButtonLocation? floatingActionButtonLocation;
final FloatingActionButtonAnimator? floatingActionButtonAnimator;
final List<Widget>? persistentFooterButtons;
final AlignmentDirectional persistentFooterAlignment;
final Widget? drawer;
final DrawerCallback? onDrawerChanged;
final Widget? endDrawer;
final DrawerCallback? onEndDrawerChanged;
final Color? drawerScrimColor;
final Color? backgroundColor;
final Widget? bottomNavigationBar;
final bool? resizeToAvoidBottomInset;
final bool primary;
final DragStartBehavior drawerDragStartBehavior;
final double? drawerEdgeDragWidth;
final bool drawerEnableOpenDragGesture;
final bool endDrawerEnableOpenDragGesture;
final String? restorationId;
final Config? config;
final List<MediaFile>? initSelectedMedia;
final List<MediaFile>? extraRecentMedia;
final bool singleMedia;
final Function(List<MediaFile> selectedMedia) onSelect;
final Widget Function(String tag, MediaFile media, BuildContext context)?
heroBuilder;
final Widget Function(List<MediaFile> media, BuildContext context)?
multipleMediaBuilder;
@override
Widget build(BuildContext context) {
return BottomSheetScaffold(
extendBody: extendBody,
extendBodyBehindAppBar: extendBodyBehindAppBar,
appBar: appBar,
floatingActionButton: floatingActionButton,
floatingActionButtonAnimator: floatingActionButtonAnimator,
floatingActionButtonLocation: floatingActionButtonLocation,
persistentFooterAlignment: persistentFooterAlignment,
persistentFooterButtons: persistentFooterButtons,
drawer: drawer,
onDrawerChanged: onDrawerChanged,
endDrawer: endDrawer,
onEndDrawerChanged: onEndDrawerChanged,
drawerDragStartBehavior: drawerDragStartBehavior,
drawerEdgeDragWidth: drawerEdgeDragWidth,
drawerEnableOpenDragGesture: drawerEnableOpenDragGesture,
drawerScrimColor: drawerScrimColor,
endDrawerEnableOpenDragGesture: endDrawerEnableOpenDragGesture,
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
restorationId: restorationId,
primary: primary,
backgroundColor: backgroundColor,
bottomNavigationBar: bottomNavigationBar,
body: body,
bottomSheet: DraggableBottomSheet(
draggableBody: true,
maxHeight: MediaQuery.of(context).size.height,
onHide: () {
if (GetInstance().isRegistered<PhoneGalleryController>()) {
Get.find<PhoneGalleryController>().resetBottomSheetView();
}
},
body: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: GalleryPickerView(
onSelect: onSelect,
config: config,
heroBuilder: heroBuilder,
multipleMediaBuilder: multipleMediaBuilder,
singleMedia: singleMedia,
isBottomSheet: true,
initSelectedMedia: initSelectedMedia,
extraRecentMedia: extraRecentMedia,
startWithRecent: true,
)),
),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../controller/gallery_controller.dart'; import '../controller/gallery_controller.dart';
import '../functions/color.dart';
import '../models/mode.dart'; import '../models/mode.dart';
import '/models/media_file.dart'; import '/models/media_file.dart';
import 'package:transparent_image/transparent_image.dart'; import 'package:transparent_image/transparent_image.dart';
@ -8,12 +9,29 @@ class ThumbnailMediaFile extends StatelessWidget {
final MediaFile file; final MediaFile file;
final Color failIconColor; final Color failIconColor;
final PhoneGalleryController controller; final PhoneGalleryController controller;
final bool isCollapsedSheet; final BoxFit fit;
final double? width, height;
final double radius, borderWidth;
final Color borderColor;
final bool noIcon, noSelectedIcon;
final bool highQuality;
final Function()? onTap;
final Function()? onLongPress;
const ThumbnailMediaFile( const ThumbnailMediaFile(
{super.key, {super.key,
this.fit = BoxFit.cover,
required this.file, required this.file,
this.width,
this.onLongPress,
this.onTap,
this.height,
this.radius = 0,
this.noIcon = false,
this.noSelectedIcon = false,
this.highQuality = true,
this.borderColor = Colors.transparent,
this.borderWidth = 0,
required this.failIconColor, required this.failIconColor,
required this.isCollapsedSheet,
required this.controller}); required this.controller});
Color adjustFailedBgColor() { Color adjustFailedBgColor() {
@ -26,70 +44,99 @@ class ThumbnailMediaFile extends StatelessWidget {
} }
} }
Color darken(Color color, [double amount = .03]) {
assert(amount >= 0 && amount <= 1);
final hsl = HSLColor.fromColor(color);
final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0));
return hslDark.toColor();
}
Color lighten(Color color, [double amount = .05]) {
assert(amount >= 0 && amount <= 1);
final hsl = HSLColor.fromColor(color);
final hslLight =
hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0));
return hslLight.toColor();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder( return FutureBuilder(
future: file.thumbnail == null ? file.getThumbnail() : null, future: file.thumbnail == null
? file.getThumbnail(highQuality: highQuality)
: null,
builder: (context, snapshot) { builder: (context, snapshot) {
return Stack( return GestureDetector(
fit: StackFit.passthrough, onTap: onTap != null
children: [ ? () {
if (file.thumbnailFailed) onTap!();
Container( }
color: adjustFailedBgColor(), : null,
child: Icon( onLongPress: onLongPress != null
file.isImage ? () {
? Icons.image_not_supported onLongPress!();
: Icons.videocam_off_rounded, }
size: 50, : null,
color: failIconColor, child: Container(
)) width: width,
else if (file.thumbnail != null && height: height,
!isCollapsedSheet && decoration: BoxDecoration(
controller.heroBuilder != null) color: adjustFailedBgColor(),
Hero( borderRadius: BorderRadius.circular(radius),
tag: file.medium.id, border: Border.all(color: borderColor, width: borderWidth)),
child: FadeInImage( child: ClipRRect(
fadeInDuration: const Duration(milliseconds: 200), borderRadius: BorderRadius.circular(radius),
fit: BoxFit.cover, child: Stack(
placeholder: MemoryImage(kTransparentImage), fit: StackFit.passthrough,
image: MemoryImage(file.thumbnail!), children: [
), if (snapshot.hasError)
) Center(
else if (file.thumbnail != null && controller.heroBuilder == null) child: Icon(
FadeInImage( file.isImage
fadeInDuration: const Duration(milliseconds: 200), ? Icons.image_not_supported
fit: BoxFit.cover, : Icons.videocam_off_rounded,
placeholder: MemoryImage(kTransparentImage), size: 50,
image: MemoryImage(file.thumbnail!), color: failIconColor,
) ),
else )
const SizedBox(), else if (file.thumbnail != null &&
if (file.thumbnail != null && !file.thumbnailFailed) controller.heroBuilder != null)
Positioned( Hero(
bottom: 10, tag: file.id,
left: 10, child: FadeInImage(
child: Icon( width: width,
file.isVideo ? Icons.video_camera_back : null, height: height,
color: Colors.white, fadeInDuration: const Duration(milliseconds: 200),
size: 20, fit: fit,
)), placeholder: MemoryImage(kTransparentImage),
], image: MemoryImage(file.thumbnail!),
),
)
else if (file.thumbnail != null &&
controller.heroBuilder == null)
FadeInImage(
width: width,
height: height,
fadeInDuration: const Duration(milliseconds: 200),
fit: fit,
placeholder: MemoryImage(kTransparentImage),
image: MemoryImage(file.thumbnail!),
)
else
SizedBox(
width: width,
height: height,
),
if (!noIcon && file.thumbnail != null)
Positioned(
bottom: 10,
left: 10,
child: Icon(
file.isVideo ? Icons.video_camera_back : null,
color: Colors.white,
size: 20,
)),
if (!noSelectedIcon && controller.isSelectedMedia(file))
Opacity(
opacity: 0.5,
child: Container(
color: Colors.black,
child: const Icon(
Icons.check,
color: Colors.white,
size: 45,
),
),
),
],
),
),
),
); );
}); });
} }

View File

@ -1,6 +1,6 @@
name: gallery_picker name: gallery_picker
description: 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. description: 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.
version: 0.2.3 version: 0.3.0
homepage: https://github.com/FlutterWay/gallery_picker homepage: https://github.com/FlutterWay/gallery_picker
environment: environment:
@ -18,7 +18,8 @@ dependencies:
get: ^4.6.5 get: ^4.6.5
video_thumbnail: ^0.5.3 video_thumbnail: ^0.5.3
intl: ^0.18.0 intl: ^0.18.0
bottom_sheet_bar: ^2.3.8 bottom_sheet_scaffold: ^0.1.1
page_transition: ^2.0.9
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter