From b8e3d88575343f6e8b9945fc3b6e25c8db6d2807 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 10 Mar 2025 11:22:50 +0530 Subject: [PATCH 1/8] [mob][photos] Show caption/description in file viewer screen --- .../ui/viewer/file/video_widget_native.dart | 133 ++++++++++++------ mobile/lib/ui/viewer/file/zoomable_image.dart | 36 ++++- 2 files changed, 122 insertions(+), 47 deletions(-) diff --git a/mobile/lib/ui/viewer/file/video_widget_native.dart b/mobile/lib/ui/viewer/file/video_widget_native.dart index 65fb1ecb0f..3c0a2f4a27 100644 --- a/mobile/lib/ui/viewer/file/video_widget_native.dart +++ b/mobile/lib/ui/viewer/file/video_widget_native.dart @@ -357,6 +357,7 @@ class _VideoWidgetNativeState extends State showControls: _showControls, isSeeking: _isSeeking, position: position, + caption: widget.file.caption, ) : const SizedBox(); }, @@ -644,6 +645,7 @@ class _SeekBarAndDuration extends StatelessWidget { final ValueNotifier showControls; final ValueNotifier isSeeking; final int position; + final String? caption; const _SeekBarAndDuration({ required this.controller, @@ -651,6 +653,7 @@ class _SeekBarAndDuration extends StatelessWidget { required this.showControls, required this.isSeeking, required this.position, + required this.caption, }); @override @@ -691,34 +694,54 @@ class _SeekBarAndDuration extends StatelessWidget { width: 1, ), ), - child: Row( + child: Column( children: [ - AnimatedSize( - duration: const Duration( - seconds: 5, - ), - curve: Curves.easeInOut, - child: Text( - secondsToDuration(position ~/ 1000), - style: getEnteTextTheme( - context, - ).mini.copyWith( - color: textBaseDark, + caption != null && caption!.isNotEmpty + ? Padding( + padding: const EdgeInsets.fromLTRB( + 0, + 8, + 0, + 12, ), - ), - ), - Expanded( - child: SeekBar( - controller!, - durationToSeconds(duration), - isSeeking, - ), - ), - Text( - duration ?? "0:00", - style: getEnteTextTheme(context).mini.copyWith( - color: textBaseDark, + child: Text( + caption!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: getEnteTextTheme(context).mini, + ), + ) + : const SizedBox.shrink(), + Row( + children: [ + AnimatedSize( + duration: const Duration( + seconds: 5, ), + curve: Curves.easeInOut, + child: Text( + secondsToDuration(position ~/ 1000), + style: getEnteTextTheme( + context, + ).mini.copyWith( + color: textBaseDark, + ), + ), + ), + Expanded( + child: SeekBar( + controller!, + durationToSeconds(duration), + isSeeking, + ), + ), + Text( + duration ?? "0:00", + style: getEnteTextTheme(context).mini.copyWith( + color: textBaseDark, + ), + ), + ], ), ], ), @@ -753,29 +776,47 @@ class _VideoDescriptionAndSwitchToMediaKitButton extends StatelessWidget { ? MainAxisAlignment.spaceBetween : MainAxisAlignment.center, children: [ - file.caption?.isNotEmpty ?? false + // file.caption?.isNotEmpty ?? false + false ? Expanded( - child: ValueListenableBuilder( - valueListenable: showControls, - builder: (context, value, _) { - return AnimatedOpacity( - duration: const Duration(milliseconds: 200), - curve: Curves.easeInQuad, - opacity: value ? 1 : 0, - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), - child: Text( - file.caption!, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: getEnteTextTheme(context) - .mini - .copyWith(color: textBaseDark), - textAlign: TextAlign.center, + child: Padding( + padding: const EdgeInsets.only(bottom: 8, left: 8, right: 8), + child: ValueListenableBuilder( + valueListenable: showControls, + builder: (context, value, _) { + return AnimatedOpacity( + duration: const Duration(milliseconds: 200), + curve: Curves.easeInQuad, + opacity: value ? 1 : 0, + child: Container( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.1), + borderRadius: const BorderRadius.all( + Radius.circular(4), + ), + ), + width: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 8.0, + ), + child: Text( + file.caption!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: getEnteTextTheme(context).mini, + ), + ), + ], + ), ), - ), - ); - }, + ); + }, + ), ), ) : const SizedBox.shrink(), diff --git a/mobile/lib/ui/viewer/file/zoomable_image.dart b/mobile/lib/ui/viewer/file/zoomable_image.dart index 0743cba757..041732e8b6 100644 --- a/mobile/lib/ui/viewer/file/zoomable_image.dart +++ b/mobile/lib/ui/viewer/file/zoomable_image.dart @@ -13,6 +13,7 @@ import "package:photos/events/files_updated_event.dart"; import 'package:photos/events/local_photos_updated_event.dart'; import "package:photos/models/file/extensions/file_props.dart"; import 'package:photos/models/file/file.dart'; +import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/actions/file/file_actions.dart"; import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/utils/file_util.dart'; @@ -167,7 +168,40 @@ class _ZoomableImageState extends State { }; return GestureDetector( onVerticalDragUpdate: verticalDragCallback, - child: content, + child: widget.photo.caption?.isNotEmpty ?? false + ? Stack( + clipBehavior: Clip.none, + children: [ + content, + Positioned( + bottom: 72 + MediaQuery.of(context).padding.bottom, + left: 0, + right: 0, + child: Container( + color: Colors.black.withOpacity(0.1), + width: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 8.0, + ), + child: Text( + widget.photo.caption!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: getEnteTextTheme(context).mini, + ), + ), + ], + ), + ), + ), + ], + ) + : content, ); } From 13c36d9c402844d8f4de3d7069e9e898e442f00e Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 10 Mar 2025 13:13:52 +0530 Subject: [PATCH 2/8] [mob][photos] Hide/show caption with enabling/disabling full screen --- mobile/lib/states/detail_page_state.dart | 37 ++++ mobile/lib/ui/viewer/file/detail_page.dart | 206 +++++++++--------- mobile/lib/ui/viewer/file/zoomable_image.dart | 47 ++-- 3 files changed, 163 insertions(+), 127 deletions(-) create mode 100644 mobile/lib/states/detail_page_state.dart diff --git a/mobile/lib/states/detail_page_state.dart b/mobile/lib/states/detail_page_state.dart new file mode 100644 index 0000000000..83df0558cb --- /dev/null +++ b/mobile/lib/states/detail_page_state.dart @@ -0,0 +1,37 @@ +import "package:flutter/material.dart"; +import "package:flutter/services.dart"; + +class InheritedDetailPageState extends InheritedWidget { + final enableFullScreenNotifier = ValueNotifier(false); + InheritedDetailPageState({ + super.key, + required super.child, + }); + + static InheritedDetailPageState of(BuildContext context) => + context.dependOnInheritedWidgetOfExactType()!; + + void toggleFullScreen({bool? shouldEnable}) { + if (shouldEnable != null) { + if (enableFullScreenNotifier.value == shouldEnable) return; + } + enableFullScreenNotifier.value = !enableFullScreenNotifier.value; + if (enableFullScreenNotifier.value) { + Future.delayed(const Duration(milliseconds: 200), () { + SystemChrome.setEnabledSystemUIMode( + SystemUiMode.manual, + overlays: [], + ); + }); + } else { + SystemChrome.setEnabledSystemUIMode( + SystemUiMode.edgeToEdge, + overlays: SystemUiOverlay.values, + ); + } + } + + @override + bool updateShouldNotify(InheritedDetailPageState oldWidget) => + oldWidget.enableFullScreenNotifier != enableFullScreenNotifier; +} diff --git a/mobile/lib/ui/viewer/file/detail_page.dart b/mobile/lib/ui/viewer/file/detail_page.dart index 0dab65b96d..82c9eb14a0 100644 --- a/mobile/lib/ui/viewer/file/detail_page.dart +++ b/mobile/lib/ui/viewer/file/detail_page.dart @@ -16,6 +16,7 @@ import 'package:photos/models/file/file.dart'; import "package:photos/models/file/file_type.dart"; import "package:photos/service_locator.dart"; import "package:photos/services/local_authentication_service.dart"; +import "package:photos/states/detail_page_state.dart"; import "package:photos/ui/common/fast_scroll_physics.dart"; import 'package:photos/ui/notification/toast.dart'; import 'package:photos/ui/tools/editor/image_editor_page.dart'; @@ -77,7 +78,6 @@ class _DetailPageState extends State { List? _files; late PageController _pageController; final _selectedIndexNotifier = ValueNotifier(0); - final _enableFullScreenNotifier = ValueNotifier(false); bool _isFirstOpened = true; bool isGuestView = false; bool swipeLocked = false; @@ -103,7 +103,6 @@ class _DetailPageState extends State { void dispose() { _guestViewEventSubscription.cancel(); _pageController.dispose(); - _enableFullScreenNotifier.dispose(); _selectedIndexNotifier.dispose(); super.dispose(); @@ -137,96 +136,102 @@ class _DetailPageState extends State { _files!.length.toString() + " files .", ); - return PopScope( - canPop: !isGuestView, - onPopInvoked: (didPop) async { - if (isGuestView) { - final authenticated = await _requestAuthentication(); - if (authenticated) { - Bus.instance.fire(GuestViewEvent(false, false)); - await localSettings.setOnGuestView(false); + return InheritedDetailPageState( + child: PopScope( + canPop: !isGuestView, + onPopInvoked: (didPop) async { + if (isGuestView) { + final authenticated = await _requestAuthentication(); + if (authenticated) { + Bus.instance.fire(GuestViewEvent(false, false)); + await localSettings.setOnGuestView(false); + } } - } - }, - child: Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(80), - child: ValueListenableBuilder( - builder: (BuildContext context, int selectedIndex, _) { - return FileAppBar( - _files![selectedIndex], - _onFileRemoved, - widget.config.mode == DetailPageMode.full, - enableFullScreenNotifier: _enableFullScreenNotifier, - ); - }, - valueListenable: _selectedIndexNotifier, + }, + child: Scaffold( + appBar: PreferredSize( + preferredSize: const Size.fromHeight(80), + child: ValueListenableBuilder( + builder: (BuildContext context, int selectedIndex, _) { + return FileAppBar( + _files![selectedIndex], + _onFileRemoved, + widget.config.mode == DetailPageMode.full, + enableFullScreenNotifier: InheritedDetailPageState.of(context) + .enableFullScreenNotifier, + ); + }, + valueListenable: _selectedIndexNotifier, + ), ), - ), - extendBodyBehindAppBar: true, - resizeToAvoidBottomInset: false, - backgroundColor: Colors.black, - body: Center( - child: Stack( - children: [ - _buildPageView(context), - ValueListenableBuilder( - builder: (BuildContext context, int selectedIndex, _) { - return FileBottomBar( - _files![selectedIndex], - _onEditFileRequested, - widget.config.mode == DetailPageMode.minimalistic && - !isGuestView, - onFileRemoved: _onFileRemoved, - userID: Configuration.instance.getUserID(), - enableFullScreenNotifier: _enableFullScreenNotifier, - ); - }, - valueListenable: _selectedIndexNotifier, - ), - ValueListenableBuilder( - valueListenable: _selectedIndexNotifier, - builder: (BuildContext context, int selectedIndex, _) { - if (_files![selectedIndex].isPanorama() == true) { - return ValueListenableBuilder( - valueListenable: _enableFullScreenNotifier, - builder: (context, value, child) { - return IgnorePointer( - ignoring: value, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 200), - opacity: !value ? 1.0 : 0.0, - child: Align( - alignment: Alignment.center, - child: Tooltip( - message: S.of(context).panorama, - child: IconButton( - style: IconButton.styleFrom( - backgroundColor: const Color(0xAA252525), - fixedSize: const Size(44, 44), + extendBodyBehindAppBar: true, + resizeToAvoidBottomInset: false, + backgroundColor: Colors.black, + body: Center( + child: Stack( + children: [ + _buildPageView(), + ValueListenableBuilder( + builder: (BuildContext context, int selectedIndex, _) { + return FileBottomBar( + _files![selectedIndex], + _onEditFileRequested, + widget.config.mode == DetailPageMode.minimalistic && + !isGuestView, + onFileRemoved: _onFileRemoved, + userID: Configuration.instance.getUserID(), + enableFullScreenNotifier: + InheritedDetailPageState.of(context) + .enableFullScreenNotifier, + ); + }, + valueListenable: _selectedIndexNotifier, + ), + ValueListenableBuilder( + valueListenable: _selectedIndexNotifier, + builder: (BuildContext context, int selectedIndex, _) { + if (_files![selectedIndex].isPanorama() == true) { + return ValueListenableBuilder( + valueListenable: InheritedDetailPageState.of(context) + .enableFullScreenNotifier, + builder: (context, value, child) { + return IgnorePointer( + ignoring: value, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: !value ? 1.0 : 0.0, + child: Align( + alignment: Alignment.center, + child: Tooltip( + message: S.of(context).panorama, + child: IconButton( + style: IconButton.styleFrom( + backgroundColor: const Color(0xAA252525), + fixedSize: const Size(44, 44), + ), + icon: const Icon( + Icons.threesixty, + color: Colors.white, + size: 26, + ), + onPressed: () async { + await openPanoramaViewerPage( + _files![selectedIndex], + ); + }, ), - icon: const Icon( - Icons.threesixty, - color: Colors.white, - size: 26, - ), - onPressed: () async { - await openPanoramaViewerPage( - _files![selectedIndex], - ); - }, ), ), ), - ), - ); - }, - ); - } - return const SizedBox(); - }, - ), - ], + ); + }, + ); + } + return const SizedBox(); + }, + ), + ], + ), ), ), ), @@ -251,7 +256,7 @@ class _DetailPageState extends State { ).ignore(); } - Widget _buildPageView(BuildContext context) { + Widget _buildPageView() { return PageView.builder( clipBehavior: Clip.none, itemBuilder: (context, index) { @@ -271,14 +276,17 @@ class _DetailPageState extends State { }, playbackCallback: (isPlaying) { Future.delayed(Duration.zero, () { - _toggleFullScreen(shouldEnable: isPlaying); + InheritedDetailPageState.of(context) + .toggleFullScreen(shouldEnable: isPlaying); }); }, backgroundDecoration: const BoxDecoration(color: Colors.black), ); return GestureDetector( onTap: () { - file.fileType != FileType.video ? _toggleFullScreen() : null; + file.fileType != FileType.video + ? InheritedDetailPageState.of(context).toggleFullScreen() + : null; }, child: fileContent, ); @@ -313,26 +321,6 @@ class _DetailPageState extends State { return false; } - void _toggleFullScreen({bool? shouldEnable}) { - if (shouldEnable != null) { - if (_enableFullScreenNotifier.value == shouldEnable) return; - } - _enableFullScreenNotifier.value = !_enableFullScreenNotifier.value; - if (_enableFullScreenNotifier.value) { - Future.delayed(const Duration(milliseconds: 200), () { - SystemChrome.setEnabledSystemUIMode( - SystemUiMode.manual, - overlays: [], - ); - }); - } else { - SystemChrome.setEnabledSystemUIMode( - SystemUiMode.edgeToEdge, - overlays: SystemUiOverlay.values, - ); - } - } - void _preloadFiles(int index) { if (index > 0) { preloadFile(_files![index - 1]); diff --git a/mobile/lib/ui/viewer/file/zoomable_image.dart b/mobile/lib/ui/viewer/file/zoomable_image.dart index 041732e8b6..399fdcf33c 100644 --- a/mobile/lib/ui/viewer/file/zoomable_image.dart +++ b/mobile/lib/ui/viewer/file/zoomable_image.dart @@ -13,6 +13,7 @@ import "package:photos/events/files_updated_event.dart"; import 'package:photos/events/local_photos_updated_event.dart'; import "package:photos/models/file/extensions/file_props.dart"; import 'package:photos/models/file/file.dart'; +import "package:photos/states/detail_page_state.dart"; import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/actions/file/file_actions.dart"; import 'package:photos/ui/common/loading_widget.dart'; @@ -177,26 +178,36 @@ class _ZoomableImageState extends State { bottom: 72 + MediaQuery.of(context).padding.bottom, left: 0, right: 0, - child: Container( - color: Colors.black.withOpacity(0.1), - width: double.infinity, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 4.0, - horizontal: 8.0, - ), - child: Text( - widget.photo.caption!, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: getEnteTextTheme(context).mini, + child: ValueListenableBuilder( + valueListenable: InheritedDetailPageState.of(context) + .enableFullScreenNotifier, + builder: (context, doNotShowCaption, _) { + return AnimatedOpacity( + opacity: doNotShowCaption ? 0.0 : 1.0, + duration: const Duration(milliseconds: 200), + child: Container( + color: Colors.black.withOpacity(0.1), + width: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 8.0, + ), + child: Text( + widget.photo.caption!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: getEnteTextTheme(context).mini, + ), + ), + ], ), ), - ], - ), + ); + }, ), ), ], From 145e025eea0a881c595e9084c5bb1c6690c2799e Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 10 Mar 2025 13:22:56 +0530 Subject: [PATCH 3/8] [mob][photos] Move caption/description inside seek bar's container in media kit player for consistancy of UI across players --- .../file/video_widget_media_kit_common.dart | 114 +++++++++--------- 1 file changed, 58 insertions(+), 56 deletions(-) diff --git a/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart b/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart index ba4b730206..927485e0ea 100644 --- a/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart +++ b/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart @@ -44,6 +44,7 @@ class _VideoWidgetState extends State { @override void initState() { + super.initState(); _isPlayingStreamSubscription = widget.controller.player.stream.playing.listen((isPlaying) { if (isPlaying && !_isSeekingNotifier.value) { @@ -55,7 +56,6 @@ class _VideoWidgetState extends State { }); _isSeekingNotifier.addListener(isSeekingListener); - super.initState(); } @override @@ -131,27 +131,6 @@ class _VideoWidgetState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - widget.file.caption != null - ? Padding( - padding: const EdgeInsets.fromLTRB( - 16, - 12, - 16, - 8, - ), - child: Text( - widget.file.caption!, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: getEnteTextTheme(context) - .mini - .copyWith( - color: textBaseDark, - ), - textAlign: TextAlign.center, - ), - ) - : const SizedBox.shrink(), PreviewStatusWidget( showControls: value, file: widget.file, @@ -161,6 +140,7 @@ class _VideoWidgetState extends State { SeekBarAndDuration( controller: widget.controller, isSeekingNotifier: _isSeekingNotifier, + caption: widget.file.caption, ), ], ), @@ -272,11 +252,13 @@ class _PlayPauseButtonState extends State { class SeekBarAndDuration extends StatelessWidget { final VideoController? controller; final ValueNotifier isSeekingNotifier; + final String? caption; const SeekBarAndDuration({ super.key, required this.controller, required this.isSeekingNotifier, + required this.caption, }); @override @@ -302,46 +284,66 @@ class SeekBarAndDuration extends StatelessWidget { width: 1, ), ), - child: Row( + child: Column( children: [ - StreamBuilder( - stream: controller?.player.stream.position, - builder: (context, snapshot) { - if (snapshot.data == null) { - return Text( - "0:00", - style: getEnteTextTheme( - context, - ).mini.copyWith( - color: textBaseDark, - ), - ); - } - return Text( - secondsToDuration(snapshot.data!.inSeconds), + caption != null && caption!.isNotEmpty + ? Padding( + padding: const EdgeInsets.fromLTRB( + 0, + 8, + 0, + 12, + ), + child: Text( + caption!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: getEnteTextTheme(context).mini, + ), + ) + : const SizedBox.shrink(), + Row( + children: [ + StreamBuilder( + stream: controller?.player.stream.position, + builder: (context, snapshot) { + if (snapshot.data == null) { + return Text( + "0:00", + style: getEnteTextTheme( + context, + ).mini.copyWith( + color: textBaseDark, + ), + ); + } + return Text( + secondsToDuration(snapshot.data!.inSeconds), + style: getEnteTextTheme( + context, + ).mini.copyWith( + color: textBaseDark, + ), + ); + }, + ), + Expanded( + child: SeekBar( + controller!, + isSeekingNotifier, + ), + ), + Text( + _secondsToDuration( + controller!.player.state.duration.inSeconds, + ), style: getEnteTextTheme( context, ).mini.copyWith( color: textBaseDark, ), - ); - }, - ), - Expanded( - child: SeekBar( - controller!, - isSeekingNotifier, - ), - ), - Text( - _secondsToDuration( - controller!.player.state.duration.inSeconds, - ), - style: getEnteTextTheme( - context, - ).mini.copyWith( - color: textBaseDark, - ), + ), + ], ), ], ), From b953d6d5137c64c58d271fdb7d6347c048bd3a57 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 10 Mar 2025 13:25:15 +0530 Subject: [PATCH 4/8] [mob][photos] Clean up --- .../ui/viewer/file/video_widget_native.dart | 196 +++++++----------- 1 file changed, 74 insertions(+), 122 deletions(-) diff --git a/mobile/lib/ui/viewer/file/video_widget_native.dart b/mobile/lib/ui/viewer/file/video_widget_native.dart index 3c0a2f4a27..5cdc9152c4 100644 --- a/mobile/lib/ui/viewer/file/video_widget_native.dart +++ b/mobile/lib/ui/viewer/file/video_widget_native.dart @@ -771,133 +771,85 @@ class _VideoDescriptionAndSwitchToMediaKitButton extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - mainAxisAlignment: Platform.isAndroid - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.center, - children: [ - // file.caption?.isNotEmpty ?? false - false - ? Expanded( - child: Padding( - padding: const EdgeInsets.only(bottom: 8, left: 8, right: 8), - child: ValueListenableBuilder( - valueListenable: showControls, - builder: (context, value, _) { - return AnimatedOpacity( - duration: const Duration(milliseconds: 200), - curve: Curves.easeInQuad, - opacity: value ? 1 : 0, - child: Container( - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.1), - borderRadius: const BorderRadius.all( - Radius.circular(4), - ), - ), - width: double.infinity, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 4.0, - horizontal: 8.0, - ), - child: Text( - file.caption!, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: getEnteTextTheme(context).mini, - ), - ), - ], - ), - ), - ); - }, - ), - ), - ) - : const SizedBox.shrink(), - Platform.isAndroid && !selectedPreview - ? ValueListenableBuilder( - valueListenable: showControls, - builder: (context, value, _) { - return IgnorePointer( - ignoring: !value, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 200), - curve: Curves.easeInQuad, - opacity: value ? 1 : 0, - child: ElTooltip( - padding: const EdgeInsets.all(12), - distance: 4, - controller: elTooltipController, - content: GestureDetector( - onLongPress: () { - Bus.instance.fire( - UseMediaKitForVideo(), - ); - HapticFeedback.vibrate(); + return Platform.isAndroid && !selectedPreview + ? Align( + alignment: Alignment.centerRight, + child: ValueListenableBuilder( + valueListenable: showControls, + builder: (context, value, _) { + return IgnorePointer( + ignoring: !value, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + curve: Curves.easeInQuad, + opacity: value ? 1 : 0, + child: ElTooltip( + padding: const EdgeInsets.all(12), + distance: 4, + controller: elTooltipController, + content: GestureDetector( + onLongPress: () { + Bus.instance.fire( + UseMediaKitForVideo(), + ); + HapticFeedback.vibrate(); + elTooltipController.hide(); + }, + child: Text(S.of(context).useDifferentPlayerInfo), + ), + position: ElTooltipPosition.topEnd, + color: backgroundElevatedDark, + appearAnimationDuration: const Duration( + milliseconds: 200, + ), + disappearAnimationDuration: const Duration( + milliseconds: 200, + ), + child: GestureDetector( + onTap: () { + if (elTooltipController.value == + ElTooltipStatus.hidden) { + elTooltipController.show(); + } else { elTooltipController.hide(); - }, - child: Text(S.of(context).useDifferentPlayerInfo), - ), - position: ElTooltipPosition.topEnd, - color: backgroundElevatedDark, - appearAnimationDuration: const Duration( - milliseconds: 200, - ), - disappearAnimationDuration: const Duration( - milliseconds: 200, - ), - child: GestureDetector( - onTap: () { - if (elTooltipController.value == - ElTooltipStatus.hidden) { - elTooltipController.show(); - } else { - elTooltipController.hide(); - } - controller?.pause(); - }, - behavior: HitTestBehavior.translucent, - onLongPress: () { - Bus.instance.fire( - UseMediaKitForVideo(), - ); - HapticFeedback.vibrate(); - }, - child: Padding( - padding: const EdgeInsets.fromLTRB(12, 0, 0, 4), - child: Container( - padding: const EdgeInsets.all(12), - child: Stack( - alignment: Alignment.bottomRight, - children: [ - Icon( - Icons.play_arrow_outlined, - size: 24, - color: Colors.white.withOpacity(0.2), - ), - Icon( - Icons.question_mark_rounded, - size: 10, - color: Colors.white.withOpacity(0.2), - ), - ], - ), + } + controller?.pause(); + }, + behavior: HitTestBehavior.translucent, + onLongPress: () { + Bus.instance.fire( + UseMediaKitForVideo(), + ); + HapticFeedback.vibrate(); + }, + child: Padding( + padding: const EdgeInsets.fromLTRB(12, 0, 0, 4), + child: Container( + padding: const EdgeInsets.all(12), + child: Stack( + alignment: Alignment.bottomRight, + children: [ + Icon( + Icons.play_arrow_outlined, + size: 24, + color: Colors.white.withOpacity(0.2), + ), + Icon( + Icons.question_mark_rounded, + size: 10, + color: Colors.white.withOpacity(0.2), + ), + ], ), ), ), ), ), - ); - }, - ) - : const SizedBox.shrink(), - ], - ); + ), + ); + }, + ), + ) + : const SizedBox.shrink(); } } From dbb14f0a24743cb5a9a9db735b4a44e6071edf48 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 10 Mar 2025 14:48:50 +0530 Subject: [PATCH 5/8] [mob][photos] Reflect edited caption/description immidiately on file viewer on changing it in file info bottom sheet --- .../events/file_caption_updated_event.dart | 7 ++++++ .../lib/ui/viewer/file/file_bottom_bar.dart | 5 ----- .../ui/viewer/file/file_caption_widget.dart | 22 ++++++++++++++++--- .../file/video_widget_media_kit_new.dart | 14 ++++++++++++ .../ui/viewer/file/video_widget_native.dart | 13 +++++++++++ mobile/lib/ui/viewer/file/zoomable_image.dart | 15 ++++++++++++- mobile/lib/utils/magic_util.dart | 4 +--- 7 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 mobile/lib/events/file_caption_updated_event.dart diff --git a/mobile/lib/events/file_caption_updated_event.dart b/mobile/lib/events/file_caption_updated_event.dart new file mode 100644 index 0000000000..2862e3704f --- /dev/null +++ b/mobile/lib/events/file_caption_updated_event.dart @@ -0,0 +1,7 @@ +import "package:photos/events/event.dart"; + +class FileCaptionUpdatedEvent extends Event { + final int fileGeneratedID; + + FileCaptionUpdatedEvent(this.fileGeneratedID); +} diff --git a/mobile/lib/ui/viewer/file/file_bottom_bar.dart b/mobile/lib/ui/viewer/file/file_bottom_bar.dart index 9fead0ca82..4c3dad5880 100644 --- a/mobile/lib/ui/viewer/file/file_bottom_bar.dart +++ b/mobile/lib/ui/viewer/file/file_bottom_bar.dart @@ -100,11 +100,6 @@ class FileBottomBarState extends State { ), onPressed: () async { await _displayDetails(widget.file); - safeRefresh(); //to instantly show the new caption if keypad is closed after pressing 'done' - here the caption will be updated before the bottom sheet is closed - await Future.delayed( - const Duration(milliseconds: 500), - ); //Waiting for some time till the caption gets updated in db if the user closes the bottom sheet without pressing 'done' - safeRefresh(); }, ), ), diff --git a/mobile/lib/ui/viewer/file/file_caption_widget.dart b/mobile/lib/ui/viewer/file/file_caption_widget.dart index bec10c7b20..3f5606122d 100644 --- a/mobile/lib/ui/viewer/file/file_caption_widget.dart +++ b/mobile/lib/ui/viewer/file/file_caption_widget.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; +import "package:photos/core/event_bus.dart"; +import "package:photos/events/file_caption_updated_event.dart"; import "package:photos/generated/l10n.dart"; import 'package:photos/models/file/file.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/ui/components/keyboard/keyboard_oveylay.dart'; import 'package:photos/ui/components/keyboard/keyboard_top_button.dart'; +import "package:photos/ui/notification/toast.dart"; import 'package:photos/utils/magic_util.dart'; class FileCaptionReadyOnly extends StatelessWidget { @@ -71,18 +74,19 @@ class _FileCaptionWidgetState extends State { @override void initState() { + super.initState(); _focusNode.addListener(_focusNodeListener); editedCaption = widget.file.caption; if (editedCaption != null && editedCaption!.isNotEmpty) { hintText = editedCaption!; } - super.initState(); } @override void dispose() { if (editedCaption != null) { - editFileCaption(null, widget.file, editedCaption!); + editFileCaption(null, widget.file, editedCaption!) + .then((isSuccess) => _onEditFileFinish(isSuccess)); } _textController.dispose(); _focusNode.removeListener(_focusNodeListener); @@ -148,7 +152,8 @@ class _FileCaptionWidgetState extends State { Future _onDoneClick(BuildContext context) async { if (editedCaption != null) { final isSuccesful = - await editFileCaption(context, widget.file, editedCaption!); + await editFileCaption(context, widget.file, editedCaption!) + .then((isSuccess) => _onEditFileFinish(isSuccess)); if (isSuccesful) { if (mounted) { Navigator.pop(context); @@ -185,4 +190,15 @@ class _FileCaptionWidgetState extends State { KeyboardOverlay.removeOverlay(); } } + + bool _onEditFileFinish(bool isSuccess) { + if (isSuccess) { + widget.file.pubMagicMetadata?.caption = editedCaption; + Bus.instance.fire(FileCaptionUpdatedEvent(widget.file.generatedID!)); + return true; + } else { + showShortToast(context, S.of(context).somethingWentWrong); + return false; + } + } } diff --git a/mobile/lib/ui/viewer/file/video_widget_media_kit_new.dart b/mobile/lib/ui/viewer/file/video_widget_media_kit_new.dart index a338114326..b0919260a4 100644 --- a/mobile/lib/ui/viewer/file/video_widget_media_kit_new.dart +++ b/mobile/lib/ui/viewer/file/video_widget_media_kit_new.dart @@ -7,6 +7,7 @@ import "package:media_kit/media_kit.dart"; import "package:media_kit_video/media_kit_video.dart"; import "package:photos/core/constants.dart"; import "package:photos/core/event_bus.dart"; +import "package:photos/events/file_caption_updated_event.dart"; import "package:photos/events/guest_view_event.dart"; import "package:photos/events/pause_video_event.dart"; import "package:photos/events/stream_switched_event.dart"; @@ -60,6 +61,8 @@ class _VideoWidgetMediaKitNewState extends State late final StreamSubscription _guestViewEventSubscription; bool _isGuestView = false; StreamSubscription? _streamSwitchedSubscription; + late final StreamSubscription + _captionUpdatedSubscription; @override void initState() { @@ -84,6 +87,7 @@ class _VideoWidgetMediaKitNewState extends State _isGuestView = event.isGuestView; }); }); + _streamSwitchedSubscription = Bus.instance.on().listen((event) { if (event.type != PlayerType.mediaKit || !mounted) return; @@ -93,6 +97,15 @@ class _VideoWidgetMediaKitNewState extends State loadOriginal(); } }); + + _captionUpdatedSubscription = + Bus.instance.on().listen((event) { + if (event.fileGeneratedID == widget.file.generatedID) { + if (mounted) { + setState(() {}); + } + } + }); } void loadPreview() { @@ -147,6 +160,7 @@ class _VideoWidgetMediaKitNewState extends State _progressNotifier.dispose(); WidgetsBinding.instance.removeObserver(this); player.dispose(); + _captionUpdatedSubscription.cancel(); super.dispose(); } diff --git a/mobile/lib/ui/viewer/file/video_widget_native.dart b/mobile/lib/ui/viewer/file/video_widget_native.dart index 5cdc9152c4..c93710c302 100644 --- a/mobile/lib/ui/viewer/file/video_widget_native.dart +++ b/mobile/lib/ui/viewer/file/video_widget_native.dart @@ -8,6 +8,7 @@ import "package:logging/logging.dart"; import "package:native_video_player/native_video_player.dart"; import "package:photos/core/constants.dart"; import "package:photos/core/event_bus.dart"; +import "package:photos/events/file_caption_updated_event.dart"; import "package:photos/events/guest_view_event.dart"; import "package:photos/events/pause_video_event.dart"; import "package:photos/events/seekbar_triggered_event.dart"; @@ -80,6 +81,8 @@ class _VideoWidgetNativeState extends State final _elTooltipController = ElTooltipController(); StreamSubscription? _subscription; StreamSubscription? _streamSwitchedSubscription; + late final StreamSubscription + _captionUpdatedSubscription; int position = 0; @override @@ -114,6 +117,15 @@ class _VideoWidgetNativeState extends State loadOriginal(update: true); } }); + + _captionUpdatedSubscription = + Bus.instance.on().listen((event) { + if (event.fileGeneratedID == widget.file.generatedID) { + if (mounted) { + setState(() {}); + } + } + }); } Future setVideoSource() async { @@ -207,6 +219,7 @@ class _VideoWidgetNativeState extends State _isSeeking.dispose(); _debouncer.cancelDebounceTimer(); _elTooltipController.dispose(); + _captionUpdatedSubscription.cancel(); super.dispose(); } diff --git a/mobile/lib/ui/viewer/file/zoomable_image.dart b/mobile/lib/ui/viewer/file/zoomable_image.dart index 399fdcf33c..9f889128d4 100644 --- a/mobile/lib/ui/viewer/file/zoomable_image.dart +++ b/mobile/lib/ui/viewer/file/zoomable_image.dart @@ -9,6 +9,7 @@ import 'package:photos/core/cache/thumbnail_in_memory_cache.dart'; import 'package:photos/core/constants.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/db/files_db.dart'; +import "package:photos/events/file_caption_updated_event.dart"; import "package:photos/events/files_updated_event.dart"; import 'package:photos/events/local_photos_updated_event.dart'; import "package:photos/models/file/extensions/file_props.dart"; @@ -57,6 +58,8 @@ class _ZoomableImageState extends State { bool _isZooming = false; PhotoViewController _photoViewController = PhotoViewController(); final _scaleStateController = PhotoViewScaleStateController(); + late final StreamSubscription + _captionUpdatedSubscription; @override void initState() { @@ -72,12 +75,22 @@ class _ZoomableImageState extends State { debugPrint("isZooming = $_isZooming, currentState $value"); // _logger.info('is reakky zooming $_isZooming with state $value'); }; + + _captionUpdatedSubscription = + Bus.instance.on().listen((event) { + if (event.fileGeneratedID == _photo.generatedID) { + if (mounted) { + setState(() {}); + } + } + }); } @override void dispose() { _photoViewController.dispose(); _scaleStateController.dispose(); + _captionUpdatedSubscription.cancel(); super.dispose(); } @@ -175,7 +188,7 @@ class _ZoomableImageState extends State { children: [ content, Positioned( - bottom: 72 + MediaQuery.of(context).padding.bottom, + bottom: 72 + MediaQuery.paddingOf(context).bottom, left: 0, right: 0, child: ValueListenableBuilder( diff --git a/mobile/lib/utils/magic_util.dart b/mobile/lib/utils/magic_util.dart index 923eb07b61..4094fa9a87 100644 --- a/mobile/lib/utils/magic_util.dart +++ b/mobile/lib/utils/magic_util.dart @@ -237,11 +237,9 @@ Future editFileCaption( caption, showDoneToast: false, ); + return true; } catch (e) { - if (context != null) { - showShortToast(context, S.of(context).somethingWentWrong); - } return false; } } From 3593a8e54559df6fa976afeec7c9f96814ce4390 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 10 Mar 2025 17:29:02 +0530 Subject: [PATCH 6/8] [mob][photos] Open file info bottom sheet when tapped on file description/caption --- .../file/video_widget_media_kit_common.dart | 24 ++++++---- .../ui/viewer/file/video_widget_native.dart | 23 ++++++---- mobile/lib/ui/viewer/file/zoomable_image.dart | 44 +++++++++++-------- 3 files changed, 55 insertions(+), 36 deletions(-) diff --git a/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart b/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart index 927485e0ea..772c663d8b 100644 --- a/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart +++ b/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart @@ -5,6 +5,7 @@ import "package:media_kit_video/media_kit_video.dart"; import "package:photos/models/file/file.dart"; import "package:photos/theme/colors.dart"; import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/actions/file/file_actions.dart"; import "package:photos/ui/common/loading_widget.dart"; import "package:photos/ui/viewer/file/preview_status_widget.dart"; import "package:photos/utils/standalone/date_time.dart"; @@ -140,7 +141,7 @@ class _VideoWidgetState extends State { SeekBarAndDuration( controller: widget.controller, isSeekingNotifier: _isSeekingNotifier, - caption: widget.file.caption, + file: widget.file, ), ], ), @@ -252,13 +253,13 @@ class _PlayPauseButtonState extends State { class SeekBarAndDuration extends StatelessWidget { final VideoController? controller; final ValueNotifier isSeekingNotifier; - final String? caption; + final EnteFile file; const SeekBarAndDuration({ super.key, required this.controller, required this.isSeekingNotifier, - required this.caption, + required this.file, }); @override @@ -286,7 +287,7 @@ class SeekBarAndDuration extends StatelessWidget { ), child: Column( children: [ - caption != null && caption!.isNotEmpty + file.caption != null && file.caption!.isNotEmpty ? Padding( padding: const EdgeInsets.fromLTRB( 0, @@ -294,11 +295,16 @@ class SeekBarAndDuration extends StatelessWidget { 0, 12, ), - child: Text( - caption!, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: getEnteTextTheme(context).mini, + child: GestureDetector( + onTap: () { + showDetailsSheet(context, file); + }, + child: Text( + file.caption!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: getEnteTextTheme(context).mini, + ), ), ) : const SizedBox.shrink(), diff --git a/mobile/lib/ui/viewer/file/video_widget_native.dart b/mobile/lib/ui/viewer/file/video_widget_native.dart index c93710c302..4b3090c3c1 100644 --- a/mobile/lib/ui/viewer/file/video_widget_native.dart +++ b/mobile/lib/ui/viewer/file/video_widget_native.dart @@ -370,7 +370,7 @@ class _VideoWidgetNativeState extends State showControls: _showControls, isSeeking: _isSeeking, position: position, - caption: widget.file.caption, + file: widget.file, ) : const SizedBox(); }, @@ -658,7 +658,7 @@ class _SeekBarAndDuration extends StatelessWidget { final ValueNotifier showControls; final ValueNotifier isSeeking; final int position; - final String? caption; + final EnteFile file; const _SeekBarAndDuration({ required this.controller, @@ -666,7 +666,7 @@ class _SeekBarAndDuration extends StatelessWidget { required this.showControls, required this.isSeeking, required this.position, - required this.caption, + required this.file, }); @override @@ -709,7 +709,7 @@ class _SeekBarAndDuration extends StatelessWidget { ), child: Column( children: [ - caption != null && caption!.isNotEmpty + file.caption != null && file.caption!.isNotEmpty ? Padding( padding: const EdgeInsets.fromLTRB( 0, @@ -717,11 +717,16 @@ class _SeekBarAndDuration extends StatelessWidget { 0, 12, ), - child: Text( - caption!, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: getEnteTextTheme(context).mini, + child: GestureDetector( + onTap: () { + showDetailsSheet(context, file); + }, + child: Text( + file.caption!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: getEnteTextTheme(context).mini, + ), ), ) : const SizedBox.shrink(), diff --git a/mobile/lib/ui/viewer/file/zoomable_image.dart b/mobile/lib/ui/viewer/file/zoomable_image.dart index 9f889128d4..5f6a7710ea 100644 --- a/mobile/lib/ui/viewer/file/zoomable_image.dart +++ b/mobile/lib/ui/viewer/file/zoomable_image.dart @@ -198,25 +198,33 @@ class _ZoomableImageState extends State { return AnimatedOpacity( opacity: doNotShowCaption ? 0.0 : 1.0, duration: const Duration(milliseconds: 200), - child: Container( - color: Colors.black.withOpacity(0.1), - width: double.infinity, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 4.0, - horizontal: 8.0, - ), - child: Text( - widget.photo.caption!, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: getEnteTextTheme(context).mini, - ), + child: IgnorePointer( + ignoring: doNotShowCaption, + child: GestureDetector( + onTap: () { + showDetailsSheet(context, widget.photo); + }, + child: Container( + color: Colors.black.withOpacity(0.1), + width: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 8.0, + ), + child: Text( + widget.photo.caption!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: getEnteTextTheme(context).mini, + ), + ), + ], ), - ], + ), ), ), ); From ba79588090a5aec2b87fc0187b2221eba945d742 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 10 Mar 2025 17:38:22 +0530 Subject: [PATCH 7/8] [mob][photos] Fix text colour --- .../lib/ui/viewer/file/video_widget_media_kit_common.dart | 4 +++- mobile/lib/ui/viewer/file/video_widget_native.dart | 4 +++- mobile/lib/ui/viewer/file/zoomable_image.dart | 7 ++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart b/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart index 772c663d8b..69e3f5eca9 100644 --- a/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart +++ b/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart @@ -303,7 +303,9 @@ class SeekBarAndDuration extends StatelessWidget { file.caption!, maxLines: 3, overflow: TextOverflow.ellipsis, - style: getEnteTextTheme(context).mini, + style: getEnteTextTheme(context) + .mini + .copyWith(color: textBaseDark), ), ), ) diff --git a/mobile/lib/ui/viewer/file/video_widget_native.dart b/mobile/lib/ui/viewer/file/video_widget_native.dart index 4b3090c3c1..a6256d965a 100644 --- a/mobile/lib/ui/viewer/file/video_widget_native.dart +++ b/mobile/lib/ui/viewer/file/video_widget_native.dart @@ -725,7 +725,9 @@ class _SeekBarAndDuration extends StatelessWidget { file.caption!, maxLines: 3, overflow: TextOverflow.ellipsis, - style: getEnteTextTheme(context).mini, + style: getEnteTextTheme(context) + .mini + .copyWith(color: textBaseDark), ), ), ) diff --git a/mobile/lib/ui/viewer/file/zoomable_image.dart b/mobile/lib/ui/viewer/file/zoomable_image.dart index 5f6a7710ea..109079a2df 100644 --- a/mobile/lib/ui/viewer/file/zoomable_image.dart +++ b/mobile/lib/ui/viewer/file/zoomable_image.dart @@ -15,6 +15,7 @@ import 'package:photos/events/local_photos_updated_event.dart'; import "package:photos/models/file/extensions/file_props.dart"; import 'package:photos/models/file/file.dart'; import "package:photos/states/detail_page_state.dart"; +import "package:photos/theme/colors.dart"; import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/actions/file/file_actions.dart"; import 'package:photos/ui/common/loading_widget.dart'; @@ -219,7 +220,11 @@ class _ZoomableImageState extends State { widget.photo.caption!, maxLines: 3, overflow: TextOverflow.ellipsis, - style: getEnteTextTheme(context).mini, + style: getEnteTextTheme(context) + .mini + .copyWith( + color: textBaseDark, + ), ), ), ], From 51ef7c60fa5095c3e8d4362417d4947a324c6751 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Tue, 11 Mar 2025 11:21:55 +0530 Subject: [PATCH 8/8] [mob][photos] Fix render overlow --- mobile/lib/ui/viewer/file/zoomable_image.dart | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/mobile/lib/ui/viewer/file/zoomable_image.dart b/mobile/lib/ui/viewer/file/zoomable_image.dart index 109079a2df..6b71809f85 100644 --- a/mobile/lib/ui/viewer/file/zoomable_image.dart +++ b/mobile/lib/ui/viewer/file/zoomable_image.dart @@ -216,15 +216,21 @@ class _ZoomableImageState extends State { vertical: 4.0, horizontal: 8.0, ), - child: Text( - widget.photo.caption!, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: getEnteTextTheme(context) - .mini - .copyWith( - color: textBaseDark, - ), + child: SizedBox( + width: + MediaQuery.sizeOf(context).width - 16, + child: Center( + child: Text( + widget.photo.caption!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: getEnteTextTheme(context) + .mini + .copyWith( + color: textBaseDark, + ), + ), + ), ), ), ],