Merge branch 'internal-15_06_2025' of https://github.com/ente-io/auth into internal-15_06_2025
This commit is contained in:
@@ -1,20 +1,25 @@
|
||||
import "dart:async";
|
||||
import "dart:io";
|
||||
import "dart:math";
|
||||
import "dart:ui";
|
||||
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/configuration.dart";
|
||||
import "package:photos/models/file/file_type.dart";
|
||||
import "package:photos/models/memories/memory.dart";
|
||||
import "package:photos/service_locator.dart";
|
||||
import "package:photos/services/smart_memories_service.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/theme/text_style.dart";
|
||||
import "package:photos/ui/actions/file/file_actions.dart";
|
||||
import "package:photos/ui/home/memories/memory_progress_indicator.dart";
|
||||
import "package:photos/ui/viewer/file/file_widget.dart";
|
||||
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
|
||||
import "package:photos/ui/viewer/file_details/favorite_widget.dart";
|
||||
import "package:photos/utils/file_util.dart";
|
||||
import "package:photos/utils/share_util.dart";
|
||||
import "package:step_progress_indicator/step_progress_indicator.dart";
|
||||
// import "package:step_progress_indicator/step_progress_indicator.dart";
|
||||
|
||||
//There are two states of variables that FullScreenMemory depends on:
|
||||
//1. The list of memories
|
||||
@@ -131,6 +136,10 @@ class FullScreenMemory extends StatefulWidget {
|
||||
class _FullScreenMemoryState extends State<FullScreenMemory> {
|
||||
PageController? _pageController;
|
||||
final _showTitle = ValueNotifier<bool>(true);
|
||||
AnimationController? _progressAnimationController;
|
||||
AnimationController? _zoomAnimationController;
|
||||
final ValueNotifier<Duration> durationNotifier =
|
||||
ValueNotifier(const Duration(seconds: 5));
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -148,13 +157,48 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
|
||||
void dispose() {
|
||||
_pageController?.dispose();
|
||||
_showTitle.dispose();
|
||||
durationNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _toggleAnimation(bool pause) {
|
||||
if (_progressAnimationController != null) {
|
||||
if (pause) {
|
||||
_progressAnimationController!.stop();
|
||||
} else {
|
||||
_progressAnimationController!.forward();
|
||||
}
|
||||
}
|
||||
if (_zoomAnimationController != null) {
|
||||
if (pause) {
|
||||
_zoomAnimationController!.stop();
|
||||
} else {
|
||||
_zoomAnimationController!.forward();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onFinalFileLoad(int duration) {
|
||||
if (_progressAnimationController!.isAnimating == true) {
|
||||
_progressAnimationController!.stop();
|
||||
}
|
||||
durationNotifier.value = Duration(seconds: duration);
|
||||
_progressAnimationController
|
||||
?..stop()
|
||||
..reset()
|
||||
..duration = durationNotifier.value
|
||||
..forward();
|
||||
_zoomAnimationController
|
||||
?..stop()
|
||||
..reset()
|
||||
..forward();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final inheritedData = FullScreenMemoryData.of(context)!;
|
||||
final showStepProgressIndicator = inheritedData.memories.length < 60;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
extendBodyBehindAppBar: true,
|
||||
@@ -180,17 +224,34 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
showStepProgressIndicator
|
||||
? StepProgressIndicator(
|
||||
totalSteps: inheritedData.memories.length,
|
||||
currentStep: value + 1,
|
||||
size: 2,
|
||||
selectedColor: Colors.white, //same for both themes
|
||||
unselectedColor: Colors.white.withOpacity(0.4),
|
||||
? ValueListenableBuilder<Duration>(
|
||||
valueListenable: durationNotifier,
|
||||
builder: (context, duration, _) {
|
||||
return NewProgressIndicator(
|
||||
totalSteps: inheritedData.memories.length,
|
||||
currentIndex: value,
|
||||
selectedColor: Colors.white,
|
||||
unselectedColor: Colors.white.withOpacity(0.4),
|
||||
duration: duration,
|
||||
animationController: (controller) {
|
||||
_progressAnimationController = controller;
|
||||
},
|
||||
onComplete: () {
|
||||
final currentIndex =
|
||||
inheritedData.indexNotifier.value;
|
||||
if (currentIndex <
|
||||
inheritedData.memories.length - 1) {
|
||||
_pageController!.nextPage(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
child!,
|
||||
@@ -231,16 +292,20 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
|
||||
body: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
const MemoryBackDrop(),
|
||||
PageView.builder(
|
||||
controller: _pageController ??= PageController(
|
||||
initialPage: widget.initialIndex,
|
||||
),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
if (index < inheritedData.memories.length - 1) {
|
||||
final nextFile = inheritedData.memories[index + 1].file;
|
||||
preloadThumbnail(nextFile);
|
||||
preloadFile(nextFile);
|
||||
}
|
||||
final currentFile = inheritedData.memories[index].file;
|
||||
final isVideo = currentFile.fileType == FileType.video;
|
||||
return GestureDetector(
|
||||
onTapDown: (TapDownDetails details) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
@@ -262,17 +327,39 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
|
||||
}
|
||||
}
|
||||
},
|
||||
child: FileWidget(
|
||||
inheritedData.memories[index].file,
|
||||
autoPlay: false,
|
||||
tagPrefix: "memories",
|
||||
backgroundDecoration: const BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
onLongPress: () {
|
||||
_toggleAnimation(true);
|
||||
},
|
||||
onLongPressUp: () {
|
||||
_toggleAnimation(false);
|
||||
},
|
||||
child: MemoriesZoomWidget(
|
||||
scaleController: (controller) {
|
||||
_zoomAnimationController = controller;
|
||||
},
|
||||
zoomIn: index % 2 == 0,
|
||||
isVideo: isVideo,
|
||||
child: FileWidget(
|
||||
inheritedData.memories[index].file,
|
||||
autoPlay: false,
|
||||
tagPrefix: "memories",
|
||||
backgroundDecoration: const BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
isFromMemories: true,
|
||||
playbackCallback: (isPlaying) {
|
||||
isPlaying
|
||||
? _toggleAnimation(false)
|
||||
: _toggleAnimation(true);
|
||||
},
|
||||
onFinalFileLoad: (duration) {
|
||||
onFinalFileLoad(duration);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
onPageChanged: (index) {
|
||||
onPageChanged: (index) async {
|
||||
unawaited(
|
||||
memoriesCacheService.markMemoryAsSeen(
|
||||
inheritedData.memories[index],
|
||||
@@ -450,3 +537,148 @@ class BottomGradient extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MemoryBackDrop extends StatelessWidget {
|
||||
const MemoryBackDrop({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final inheritedData = FullScreenMemoryData.of(context)!;
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: inheritedData.indexNotifier,
|
||||
builder: (context, value, _) {
|
||||
final currentFile = inheritedData.memories[value].file;
|
||||
if (currentFile.fileType == FileType.video ||
|
||||
currentFile.fileType == FileType.livePhoto) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
ThumbnailWidget(
|
||||
currentFile,
|
||||
shouldShowSyncStatus: false,
|
||||
shouldShowFavoriteIcon: false,
|
||||
),
|
||||
BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 100,
|
||||
sigmaY: 100,
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MemoriesZoomWidget extends StatefulWidget {
|
||||
final Widget child;
|
||||
final bool isVideo;
|
||||
final void Function(AnimationController)? scaleController;
|
||||
final bool zoomIn;
|
||||
|
||||
const MemoriesZoomWidget({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.isVideo,
|
||||
required this.zoomIn,
|
||||
this.scaleController,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MemoriesZoomWidget> createState() => _MemoriesZoomWidgetState();
|
||||
}
|
||||
|
||||
class _MemoriesZoomWidgetState extends State<MemoriesZoomWidget>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _scaleAnimation;
|
||||
late Animation<Offset> _panAnimation;
|
||||
Random random = Random();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initAnimation();
|
||||
}
|
||||
|
||||
void _initAnimation() {
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(
|
||||
seconds: 5,
|
||||
),
|
||||
);
|
||||
|
||||
final startScale = widget.zoomIn ? 1.05 : 1.15;
|
||||
final endScale = widget.zoomIn ? 1.15 : 1.05;
|
||||
|
||||
final startX = (random.nextDouble() - 0.5) * 0.1;
|
||||
final startY = (random.nextDouble() - 0.5) * 0.1;
|
||||
final endX = (random.nextDouble() - 0.5) * 0.1;
|
||||
final endY = (random.nextDouble() - 0.5) * 0.1;
|
||||
|
||||
_scaleAnimation = Tween<double>(
|
||||
begin: startScale,
|
||||
end: endScale,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
|
||||
_panAnimation = Tween<Offset>(
|
||||
begin: Offset(startX, startY),
|
||||
end: Offset(endX, endY),
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.scaleController != null) {
|
||||
widget.scaleController!(_controller);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.isVideo
|
||||
? widget.child
|
||||
: AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
return Transform.scale(
|
||||
scale: _scaleAnimation.value,
|
||||
child: Transform.translate(
|
||||
offset: Offset(
|
||||
_panAnimation.value.dx * 100,
|
||||
_panAnimation.value.dy * 100,
|
||||
),
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,17 +130,11 @@ class _MemoriesWidgetState extends State<MemoriesWidget> {
|
||||
controller: _controller,
|
||||
itemCount: collatedMemories.length,
|
||||
itemBuilder: (context, itemIndex) {
|
||||
final maxScaleOffsetX =
|
||||
_maxWidth + MemoryCoverWidget.horizontalPadding * 2;
|
||||
final offsetOfItem =
|
||||
(_maxWidth + MemoryCoverWidget.horizontalPadding * 2) * itemIndex;
|
||||
return MemoryCoverWidget(
|
||||
memories: collatedMemories[itemIndex].$1,
|
||||
controller: _controller,
|
||||
offsetOfItem: offsetOfItem,
|
||||
maxHeight: _maxHeight,
|
||||
maxWidth: _maxWidth,
|
||||
maxScaleOffsetX: maxScaleOffsetX,
|
||||
title: collatedMemories[itemIndex].$2,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -11,22 +11,18 @@ import "package:photos/utils/navigation_util.dart";
|
||||
class MemoryCoverWidget extends StatefulWidget {
|
||||
final List<Memory> memories;
|
||||
final ScrollController controller;
|
||||
final double offsetOfItem;
|
||||
final double maxHeight;
|
||||
final double maxWidth;
|
||||
static const outerStrokeWidth = 1.0;
|
||||
static const aspectRatio = 0.68;
|
||||
static const horizontalPadding = 2.5;
|
||||
final double maxScaleOffsetX;
|
||||
final String title;
|
||||
|
||||
const MemoryCoverWidget({
|
||||
required this.memories,
|
||||
required this.controller,
|
||||
required this.offsetOfItem,
|
||||
required this.maxHeight,
|
||||
required this.maxWidth,
|
||||
required this.maxScaleOffsetX,
|
||||
required this.title,
|
||||
super.key,
|
||||
});
|
||||
@@ -44,7 +40,6 @@ class _MemoryCoverWidgetState extends State<MemoryCoverWidget> {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final widthOfScreen = MediaQuery.sizeOf(context).width;
|
||||
final index = _getNextMemoryIndex();
|
||||
final title = widget.title;
|
||||
|
||||
@@ -56,9 +51,6 @@ class _MemoryCoverWidgetState extends State<MemoryCoverWidget> {
|
||||
return AnimatedBuilder(
|
||||
animation: widget.controller,
|
||||
builder: (context, child) {
|
||||
final diff = (widget.controller.offset - widget.offsetOfItem) +
|
||||
widget.maxScaleOffsetX;
|
||||
final scale = 1 - (diff / widthOfScreen).abs() / 3.7;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: MemoryCoverWidget.horizontalPadding,
|
||||
@@ -81,8 +73,8 @@ class _MemoryCoverWidgetState extends State<MemoryCoverWidget> {
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
height: widget.maxHeight * scale,
|
||||
width: widget.maxWidth * scale,
|
||||
height: widget.maxHeight ,
|
||||
width: widget.maxWidth ,
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: brightness == Brightness.dark
|
||||
? [
|
||||
@@ -122,29 +114,26 @@ class _MemoryCoverWidgetState extends State<MemoryCoverWidget> {
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 8 * scale,
|
||||
child: Transform.scale(
|
||||
scale: scale,
|
||||
child: SizedBox(
|
||||
width: widget.maxWidth,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: Hero(
|
||||
tag: title,
|
||||
child: Center(
|
||||
child: Text(
|
||||
title,
|
||||
style: getEnteTextTheme(context)
|
||||
.miniBold
|
||||
.copyWith(
|
||||
color: isSeen
|
||||
? textFaintDark
|
||||
: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
bottom: 8 ,
|
||||
child: SizedBox(
|
||||
width: widget.maxWidth,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: Hero(
|
||||
tag: title,
|
||||
child: Center(
|
||||
child: Text(
|
||||
title,
|
||||
style: getEnteTextTheme(context)
|
||||
.miniBold
|
||||
.copyWith(
|
||||
color: isSeen
|
||||
? textFaintDark
|
||||
: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -173,27 +162,24 @@ class _MemoryCoverWidgetState extends State<MemoryCoverWidget> {
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 8 * scale,
|
||||
child: Transform.scale(
|
||||
scale: scale,
|
||||
child: SizedBox(
|
||||
width: widget.maxWidth,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: Hero(
|
||||
tag: title,
|
||||
child: Center(
|
||||
child: Text(
|
||||
title,
|
||||
style: getEnteTextTheme(context)
|
||||
.miniBold
|
||||
.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
bottom: 8 ,
|
||||
child: SizedBox(
|
||||
width: widget.maxWidth,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: Hero(
|
||||
tag: title,
|
||||
child: Center(
|
||||
child: Text(
|
||||
title,
|
||||
style: getEnteTextTheme(context)
|
||||
.miniBold
|
||||
.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
106
mobile/lib/ui/home/memories/memory_progress_indicator.dart
Normal file
106
mobile/lib/ui/home/memories/memory_progress_indicator.dart
Normal file
@@ -0,0 +1,106 @@
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class NewProgressIndicator extends StatefulWidget {
|
||||
final int totalSteps;
|
||||
final int currentIndex;
|
||||
final Duration duration;
|
||||
final Color selectedColor;
|
||||
final Color unselectedColor;
|
||||
final double height;
|
||||
final double gap;
|
||||
final void Function(AnimationController)? animationController;
|
||||
final VoidCallback? onComplete;
|
||||
|
||||
const NewProgressIndicator({
|
||||
super.key,
|
||||
required this.totalSteps,
|
||||
required this.currentIndex,
|
||||
this.duration = const Duration(seconds: 5),
|
||||
this.selectedColor = Colors.white,
|
||||
this.unselectedColor = Colors.white54,
|
||||
this.height = 2.0,
|
||||
this.gap = 4.0,
|
||||
this.animationController,
|
||||
this.onComplete,
|
||||
});
|
||||
|
||||
@override
|
||||
State<NewProgressIndicator> createState() => _NewProgressIndicatorState();
|
||||
}
|
||||
|
||||
class _NewProgressIndicatorState extends State<NewProgressIndicator>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: widget.duration,
|
||||
);
|
||||
|
||||
_animation =
|
||||
Tween<double>(begin: 0.0, end: 1.0).animate(_animationController);
|
||||
|
||||
if (widget.animationController != null) {
|
||||
widget.animationController!(_animationController);
|
||||
}
|
||||
|
||||
_animationController.addStatusListener((status) {
|
||||
if (status == AnimationStatus.completed && widget.onComplete != null) {
|
||||
widget.onComplete!();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: List.generate(widget.totalSteps, (index) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 4, right: 4),
|
||||
child: index < widget.currentIndex
|
||||
? Container(
|
||||
height: widget.height,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.selectedColor,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
)
|
||||
: index == widget.currentIndex
|
||||
? AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (context, child) {
|
||||
return LinearProgressIndicator(
|
||||
value: _animation.value,
|
||||
backgroundColor: widget.unselectedColor,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
widget.selectedColor,
|
||||
),
|
||||
minHeight: widget.height,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
);
|
||||
},
|
||||
)
|
||||
: Container(
|
||||
height: widget.height,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.unselectedColor,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ class FileWidget extends StatelessWidget {
|
||||
final Function(bool)? playbackCallback;
|
||||
final BoxDecoration? backgroundDecoration;
|
||||
final bool? autoPlay;
|
||||
final bool? isFromMemories;
|
||||
final Function(int)? onFinalFileLoad;
|
||||
|
||||
const FileWidget(
|
||||
this.file, {
|
||||
@@ -20,6 +22,8 @@ class FileWidget extends StatelessWidget {
|
||||
this.playbackCallback,
|
||||
required this.tagPrefix,
|
||||
this.backgroundDecoration,
|
||||
this.isFromMemories = false,
|
||||
this.onFinalFileLoad,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -37,7 +41,9 @@ class FileWidget extends StatelessWidget {
|
||||
shouldDisableScroll: shouldDisableScroll,
|
||||
tagPrefix: tagPrefix,
|
||||
backgroundDecoration: backgroundDecoration,
|
||||
isFromMemories: isFromMemories ?? false,
|
||||
key: key ?? ValueKey(fileKey),
|
||||
onFinalFileLoad: onFinalFileLoad,
|
||||
);
|
||||
} else if (file.fileType == FileType.video) {
|
||||
// use old video widget on iOS simulator as the new one crashes while
|
||||
@@ -54,6 +60,8 @@ class FileWidget extends StatelessWidget {
|
||||
file,
|
||||
tagPrefix: tagPrefix,
|
||||
playbackCallback: playbackCallback,
|
||||
onFinalFileLoad: onFinalFileLoad,
|
||||
isFromMemories: isFromMemories ?? false,
|
||||
key: key ?? ValueKey(fileKey),
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -23,10 +23,15 @@ class VideoWidget extends StatefulWidget {
|
||||
final EnteFile file;
|
||||
final String? tagPrefix;
|
||||
final Function(bool)? playbackCallback;
|
||||
final Function(int)? onFinalFileLoad;
|
||||
final bool isFromMemories;
|
||||
|
||||
const VideoWidget(
|
||||
this.file, {
|
||||
this.tagPrefix,
|
||||
this.playbackCallback,
|
||||
this.onFinalFileLoad,
|
||||
this.isFromMemories = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -149,6 +154,7 @@ class _VideoWidgetState extends State<VideoWidget> {
|
||||
playbackCallback: widget.playbackCallback,
|
||||
playlistData: playlistData,
|
||||
selectedPreview: playPreview,
|
||||
isFromMemories: widget.isFromMemories,
|
||||
onStreamChange: () {
|
||||
setState(() {
|
||||
selectPreviewForPlay = !selectPreviewForPlay;
|
||||
@@ -162,6 +168,7 @@ class _VideoWidgetState extends State<VideoWidget> {
|
||||
);
|
||||
});
|
||||
},
|
||||
onFinalFileLoad: widget.onFinalFileLoad,
|
||||
);
|
||||
}
|
||||
return VideoWidgetMediaKitNew(
|
||||
@@ -171,6 +178,7 @@ class _VideoWidgetState extends State<VideoWidget> {
|
||||
playbackCallback: widget.playbackCallback,
|
||||
preview: playlistData?.preview,
|
||||
selectedPreview: playPreview,
|
||||
isFromMemories: widget.isFromMemories,
|
||||
onStreamChange: () {
|
||||
setState(() {
|
||||
selectPreviewForPlay = !selectPreviewForPlay;
|
||||
@@ -184,6 +192,7 @@ class _VideoWidgetState extends State<VideoWidget> {
|
||||
);
|
||||
});
|
||||
},
|
||||
onFinalFileLoad: widget.onFinalFileLoad,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ class _VideoWidgetState extends State<VideoWidget> {
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (widget.isFromMemories) return;
|
||||
showControlsNotifier.value = !showControlsNotifier.value;
|
||||
if (widget.playbackCallback != null) {
|
||||
widget.playbackCallback!(
|
||||
@@ -107,48 +108,62 @@ class _VideoWidgetState extends State<VideoWidget> {
|
||||
);
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (widget.isFromMemories) {
|
||||
widget.controller.player.stop();
|
||||
}
|
||||
},
|
||||
onLongPressUp: () {
|
||||
if (widget.isFromMemories) {
|
||||
widget.controller.player.play();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
constraints: const BoxConstraints.expand(),
|
||||
),
|
||||
),
|
||||
IgnorePointer(
|
||||
ignoring: !value,
|
||||
child: PlayPauseButtonMediaKit(widget.controller),
|
||||
),
|
||||
Positioned(
|
||||
bottom: verticalMargin,
|
||||
right: 0,
|
||||
left: 0,
|
||||
child: IgnorePointer(
|
||||
ignoring: !value,
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
left: false,
|
||||
right: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: widget.isFromMemories ? 32 : 0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
VideoStreamChangeWidget(
|
||||
showControls: value,
|
||||
file: widget.file,
|
||||
isPreviewPlayer: widget.isPreviewPlayer,
|
||||
onStreamChange: widget.onStreamChange,
|
||||
widget.isFromMemories
|
||||
? const SizedBox.shrink()
|
||||
: IgnorePointer(
|
||||
ignoring: !value,
|
||||
child: PlayPauseButtonMediaKit(widget.controller),
|
||||
),
|
||||
widget.isFromMemories
|
||||
? const SizedBox.shrink()
|
||||
: Positioned(
|
||||
bottom: verticalMargin,
|
||||
right: 0,
|
||||
left: 0,
|
||||
child: IgnorePointer(
|
||||
ignoring: !value,
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
left: false,
|
||||
right: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: widget.isFromMemories ? 32 : 0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
VideoStreamChangeWidget(
|
||||
showControls: value,
|
||||
file: widget.file,
|
||||
isPreviewPlayer: widget.isPreviewPlayer,
|
||||
onStreamChange: widget.onStreamChange,
|
||||
),
|
||||
SeekBarAndDuration(
|
||||
controller: widget.controller,
|
||||
isSeekingNotifier: _isSeekingNotifier,
|
||||
file: widget.file,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SeekBarAndDuration(
|
||||
controller: widget.controller,
|
||||
isSeekingNotifier: _isSeekingNotifier,
|
||||
file: widget.file,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -36,6 +36,7 @@ class VideoWidgetMediaKitNew extends StatefulWidget {
|
||||
final void Function() onStreamChange;
|
||||
final File? preview;
|
||||
final bool selectedPreview;
|
||||
final Function(int)? onFinalFileLoad;
|
||||
|
||||
const VideoWidgetMediaKitNew(
|
||||
this.file, {
|
||||
@@ -45,6 +46,7 @@ class VideoWidgetMediaKitNew extends StatefulWidget {
|
||||
required this.onStreamChange,
|
||||
this.preview,
|
||||
required this.selectedPreview,
|
||||
this.onFinalFileLoad,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -307,6 +309,8 @@ class _VideoWidgetMediaKitNewState extends State<VideoWidgetMediaKitNew>
|
||||
}
|
||||
player.open(Media(url), play: _isAppInFG);
|
||||
});
|
||||
final duration = controller!.player.state.duration.inSeconds;
|
||||
widget.onFinalFileLoad?.call(duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ class VideoWidgetNative extends StatefulWidget {
|
||||
final void Function()? onStreamChange;
|
||||
final PlaylistData? playlistData;
|
||||
final bool selectedPreview;
|
||||
final Function(int)? onFinalFileLoad;
|
||||
|
||||
const VideoWidgetNative(
|
||||
this.file, {
|
||||
@@ -55,6 +56,7 @@ class VideoWidgetNative extends StatefulWidget {
|
||||
required this.onStreamChange,
|
||||
super.key,
|
||||
this.playlistData,
|
||||
this.onFinalFileLoad,
|
||||
required this.selectedPreview,
|
||||
});
|
||||
|
||||
@@ -302,12 +304,23 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (widget.isFromMemories) return;
|
||||
_showControls.value = !_showControls.value;
|
||||
if (widget.playbackCallback != null) {
|
||||
widget.playbackCallback!(!_showControls.value);
|
||||
}
|
||||
_elTooltipController.hide();
|
||||
},
|
||||
onLongPress: () {
|
||||
if (widget.isFromMemories) {
|
||||
_controller?.pause();
|
||||
}
|
||||
},
|
||||
onLongPressUp: () {
|
||||
if (widget.isFromMemories) {
|
||||
_controller?.play();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
constraints: const BoxConstraints.expand(),
|
||||
),
|
||||
@@ -331,32 +344,38 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
Positioned.fill(
|
||||
child: Center(
|
||||
child: ValueListenableBuilder(
|
||||
builder: (BuildContext context, bool value, _) {
|
||||
return value
|
||||
? ValueListenableBuilder(
|
||||
builder: (context, bool value, _) {
|
||||
return AnimatedOpacity(
|
||||
duration:
|
||||
const Duration(milliseconds: 200),
|
||||
opacity: value ? 1 : 0,
|
||||
curve: Curves.easeInOutQuad,
|
||||
child: IgnorePointer(
|
||||
ignoring: !value,
|
||||
child: PlayPauseButton(_controller),
|
||||
),
|
||||
);
|
||||
},
|
||||
valueListenable: _showControls,
|
||||
)
|
||||
: const SizedBox();
|
||||
},
|
||||
valueListenable: _isPlaybackReady,
|
||||
),
|
||||
),
|
||||
),
|
||||
widget.isFromMemories
|
||||
? const SizedBox.shrink()
|
||||
: Positioned.fill(
|
||||
child: Center(
|
||||
child: ValueListenableBuilder(
|
||||
builder:
|
||||
(BuildContext context, bool value, _) {
|
||||
return value
|
||||
? ValueListenableBuilder(
|
||||
builder: (context, bool value, _) {
|
||||
return AnimatedOpacity(
|
||||
duration: const Duration(
|
||||
milliseconds: 200,
|
||||
),
|
||||
opacity: value ? 1 : 0,
|
||||
curve: Curves.easeInOutQuad,
|
||||
child: IgnorePointer(
|
||||
ignoring: !value,
|
||||
child: PlayPauseButton(
|
||||
_controller,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
valueListenable: _showControls,
|
||||
)
|
||||
: const SizedBox();
|
||||
},
|
||||
valueListenable: _isPlaybackReady,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: verticalMargin,
|
||||
right: 0,
|
||||
@@ -394,7 +413,7 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
|
||||
valueListenable: _isPlaybackReady,
|
||||
builder:
|
||||
(BuildContext context, bool value, _) {
|
||||
return value
|
||||
return value && !widget.isFromMemories
|
||||
? _SeekBarAndDuration(
|
||||
controller: _controller,
|
||||
duration: duration,
|
||||
@@ -525,6 +544,8 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
|
||||
Future<void> _onPlaybackReady() async {
|
||||
if (_isPlaybackReady.value) return;
|
||||
await _controller!.play();
|
||||
final durationInSeconds = durationToSeconds(duration) ?? 0;
|
||||
widget.onFinalFileLoad?.call(durationInSeconds);
|
||||
unawaited(_controller!.setVolume(1));
|
||||
_isPlaybackReady.value = true;
|
||||
}
|
||||
@@ -739,11 +760,7 @@ class _SeekBarAndDuration extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: showControls,
|
||||
builder: (
|
||||
BuildContext context,
|
||||
bool value,
|
||||
_,
|
||||
) {
|
||||
builder: (BuildContext context, bool value, _) {
|
||||
return AnimatedOpacity(
|
||||
duration: const Duration(
|
||||
milliseconds: 200,
|
||||
|
||||
@@ -31,6 +31,7 @@ class ZoomableImage extends StatefulWidget {
|
||||
final Decoration? backgroundDecoration;
|
||||
final bool shouldCover;
|
||||
final bool isGuestView;
|
||||
final Function(int)? onFinalFileLoad;
|
||||
|
||||
const ZoomableImage(
|
||||
this.photo, {
|
||||
@@ -40,6 +41,7 @@ class ZoomableImage extends StatefulWidget {
|
||||
this.backgroundDecoration,
|
||||
this.shouldCover = false,
|
||||
this.isGuestView = false,
|
||||
this.onFinalFileLoad,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -426,6 +428,9 @@ class _ZoomableImageState extends State<ZoomableImage> {
|
||||
_loadedFinalImage = true;
|
||||
_logger.info("Final image loaded");
|
||||
});
|
||||
if (_imageProvider != null) {
|
||||
widget.onFinalFileLoad?.call(5);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updatePhotoViewController({
|
||||
|
||||
@@ -23,6 +23,8 @@ class ZoomableLiveImageNew extends StatefulWidget {
|
||||
final Function(bool)? shouldDisableScroll;
|
||||
final String? tagPrefix;
|
||||
final Decoration? backgroundDecoration;
|
||||
final bool isFromMemories;
|
||||
final Function(int)? onFinalFileLoad;
|
||||
|
||||
const ZoomableLiveImageNew(
|
||||
this.enteFile, {
|
||||
@@ -30,6 +32,8 @@ class ZoomableLiveImageNew extends StatefulWidget {
|
||||
this.shouldDisableScroll,
|
||||
required this.tagPrefix,
|
||||
this.backgroundDecoration,
|
||||
this.isFromMemories = false,
|
||||
this.onFinalFileLoad,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -94,13 +98,17 @@ class _ZoomableLiveImageNewState extends State<ZoomableLiveImageNew>
|
||||
shouldDisableScroll: widget.shouldDisableScroll,
|
||||
backgroundDecoration: widget.backgroundDecoration,
|
||||
isGuestView: isGuestView,
|
||||
onFinalFileLoad: widget.onFinalFileLoad,
|
||||
);
|
||||
}
|
||||
return GestureDetector(
|
||||
onLongPressStart: (_) => {_onLongPressEvent(true)},
|
||||
onLongPressEnd: (_) => {_onLongPressEvent(false)},
|
||||
child: content,
|
||||
);
|
||||
if (!widget.isFromMemories) {
|
||||
return GestureDetector(
|
||||
onLongPressStart: (_) => _onLongPressEvent(true),
|
||||
onLongPressEnd: (_) => _onLongPressEvent(false),
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -12,7 +12,7 @@ description: ente photos application
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
|
||||
version: 1.1.2+1055
|
||||
version: 1.1.2+1056
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
||||
Reference in New Issue
Block a user