From 73e37c104952a9eebc39867e91279c2448760078 Mon Sep 17 00:00:00 2001 From: Mati Date: Wed, 25 Oct 2023 14:06:23 -0300 Subject: [PATCH 1/2] feat(image preview): implements the preview widget PE-4709 --- .../fs_entry_preview_cubit.dart | 33 ++++- .../fs_entry_preview_state.dart | 4 + .../components/fs_entry_preview_widget.dart | 131 +++++++++++++++++- 3 files changed, 156 insertions(+), 12 deletions(-) diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart index 4e01584c74..695d197a84 100644 --- a/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_cubit.dart @@ -7,8 +7,8 @@ import 'package:ardrive/models/models.dart'; import 'package:ardrive/pages/pages.dart'; import 'package:ardrive/services/services.dart'; import 'package:ardrive/utils/constants.dart'; -import 'package:ardrive/utils/mime_lookup.dart'; import 'package:ardrive_http/ardrive_http.dart'; +import 'package:ardrive_io/ardrive_io.dart'; import 'package:cryptography/cryptography.dart'; import 'package:drift/drift.dart'; import 'package:equatable/equatable.dart'; @@ -79,7 +79,12 @@ class FsEntryPreviewCubit extends Cubit { final data = await _getPreviewData(file, previewUrl); if (data != null) { - emit(FsEntryPreviewImage(imageBytes: data, previewUrl: previewUrl)); + emit(FsEntryPreviewImage( + imageBytes: data, + previewUrl: previewUrl, + filename: file.name, + contentType: file.contentType, + )); } else { emit(FsEntryPreviewUnavailable()); } @@ -278,7 +283,13 @@ class FsEntryPreviewCubit extends Cubit { switch (drive.privacy) { case DrivePrivacy.public: emit( - FsEntryPreviewImage(imageBytes: dataBytes, previewUrl: dataUrl), + FsEntryPreviewImage( + imageBytes: dataBytes, + previewUrl: dataUrl, + filename: file.name, + contentType: file.dataContentType ?? + lookupMimeTypeWithDefaultType(file.name), + ), ); break; case DrivePrivacy.private: @@ -289,7 +300,13 @@ class FsEntryPreviewCubit extends Cubit { if (isPinFile) { emit( - FsEntryPreviewImage(imageBytes: dataBytes, previewUrl: dataUrl), + FsEntryPreviewImage( + imageBytes: dataBytes, + previewUrl: dataUrl, + filename: file.name, + contentType: file.dataContentType ?? + lookupMimeTypeWithDefaultType(file.name), + ), ); break; } @@ -314,7 +331,13 @@ class FsEntryPreviewCubit extends Cubit { fileKey, ); emit( - FsEntryPreviewImage(imageBytes: decodedBytes, previewUrl: dataUrl), + FsEntryPreviewImage( + imageBytes: decodedBytes, + previewUrl: dataUrl, + filename: file.name, + contentType: file.dataContentType ?? + lookupMimeTypeWithDefaultType(file.name), + ), ); break; diff --git a/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart b/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart index f7ed5d9a4f..c2ac51a34f 100644 --- a/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart +++ b/lib/blocs/fs_entry_preview/fs_entry_preview_state.dart @@ -26,9 +26,13 @@ class FsEntryPreviewLoading extends FsEntryPreviewSuccess { class FsEntryPreviewImage extends FsEntryPreviewSuccess { final Uint8List imageBytes; + final String filename; + final String contentType; const FsEntryPreviewImage({ required this.imageBytes, + required this.filename, + required this.contentType, required String previewUrl, }) : super(previewUrl: previewUrl); diff --git a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart index 18018e536c..e872d1eaca 100644 --- a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart +++ b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart @@ -32,13 +32,11 @@ class _FsEntryPreviewWidgetState extends State { ); case FsEntryPreviewImage: - return ArDriveImage( - fit: BoxFit.contain, - height: double.maxFinite, - width: double.maxFinite, - image: MemoryImage( - (widget.state as FsEntryPreviewImage).imageBytes, - ), + return ImagePreviewWidget( + filename: (widget.state as FsEntryPreviewImage).filename, + contentType: (widget.state as FsEntryPreviewImage).contentType, + imageBytes: (widget.state as FsEntryPreviewImage).imageBytes, + isSharePage: widget.isSharePage, ); case FsEntryPreviewAudio: @@ -1028,6 +1026,125 @@ class _FullScreenVideoPlayerWidgetState } } +class ImagePreviewWidget extends StatefulWidget { + final Uint8List imageBytes; + final String filename; + final String contentType; + final bool isSharePage; + + const ImagePreviewWidget({ + super.key, + required this.filename, + required this.contentType, + required this.imageBytes, + required this.isSharePage, + }); + + @override + State createState() { + return _ImagePreviewWidgetState(); + } +} + +class _ImagePreviewWidgetState extends State { + @override + Widget build(BuildContext context) { + if (!widget.isSharePage) { + return _buildImage(); + } else { + final theme = ArDriveTheme.of(context); + + return Column( + children: [ + Flexible(child: _buildImage()), + Container( + color: theme.themeData.colors.themeBgCanvas, + child: _buildActionBar(), + ), + ], + ); + } + } + + Widget _buildImage() { + return ArDriveImage( + fit: BoxFit.contain, + height: double.maxFinite, + width: double.maxFinite, + image: MemoryImage( + widget.imageBytes, + ), + ); + } + + Widget _buildActionBar() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + height: 96, + child: Padding( + padding: const EdgeInsets.only( + left: 24, + top: 24, + bottom: 24, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _getFileNameWithNoExtension(), + style: ArDriveTypography.body.smallBold700( + color: ArDriveTheme.of(context) + .themeData + .colors + .themeFgDefault, + ), + ), + Text( + _getFileExtension(), + style: ArDriveTypography.body.smallRegular( + color: ArDriveTheme.of(context) + .themeData + .colors + .themeFgDisabled, + ), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + right: 24, + top: 24, + bottom: 24, + ), + child: IconButton( + onPressed: goFullScreen, + icon: const Icon(Icons.fullscreen_outlined, size: 24), + ), + ), + ], + ); + } + + String _getFileNameWithNoExtension() { + return widget.filename.substring(0, widget.filename.lastIndexOf('.')); + } + + String _getFileExtension() { + return widget.contentType + .substring( + widget.contentType.lastIndexOf('/') + 1, + ) + .toUpperCase(); + } + + void goFullScreen() {} +} + class AudioPlayerWidget extends StatefulWidget { final String audioUrl; final String filename; From 8102148a4211481cc8d17efe410744b6e361e998 Mon Sep 17 00:00:00 2001 From: Mati Date: Wed, 25 Oct 2023 15:46:08 -0300 Subject: [PATCH 2/2] feat(image preview): implements full screen mode PE-4709 --- .../components/fs_entry_preview_widget.dart | 83 ++++++++++++++++++- 1 file changed, 79 insertions(+), 4 deletions(-) diff --git a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart index e872d1eaca..053de0464e 100644 --- a/lib/pages/drive_detail/components/fs_entry_preview_widget.dart +++ b/lib/pages/drive_detail/components/fs_entry_preview_widget.dart @@ -37,6 +37,7 @@ class _FsEntryPreviewWidgetState extends State { contentType: (widget.state as FsEntryPreviewImage).contentType, imageBytes: (widget.state as FsEntryPreviewImage).imageBytes, isSharePage: widget.isSharePage, + isFullScreen: false, ); case FsEntryPreviewAudio: @@ -1026,18 +1027,74 @@ class _FullScreenVideoPlayerWidgetState } } +class ImagePreviewFullScreenWidget extends StatefulWidget { + final Uint8List imageBytes; + final String filename; + final String contentType; + + const ImagePreviewFullScreenWidget({ + super.key, + required this.filename, + required this.contentType, + required this.imageBytes, + }); + + @override + State createState() { + return _ImagePreviewFullScreenWidgetState(); + } +} + +class _ImagePreviewFullScreenWidgetState + extends State { + bool fullScreenMode = false; + + @override + void initState() { + SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeRight, + DeviceOrientation.landscapeLeft, + ]); + + super.initState(); + } + + @override + void dispose() { + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + ]); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ImagePreviewWidget( + filename: widget.filename, + contentType: widget.contentType, + imageBytes: widget.imageBytes, + isFullScreen: true, + ), + ); + } +} + class ImagePreviewWidget extends StatefulWidget { final Uint8List imageBytes; final String filename; final String contentType; final bool isSharePage; + final bool isFullScreen; const ImagePreviewWidget({ super.key, required this.filename, required this.contentType, required this.imageBytes, - required this.isSharePage, + this.isSharePage = false, + this.isFullScreen = false, }); @override @@ -1049,7 +1106,7 @@ class ImagePreviewWidget extends StatefulWidget { class _ImagePreviewWidgetState extends State { @override Widget build(BuildContext context) { - if (!widget.isSharePage) { + if (!widget.isSharePage && !widget.isFullScreen) { return _buildImage(); } else { final theme = ArDriveTheme.of(context); @@ -1123,7 +1180,9 @@ class _ImagePreviewWidgetState extends State { ), child: IconButton( onPressed: goFullScreen, - icon: const Icon(Icons.fullscreen_outlined, size: 24), + icon: widget.isFullScreen + ? const Icon(Icons.fullscreen_exit_outlined) + : const Icon(Icons.fullscreen_outlined, size: 24), ), ), ], @@ -1142,7 +1201,23 @@ class _ImagePreviewWidgetState extends State { .toUpperCase(); } - void goFullScreen() {} + void goFullScreen() { + if (widget.isFullScreen) { + Navigator.of(context).pop(); + } else { + Navigator.of(context).push( + PageRouteBuilder( + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, + pageBuilder: (context, _, __) => ImagePreviewFullScreenWidget( + filename: widget.filename, + contentType: widget.contentType, + imageBytes: widget.imageBytes, + ), + ), + ); + } + } } class AudioPlayerWidget extends StatefulWidget {