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/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/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_common.dart b/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart index ba4b730206..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 @@ -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"; @@ -44,6 +45,7 @@ class _VideoWidgetState extends State { @override void initState() { + super.initState(); _isPlayingStreamSubscription = widget.controller.player.stream.playing.listen((isPlaying) { if (isPlaying && !_isSeekingNotifier.value) { @@ -55,7 +57,6 @@ class _VideoWidgetState extends State { }); _isSeekingNotifier.addListener(isSeekingListener); - super.initState(); } @override @@ -131,27 +132,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 +141,7 @@ class _VideoWidgetState extends State { SeekBarAndDuration( controller: widget.controller, isSeekingNotifier: _isSeekingNotifier, + file: widget.file, ), ], ), @@ -272,11 +253,13 @@ class _PlayPauseButtonState extends State { class SeekBarAndDuration extends StatelessWidget { final VideoController? controller; final ValueNotifier isSeekingNotifier; + final EnteFile file; const SeekBarAndDuration({ super.key, required this.controller, required this.isSeekingNotifier, + required this.file, }); @override @@ -302,46 +285,73 @@ 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), + file.caption != null && file.caption!.isNotEmpty + ? Padding( + padding: const EdgeInsets.fromLTRB( + 0, + 8, + 0, + 12, + ), + child: GestureDetector( + onTap: () { + showDetailsSheet(context, file); + }, + child: Text( + file.caption!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: getEnteTextTheme(context) + .mini + .copyWith(color: textBaseDark), + ), + ), + ) + : 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, - ), + ), + ], ), ], ), 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 65fb1ecb0f..a6256d965a 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(); } @@ -357,6 +370,7 @@ class _VideoWidgetNativeState extends State showControls: _showControls, isSeeking: _isSeeking, position: position, + file: widget.file, ) : const SizedBox(); }, @@ -644,6 +658,7 @@ class _SeekBarAndDuration extends StatelessWidget { final ValueNotifier showControls; final ValueNotifier isSeeking; final int position; + final EnteFile file; const _SeekBarAndDuration({ required this.controller, @@ -651,6 +666,7 @@ class _SeekBarAndDuration extends StatelessWidget { required this.showControls, required this.isSeeking, required this.position, + required this.file, }); @override @@ -691,34 +707,61 @@ 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, + file.caption != null && file.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: GestureDetector( + onTap: () { + showDetailsSheet(context, file); + }, + child: Text( + file.caption!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: getEnteTextTheme(context) + .mini + .copyWith(color: textBaseDark), + ), + ), + ) + : 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, + ), + ), + ], ), ], ), @@ -748,115 +791,85 @@ class _VideoDescriptionAndSwitchToMediaKitButton extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - mainAxisAlignment: Platform.isAndroid - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.center, - children: [ - file.caption?.isNotEmpty ?? 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, - ), + 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), ), - ); - }, - ), - ) - : 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(); + 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(); } } diff --git a/mobile/lib/ui/viewer/file/zoomable_image.dart b/mobile/lib/ui/viewer/file/zoomable_image.dart index 0743cba757..6b71809f85 100644 --- a/mobile/lib/ui/viewer/file/zoomable_image.dart +++ b/mobile/lib/ui/viewer/file/zoomable_image.dart @@ -9,10 +9,14 @@ 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"; 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'; import 'package:photos/utils/file_util.dart'; @@ -55,6 +59,8 @@ class _ZoomableImageState extends State { bool _isZooming = false; PhotoViewController _photoViewController = PhotoViewController(); final _scaleStateController = PhotoViewScaleStateController(); + late final StreamSubscription + _captionUpdatedSubscription; @override void initState() { @@ -70,12 +76,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(); } @@ -167,7 +183,68 @@ 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.paddingOf(context).bottom, + left: 0, + right: 0, + child: ValueListenableBuilder( + valueListenable: InheritedDetailPageState.of(context) + .enableFullScreenNotifier, + builder: (context, doNotShowCaption, _) { + return AnimatedOpacity( + opacity: doNotShowCaption ? 0.0 : 1.0, + duration: const Duration(milliseconds: 200), + 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: 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, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + }, + ), + ), + ], + ) + : content, ); } 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; } }