Resolve merge conflicts and merge UI/UX improvements to memory_improvement branch
This commit is contained in:
18
mobile/lib/events/reset_zoom_of_photo_view_event.dart
Normal file
18
mobile/lib/events/reset_zoom_of_photo_view_event.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import "package:photos/events/event.dart";
|
||||
|
||||
class ResetZoomOfPhotoView extends Event {
|
||||
final int? uploadedFileID;
|
||||
final String? localID;
|
||||
|
||||
ResetZoomOfPhotoView({
|
||||
required this.localID,
|
||||
required this.uploadedFileID,
|
||||
});
|
||||
|
||||
bool isSamePhoto({required int? uploadedFileID, required String? localID}) {
|
||||
if (this.uploadedFileID == uploadedFileID && this.localID == localID) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:photos/models/memories/memory.dart";
|
||||
import "package:photos/theme/colors.dart";
|
||||
import "package:photos/ui/home/memories/full_screen_memory.dart";
|
||||
|
||||
class AllMemoriesPage extends StatefulWidget {
|
||||
@@ -50,27 +51,26 @@ class _AllMemoriesPageState extends State<AllMemoriesPage>
|
||||
return FullScreenMemoryDataUpdater(
|
||||
initialIndex: initialMemoryIndex,
|
||||
memories: widget.allMemories[index],
|
||||
child: ClipRRect(
|
||||
child: FullScreenMemory(
|
||||
widget.allTitles[index],
|
||||
initialMemoryIndex,
|
||||
onNextMemory: index < widget.allMemories.length - 1
|
||||
? () => pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.ease,
|
||||
)
|
||||
: null,
|
||||
onPreviousMemory: index > 0
|
||||
? () => pageController.previousPage(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.ease,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: FullScreenMemory(
|
||||
widget.allTitles[index],
|
||||
initialMemoryIndex,
|
||||
onNextMemory: index < widget.allMemories.length - 1
|
||||
? () => pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.ease,
|
||||
)
|
||||
: null,
|
||||
onPreviousMemory: index > 0
|
||||
? () => pageController.previousPage(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.ease,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
backgroundColor: backgroundBaseDark,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
150
mobile/lib/ui/home/memories/custom_listener.dart
Normal file
150
mobile/lib/ui/home/memories/custom_listener.dart
Normal file
@@ -0,0 +1,150 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class ActivePointers with ChangeNotifier {
|
||||
final Set<int> _activePointers = {};
|
||||
bool get hasActivePointers => _activePointers.isNotEmpty;
|
||||
bool activePointerWasPartOfMultitouch = false;
|
||||
|
||||
void add(int pointer) {
|
||||
if (_activePointers.isNotEmpty && !_activePointers.contains(pointer)) {
|
||||
activePointerWasPartOfMultitouch = true;
|
||||
}
|
||||
_activePointers.add(pointer);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void remove(int pointer) {
|
||||
_activePointers.remove(pointer);
|
||||
if (_activePointers.isEmpty) {
|
||||
activePointerWasPartOfMultitouch = false;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// `onLongPress` and `onLongPressUp` have not been tested enough to make sure
|
||||
/// it works as expected, so they are commented out for now.
|
||||
class MemoriesPointerGestureListener extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Function(PointerEvent)? onTap;
|
||||
// final VoidCallback? onLongPress;
|
||||
// final VoidCallback? onLongPressUp;
|
||||
|
||||
/// How long the pointer must stay down before a long‐press fires.
|
||||
final Duration longPressDuration;
|
||||
|
||||
/// Maximum movement (in logical pixels) before we consider it a drag.
|
||||
final double touchSlop;
|
||||
|
||||
/// Notifier that indicates whether there are active pointers.
|
||||
final ValueNotifier<bool>? hasPointerNotifier;
|
||||
static const double kTouchSlop = 18.0; // Default touch slop value
|
||||
|
||||
const MemoriesPointerGestureListener({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.onTap,
|
||||
// this.onLongPress,
|
||||
// this.onLongPressUp,
|
||||
this.hasPointerNotifier,
|
||||
this.longPressDuration = const Duration(milliseconds: 500),
|
||||
this.touchSlop = kTouchSlop, // from flutter/gestures/constants.dart
|
||||
});
|
||||
|
||||
@override
|
||||
MemoriesPointerGestureListenerState createState() =>
|
||||
MemoriesPointerGestureListenerState();
|
||||
}
|
||||
|
||||
class MemoriesPointerGestureListenerState
|
||||
extends State<MemoriesPointerGestureListener> {
|
||||
Timer? _longPressTimer;
|
||||
bool _longPressFired = false;
|
||||
Offset? _downPosition;
|
||||
bool hasPointerMoved = false;
|
||||
final _activePointers = ActivePointers();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_activePointers.addListener(_activatePointerListener);
|
||||
}
|
||||
|
||||
void _activatePointerListener() {
|
||||
if (widget.hasPointerNotifier != null) {
|
||||
widget.hasPointerNotifier!.value = _activePointers.hasActivePointers;
|
||||
}
|
||||
}
|
||||
|
||||
void _handlePointerDown(PointerDownEvent event) {
|
||||
_addPointer(event.pointer);
|
||||
_downPosition = event.localPosition;
|
||||
_longPressFired = false;
|
||||
_longPressTimer?.cancel();
|
||||
_longPressTimer = Timer(widget.longPressDuration, () {
|
||||
_longPressFired = true;
|
||||
// widget.onLongPress?.call();
|
||||
});
|
||||
}
|
||||
|
||||
void _handlePointerMove(PointerMoveEvent event) {
|
||||
if (_longPressTimer != null && _downPosition != null) {
|
||||
final distance = (event.localPosition - _downPosition!).distance;
|
||||
if (distance > widget.touchSlop) {
|
||||
// user started dragging – cancel long‐press
|
||||
hasPointerMoved = true;
|
||||
_longPressTimer!.cancel();
|
||||
_longPressTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handlePointerUp(PointerUpEvent event) {
|
||||
_longPressTimer?.cancel();
|
||||
_longPressTimer = null;
|
||||
|
||||
if (_longPressFired) {
|
||||
// widget.onLongPressUp?.call();
|
||||
} else {
|
||||
if (!_activePointers.activePointerWasPartOfMultitouch &&
|
||||
!hasPointerMoved) {
|
||||
widget.onTap?.call(event);
|
||||
}
|
||||
}
|
||||
_removePointer(event.pointer);
|
||||
_reset();
|
||||
}
|
||||
|
||||
void _handlePointerCancel(PointerCancelEvent event) {
|
||||
_longPressTimer?.cancel();
|
||||
_longPressTimer = null;
|
||||
_longPressFired = false;
|
||||
_removePointer(event.pointer);
|
||||
_reset();
|
||||
}
|
||||
|
||||
void _removePointer(int pointer) {
|
||||
_activePointers.remove(pointer);
|
||||
}
|
||||
|
||||
void _addPointer(int pointer) {
|
||||
_activePointers.add(pointer);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Listener(
|
||||
onPointerDown: _handlePointerDown,
|
||||
onPointerMove: _handlePointerMove,
|
||||
onPointerUp: _handlePointerUp,
|
||||
onPointerCancel: _handlePointerCancel,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void _reset() {
|
||||
hasPointerMoved = false;
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,17 @@ import "dart:ui";
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/configuration.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/events/reset_zoom_of_photo_view_event.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/colors.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/custom_listener.dart";
|
||||
import "package:photos/ui/home/memories/memory_progress_indicator.dart";
|
||||
import "package:photos/ui/viewer/file/file_widget.dart";
|
||||
|
||||
@@ -37,6 +41,8 @@ import "package:photos/utils/share_util.dart";
|
||||
//ValueNotifier inside the InheritedWidget and the widgets that need to change
|
||||
//are wrapped in a ValueListenableBuilder.
|
||||
|
||||
//TODO: Use better naming convention. "Memory" should be a whole memory and
|
||||
//parts of the memory should be called "items".
|
||||
class FullScreenMemoryDataUpdater extends StatefulWidget {
|
||||
final List<Memory> memories;
|
||||
final int initialIndex;
|
||||
@@ -145,28 +151,61 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
|
||||
final ValueNotifier<Duration> durationNotifier =
|
||||
ValueNotifier(const Duration(seconds: 5));
|
||||
|
||||
/// Used to check if any pointer is on the screen.
|
||||
final hasPointerOnScreenNotifier = ValueNotifier<bool>(false);
|
||||
bool hasFinalFileLoaded = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.delayed(const Duration(seconds: 3), () {
|
||||
if (mounted) _showTitle.value = false;
|
||||
});
|
||||
hasPointerOnScreenNotifier.addListener(
|
||||
_hasPointerListener,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_showTitle.dispose();
|
||||
durationNotifier.dispose();
|
||||
hasPointerOnScreenNotifier.removeListener(_hasPointerListener);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _toggleAnimation(bool pause) {
|
||||
/// Used to check if user has touched the screen and then to pause animation
|
||||
/// and once the pointer is removed from the screen, it resumes the animation
|
||||
/// It also resets the zoom of the photo view to default for better user
|
||||
/// experience after finger(s) is removed from the screen after zooming in by
|
||||
/// pinching.
|
||||
void _hasPointerListener() {
|
||||
if (hasPointerOnScreenNotifier.value) {
|
||||
_toggleAnimation(pause: true);
|
||||
} else {
|
||||
_toggleAnimation(pause: false);
|
||||
final inheritedData = FullScreenMemoryData.of(context)!;
|
||||
final currentFile =
|
||||
inheritedData.memories[inheritedData.indexNotifier.value].file;
|
||||
Bus.instance.fire(
|
||||
ResetZoomOfPhotoView(
|
||||
localID: currentFile.localID,
|
||||
uploadedFileID: currentFile.uploadedFileID,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _toggleAnimation({required bool pause}) {
|
||||
if (pause) {
|
||||
_progressAnimationController?.stop();
|
||||
_zoomAnimationController?.stop();
|
||||
} else {
|
||||
_progressAnimationController?.forward();
|
||||
_zoomAnimationController?.forward();
|
||||
if (hasFinalFileLoaded) {
|
||||
_progressAnimationController?.forward();
|
||||
_zoomAnimationController?.forward();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +219,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
|
||||
}
|
||||
|
||||
void onFinalFileLoad(int duration) {
|
||||
hasFinalFileLoaded = true;
|
||||
if (_progressAnimationController?.isAnimating == true) {
|
||||
_progressAnimationController!.stop();
|
||||
}
|
||||
@@ -196,6 +236,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
|
||||
}
|
||||
|
||||
void _goToNext(FullScreenMemoryData inheritedData) {
|
||||
hasFinalFileLoaded = false;
|
||||
final currentIndex = inheritedData.indexNotifier.value;
|
||||
if (currentIndex < inheritedData.memories.length - 1) {
|
||||
inheritedData.indexNotifier.value += 1;
|
||||
@@ -206,6 +247,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
|
||||
}
|
||||
|
||||
void _goToPrevious(FullScreenMemoryData inheritedData) {
|
||||
hasFinalFileLoaded = false;
|
||||
final currentIndex = inheritedData.indexNotifier.value;
|
||||
if (currentIndex > 0) {
|
||||
inheritedData.indexNotifier.value -= 1;
|
||||
@@ -228,180 +270,203 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenPadding = MediaQuery.paddingOf(context);
|
||||
final inheritedData = FullScreenMemoryData.of(context)!;
|
||||
final showStepProgressIndicator = inheritedData.memories.length < 60;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 84,
|
||||
automaticallyImplyLeading: false,
|
||||
title: ValueListenableBuilder(
|
||||
valueListenable: inheritedData.indexNotifier,
|
||||
child: InkWell(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.fromLTRB(4, 8, 8, 8),
|
||||
child: Icon(Icons.close, color: Colors.white),
|
||||
),
|
||||
),
|
||||
builder: (context, value, child) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
showStepProgressIndicator
|
||||
? ValueListenableBuilder<Duration>(
|
||||
valueListenable: durationNotifier,
|
||||
builder: (context, duration, _) {
|
||||
return MemoryProgressIndicator(
|
||||
totalSteps: inheritedData.memories.length,
|
||||
currentIndex: value,
|
||||
selectedColor: Colors.white,
|
||||
unselectedColor: Colors.white.withOpacity(0.4),
|
||||
duration: duration,
|
||||
animationController: (controller) {
|
||||
_progressAnimationController = controller;
|
||||
},
|
||||
onComplete: () {
|
||||
_goToNext(inheritedData);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
child!,
|
||||
Text(
|
||||
SmartMemoriesService.getDateFormatted(
|
||||
creationTime:
|
||||
inheritedData.memories[value].file.creationTime!,
|
||||
context: context,
|
||||
),
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
flexibleSpace: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.black54,
|
||||
Colors.black45,
|
||||
Colors.transparent,
|
||||
],
|
||||
stops: [0, 0.6, 1],
|
||||
),
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
4,
|
||||
screenPadding.top + 8,
|
||||
4,
|
||||
screenPadding.bottom + 8,
|
||||
),
|
||||
body: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
const MemoryBackDrop(),
|
||||
ValueListenableBuilder<int>(
|
||||
valueListenable: inheritedData.indexNotifier,
|
||||
builder: (context, index, _) {
|
||||
if (index < inheritedData.memories.length - 1) {
|
||||
final nextFile = inheritedData.memories[index + 1].file;
|
||||
preloadThumbnail(nextFile);
|
||||
preloadFile(nextFile);
|
||||
}
|
||||
final currentMemory = inheritedData.memories[index];
|
||||
final isVideo = currentMemory.file.fileType == FileType.video;
|
||||
final currentFile = currentMemory.file;
|
||||
|
||||
return GestureDetector(
|
||||
onTapUp: (TapUpDetails details) {
|
||||
final screenWidth = MediaQuery.sizeOf(context).width;
|
||||
final edgeWidth = screenWidth * 0.20;
|
||||
if (details.localPosition.dx < edgeWidth) {
|
||||
_goToPrevious(inheritedData);
|
||||
} else if (details.localPosition.dx >
|
||||
screenWidth - edgeWidth) {
|
||||
_goToNext(inheritedData);
|
||||
}
|
||||
},
|
||||
onLongPress: () => isVideo ? null : _toggleAnimation(true),
|
||||
onLongPressUp: () => isVideo ? null : _toggleAnimation(false),
|
||||
child: MemoriesZoomWidget(
|
||||
scaleController: (controller) {
|
||||
_zoomAnimationController = controller;
|
||||
},
|
||||
zoomIn: index % 2 == 0,
|
||||
isVideo: isVideo,
|
||||
child: FileWidget(
|
||||
currentFile,
|
||||
autoPlay: false,
|
||||
tagPrefix: "memories",
|
||||
backgroundDecoration:
|
||||
const BoxDecoration(color: Colors.transparent),
|
||||
isFromMemories: true,
|
||||
playbackCallback: (isPlaying) {
|
||||
_toggleAnimation(!isPlaying);
|
||||
},
|
||||
onFinalFileLoad: ({required int memoryDuration}) {
|
||||
onFinalFileLoad(memoryDuration);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: strokeFainterDark,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
title: ValueListenableBuilder(
|
||||
valueListenable: inheritedData.indexNotifier,
|
||||
child: InkWell(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.fromLTRB(4, 8, 8, 8),
|
||||
child: Icon(Icons.close, color: Colors.white),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SafeArea(
|
||||
top: false,
|
||||
child: Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 72),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: _showTitle,
|
||||
builder: (context, value, _) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeIn,
|
||||
child: value
|
||||
? Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 12),
|
||||
child: Hero(
|
||||
tag: widget.title,
|
||||
child: Text(
|
||||
widget.title,
|
||||
style: getEnteTextTheme(context)
|
||||
.largeBold
|
||||
.copyWith(
|
||||
fontSize: 40,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
builder: (context, value, child) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
showStepProgressIndicator
|
||||
? ValueListenableBuilder<Duration>(
|
||||
valueListenable: durationNotifier,
|
||||
builder: (context, duration, _) {
|
||||
return MemoryProgressIndicator(
|
||||
totalSteps: inheritedData.memories.length,
|
||||
currentIndex: value,
|
||||
selectedColor: Colors.white,
|
||||
unselectedColor:
|
||||
Colors.white.withOpacity(0.4),
|
||||
duration: duration,
|
||||
animationController: (controller) {
|
||||
_progressAnimationController = controller;
|
||||
},
|
||||
onComplete: () {
|
||||
_goToNext(inheritedData);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
: showStepProgressIndicator
|
||||
? const SizedBox.shrink()
|
||||
: const MemoryCounter(),
|
||||
: const SizedBox.shrink(),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
child!,
|
||||
Text(
|
||||
SmartMemoriesService.getDateFormatted(
|
||||
creationTime: inheritedData
|
||||
.memories[value].file.creationTime!,
|
||||
context: context,
|
||||
),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
flexibleSpace: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.black54,
|
||||
Colors.black45,
|
||||
Colors.transparent,
|
||||
],
|
||||
stops: [0, 0.6, 1],
|
||||
),
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
),
|
||||
body: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
const _MemoryBlur(),
|
||||
ValueListenableBuilder<int>(
|
||||
valueListenable: inheritedData.indexNotifier,
|
||||
builder: (context, index, _) {
|
||||
if (index < inheritedData.memories.length - 1) {
|
||||
final nextFile = inheritedData.memories[index + 1].file;
|
||||
preloadThumbnail(nextFile);
|
||||
preloadFile(nextFile);
|
||||
}
|
||||
final currentMemory = inheritedData.memories[index];
|
||||
final isVideo =
|
||||
currentMemory.file.fileType == FileType.video;
|
||||
final currentFile = currentMemory.file;
|
||||
|
||||
return MemoriesPointerGestureListener(
|
||||
onTap: (PointerEvent event) {
|
||||
final screenWidth = MediaQuery.sizeOf(context).width;
|
||||
final goToPreviousTapAreaWidth = screenWidth * 0.20;
|
||||
if (event.localPosition.dx < goToPreviousTapAreaWidth) {
|
||||
_goToPrevious(inheritedData);
|
||||
} else {
|
||||
_goToNext(inheritedData);
|
||||
}
|
||||
},
|
||||
hasPointerNotifier: hasPointerOnScreenNotifier,
|
||||
child: MemoriesZoomWidget(
|
||||
key: ValueKey(
|
||||
currentFile.uploadedFileID ?? currentFile.localID,
|
||||
),
|
||||
scaleController: (controller) {
|
||||
_zoomAnimationController = controller;
|
||||
},
|
||||
zoomIn: index % 2 == 0,
|
||||
isVideo: isVideo,
|
||||
child: FileWidget(
|
||||
currentFile,
|
||||
autoPlay: false,
|
||||
tagPrefix: "memories",
|
||||
backgroundDecoration:
|
||||
const BoxDecoration(color: Colors.transparent),
|
||||
isFromMemories: true,
|
||||
playbackCallback: (isPlaying) {
|
||||
_toggleAnimation(pause: !isPlaying);
|
||||
},
|
||||
onFinalFileLoad: ({required int memoryDuration}) {
|
||||
onFinalFileLoad(memoryDuration);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _showTitle,
|
||||
builder: (context, value, _) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeIn,
|
||||
child: value
|
||||
? Padding(
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(16, 4, 16, 12),
|
||||
child: Hero(
|
||||
tag: widget.title,
|
||||
child: Text(
|
||||
widget.title,
|
||||
style: getEnteTextTheme(context)
|
||||
.largeBold
|
||||
.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: showStepProgressIndicator
|
||||
? const SizedBox.shrink()
|
||||
: const MemoryCounter(),
|
||||
);
|
||||
},
|
||||
),
|
||||
const BottomIcons(),
|
||||
],
|
||||
),
|
||||
const BottomGradient(),
|
||||
],
|
||||
),
|
||||
),
|
||||
const BottomGradient(),
|
||||
const BottomIcons(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -427,9 +492,9 @@ class BottomIcons extends StatelessWidget {
|
||||
color: Colors.white, //same for both themes
|
||||
),
|
||||
onPressed: () async {
|
||||
fullScreenState?._toggleAnimation(true);
|
||||
fullScreenState?._toggleAnimation(pause: true);
|
||||
await showDetailsSheet(context, currentFile);
|
||||
fullScreenState?._toggleAnimation(false);
|
||||
fullScreenState?._toggleAnimation(pause: false);
|
||||
},
|
||||
),
|
||||
];
|
||||
@@ -445,7 +510,7 @@ class BottomIcons extends StatelessWidget {
|
||||
color: Colors.white, //same for both themes
|
||||
),
|
||||
onPressed: () async {
|
||||
fullScreenState?._toggleAnimation(true);
|
||||
fullScreenState?._toggleAnimation(pause: true);
|
||||
await showSingleFileDeleteSheet(
|
||||
context,
|
||||
inheritedData
|
||||
@@ -458,7 +523,7 @@ class BottomIcons extends StatelessWidget {
|
||||
},
|
||||
},
|
||||
);
|
||||
fullScreenState?._toggleAnimation(false);
|
||||
fullScreenState?._toggleAnimation(pause: false);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
@@ -474,22 +539,19 @@ class BottomIcons extends StatelessWidget {
|
||||
color: Colors.white, //same for both themes
|
||||
),
|
||||
onPressed: () async {
|
||||
fullScreenState?._toggleAnimation(true);
|
||||
fullScreenState?._toggleAnimation(pause: true);
|
||||
await share(context, [currentFile]);
|
||||
fullScreenState?._toggleAnimation(false);
|
||||
fullScreenState?._toggleAnimation(pause: false);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: Container(
|
||||
alignment: Alignment.bottomCenter,
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: rowChildren,
|
||||
),
|
||||
return Container(
|
||||
alignment: Alignment.bottomCenter,
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: rowChildren,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -522,7 +584,7 @@ class BottomGradient extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return IgnorePointer(
|
||||
child: Container(
|
||||
height: 172,
|
||||
height: 96,
|
||||
width: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
@@ -541,8 +603,8 @@ class BottomGradient extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class MemoryBackDrop extends StatelessWidget {
|
||||
const MemoryBackDrop({super.key});
|
||||
class _MemoryBlur extends StatelessWidget {
|
||||
const _MemoryBlur();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -13,6 +13,7 @@ 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/events/reset_zoom_of_photo_view_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";
|
||||
@@ -64,6 +65,7 @@ class _ZoomableImageState extends State<ZoomableImage> {
|
||||
final _scaleStateController = PhotoViewScaleStateController();
|
||||
late final StreamSubscription<FileCaptionUpdatedEvent>
|
||||
_captionUpdatedSubscription;
|
||||
late final StreamSubscription<ResetZoomOfPhotoView> _resetZoomSubscription;
|
||||
|
||||
// This is to prevent the app from crashing when loading 200MP images
|
||||
// https://github.com/flutter/flutter/issues/110331
|
||||
@@ -92,6 +94,16 @@ class _ZoomableImageState extends State<ZoomableImage> {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_resetZoomSubscription =
|
||||
Bus.instance.on<ResetZoomOfPhotoView>().listen((event) {
|
||||
if (event.isSamePhoto(
|
||||
uploadedFileID: widget.photo.uploadedFileID,
|
||||
localID: widget.photo.localID,
|
||||
)) {
|
||||
_scaleStateController.scaleState = PhotoViewScaleState.initial;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -99,6 +111,7 @@ class _ZoomableImageState extends State<ZoomableImage> {
|
||||
_photoViewController.dispose();
|
||||
_scaleStateController.dispose();
|
||||
_captionUpdatedSubscription.cancel();
|
||||
_resetZoomSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user