[mob][photo] Show file caption/description in file viewer. (#5279)

## Description

- Tapping on description/caption will open file info.

<img
src="https://github.com/user-attachments/assets/0f9422ec-49bb-43d8-9568-b57748587866"
width="300px">

<img
src="https://github.com/user-attachments/assets/43b704b4-6fc4-44ed-8d7a-97b7d27c90b0"
width="300px">

<img
src="https://github.com/user-attachments/assets/65fca334-14a7-4f01-95c4-46b231687438"
width="300px">

<img
src="https://github.com/user-attachments/assets/8e56cb29-7af6-439e-8627-3badc60aa383"
width="300px">
This commit is contained in:
Ashil
2025-03-11 11:36:11 +05:30
committed by GitHub
10 changed files with 460 additions and 305 deletions

View File

@@ -0,0 +1,7 @@
import "package:photos/events/event.dart";
class FileCaptionUpdatedEvent extends Event {
final int fileGeneratedID;
FileCaptionUpdatedEvent(this.fileGeneratedID);
}

View File

@@ -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<InheritedDetailPageState>()!;
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;
}

View File

@@ -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<DetailPage> {
List<EnteFile>? _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<DetailPage> {
void dispose() {
_guestViewEventSubscription.cancel();
_pageController.dispose();
_enableFullScreenNotifier.dispose();
_selectedIndexNotifier.dispose();
super.dispose();
@@ -137,96 +136,102 @@ class _DetailPageState extends State<DetailPage> {
_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<DetailPage> {
).ignore();
}
Widget _buildPageView(BuildContext context) {
Widget _buildPageView() {
return PageView.builder(
clipBehavior: Clip.none,
itemBuilder: (context, index) {
@@ -271,14 +276,17 @@ class _DetailPageState extends State<DetailPage> {
},
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<DetailPage> {
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]);

View File

@@ -100,11 +100,6 @@ class FileBottomBarState extends State<FileBottomBar> {
),
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();
},
),
),

View File

@@ -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<FileCaptionWidget> {
@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<FileCaptionWidget> {
Future<void> _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<FileCaptionWidget> {
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;
}
}
}

View File

@@ -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<VideoWidget> {
@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<VideoWidget> {
});
_isSeekingNotifier.addListener(isSeekingListener);
super.initState();
}
@override
@@ -131,27 +132,6 @@ class _VideoWidgetState extends State<VideoWidget> {
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<VideoWidget> {
SeekBarAndDuration(
controller: widget.controller,
isSeekingNotifier: _isSeekingNotifier,
file: widget.file,
),
],
),
@@ -272,11 +253,13 @@ class _PlayPauseButtonState extends State<PlayPauseButtonMediaKit> {
class SeekBarAndDuration extends StatelessWidget {
final VideoController? controller;
final ValueNotifier<bool> 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,
),
),
],
),
],
),

View File

@@ -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<VideoWidgetMediaKitNew>
late final StreamSubscription<GuestViewEvent> _guestViewEventSubscription;
bool _isGuestView = false;
StreamSubscription<StreamSwitchedEvent>? _streamSwitchedSubscription;
late final StreamSubscription<FileCaptionUpdatedEvent>
_captionUpdatedSubscription;
@override
void initState() {
@@ -84,6 +87,7 @@ class _VideoWidgetMediaKitNewState extends State<VideoWidgetMediaKitNew>
_isGuestView = event.isGuestView;
});
});
_streamSwitchedSubscription =
Bus.instance.on<StreamSwitchedEvent>().listen((event) {
if (event.type != PlayerType.mediaKit || !mounted) return;
@@ -93,6 +97,15 @@ class _VideoWidgetMediaKitNewState extends State<VideoWidgetMediaKitNew>
loadOriginal();
}
});
_captionUpdatedSubscription =
Bus.instance.on<FileCaptionUpdatedEvent>().listen((event) {
if (event.fileGeneratedID == widget.file.generatedID) {
if (mounted) {
setState(() {});
}
}
});
}
void loadPreview() {
@@ -147,6 +160,7 @@ class _VideoWidgetMediaKitNewState extends State<VideoWidgetMediaKitNew>
_progressNotifier.dispose();
WidgetsBinding.instance.removeObserver(this);
player.dispose();
_captionUpdatedSubscription.cancel();
super.dispose();
}

View File

@@ -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<VideoWidgetNative>
final _elTooltipController = ElTooltipController();
StreamSubscription<PlaybackEvent>? _subscription;
StreamSubscription<StreamSwitchedEvent>? _streamSwitchedSubscription;
late final StreamSubscription<FileCaptionUpdatedEvent>
_captionUpdatedSubscription;
int position = 0;
@override
@@ -114,6 +117,15 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
loadOriginal(update: true);
}
});
_captionUpdatedSubscription =
Bus.instance.on<FileCaptionUpdatedEvent>().listen((event) {
if (event.fileGeneratedID == widget.file.generatedID) {
if (mounted) {
setState(() {});
}
}
});
}
Future<void> setVideoSource() async {
@@ -207,6 +219,7 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
_isSeeking.dispose();
_debouncer.cancelDebounceTimer();
_elTooltipController.dispose();
_captionUpdatedSubscription.cancel();
super.dispose();
}
@@ -357,6 +370,7 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
showControls: _showControls,
isSeeking: _isSeeking,
position: position,
file: widget.file,
)
: const SizedBox();
},
@@ -644,6 +658,7 @@ class _SeekBarAndDuration extends StatelessWidget {
final ValueNotifier<bool> showControls;
final ValueNotifier<bool> 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();
}
}

View File

@@ -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<ZoomableImage> {
bool _isZooming = false;
PhotoViewController _photoViewController = PhotoViewController();
final _scaleStateController = PhotoViewScaleStateController();
late final StreamSubscription<FileCaptionUpdatedEvent>
_captionUpdatedSubscription;
@override
void initState() {
@@ -70,12 +76,22 @@ class _ZoomableImageState extends State<ZoomableImage> {
debugPrint("isZooming = $_isZooming, currentState $value");
// _logger.info('is reakky zooming $_isZooming with state $value');
};
_captionUpdatedSubscription =
Bus.instance.on<FileCaptionUpdatedEvent>().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<ZoomableImage> {
};
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<bool>(
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,
);
}

View File

@@ -237,11 +237,9 @@ Future<bool> editFileCaption(
caption,
showDoneToast: false,
);
return true;
} catch (e) {
if (context != null) {
showShortToast(context, S.of(context).somethingWentWrong);
}
return false;
}
}