diff --git a/lib/controllers/memory_slide_controller.dart b/lib/controllers/memory_slide_controller.dart index 811d70f..6138ba5 100644 --- a/lib/controllers/memory_slide_controller.dart +++ b/lib/controllers/memory_slide_controller.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:property_change_notifier/property_change_notifier.dart'; class MemorySlideController extends PropertyChangeNotifier { @@ -39,6 +41,11 @@ class MemorySlideController extends PropertyChangeNotifier { } } + void previous() { + _index = max(_index - 1, 0); + notifyListeners(); + } + void reset() { _completed = false; _paused = false; diff --git a/lib/foreign_types/memory.dart b/lib/foreign_types/memory.dart index 422b87a..fc677a7 100644 --- a/lib/foreign_types/memory.dart +++ b/lib/foreign_types/memory.dart @@ -1,3 +1,4 @@ +import 'package:path/path.dart'; import 'package:share_location/enums.dart'; class Memory { @@ -25,6 +26,8 @@ class Memory { ); } + String get filename => basename(location); + MemoryType get type => - location.split('.').last == 'jpg' ? MemoryType.photo : MemoryType.video; + filename.split('.').last == 'jpg' ? MemoryType.photo : MemoryType.video; } diff --git a/lib/widgets/memory.dart b/lib/widgets/memory.dart index 13a353d..24c65b4 100644 --- a/lib/widgets/memory.dart +++ b/lib/widgets/memory.dart @@ -1,4 +1,5 @@ import 'dart:typed_data'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:share_location/constants/spacing.dart'; @@ -20,6 +21,7 @@ enum MemoryFetchStatus { class MemoryView extends StatefulWidget { final String location; final DateTime creationDate; + final String filename; final bool loopVideo; final void Function(VideoPlayerController)? onVideoControllerInitialized; final VoidCallback? onFileDownloaded; @@ -28,6 +30,7 @@ class MemoryView extends StatefulWidget { Key? key, required this.location, required this.creationDate, + required this.filename, this.loopVideo = false, this.onVideoControllerInitialized, this.onFileDownloaded, @@ -131,11 +134,30 @@ class _MemoryViewState extends AuthRequiredState { } if (status == MemoryFetchStatus.done) { - return RawMemoryDisplay( - data: data!, - type: type!, - loopVideo: widget.loopVideo, - onVideoControllerInitialized: widget.onVideoControllerInitialized, + return Stack( + fit: StackFit.expand, + alignment: Alignment.center, + children: [ + if (type == MemoryType.photo) + ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: RawMemoryDisplay( + filename: widget.filename, + data: data!, + type: type!, + loopVideo: widget.loopVideo, + fit: BoxFit.cover, + ), + ), + RawMemoryDisplay( + filename: widget.filename, + data: data!, + type: type!, + fit: BoxFit.contain, + loopVideo: widget.loopVideo, + onVideoControllerInitialized: widget.onVideoControllerInitialized, + ), + ], ); } diff --git a/lib/widgets/memory_slide.dart b/lib/widgets/memory_slide.dart index b364e63..46dd8a7 100644 --- a/lib/widgets/memory_slide.dart +++ b/lib/widgets/memory_slide.dart @@ -71,6 +71,7 @@ class _MemorySlideState extends State child: MemoryView( creationDate: widget.memory.creationDate, location: widget.memory.location, + filename: widget.memory.filename, loopVideo: false, onFileDownloaded: () { if (widget.memory.type == MemoryType.photo) { diff --git a/lib/widgets/raw_memory_display.dart b/lib/widgets/raw_memory_display.dart index 22f4aba..85d5655 100644 --- a/lib/widgets/raw_memory_display.dart +++ b/lib/widgets/raw_memory_display.dart @@ -12,12 +12,14 @@ class RawMemoryDisplay extends StatefulWidget { final bool loopVideo; final String? filename; final void Function(VideoPlayerController)? onVideoControllerInitialized; + final BoxFit? fit; const RawMemoryDisplay({ Key? key, required this.data, required this.type, this.loopVideo = false, + this.fit = BoxFit.cover, this.filename, this.onVideoControllerInitialized, }) : super(key: key); @@ -41,14 +43,15 @@ class _RawMemoryDisplayState extends State { Future createTempVideo() async { final tempDirectory = await getTemporaryDirectory(); final path = '${tempDirectory.path}/${widget.filename ?? 'video.mp4'}'; + final file = File(path); - if (widget.filename != null) { - // File already exists, so just return the path - return File(path); + if (await file.exists()) { + // File already exists, so just return it + return file; } // File needs to be created - final file = await File(path).create(); + await file.create(); await file.writeAsBytes(widget.data); return file; @@ -82,17 +85,27 @@ class _RawMemoryDisplayState extends State { case MemoryType.photo: return Image.memory( widget.data, - fit: BoxFit.cover, + fit: widget.fit, ); case MemoryType.video: if (videoController == null) { return const SizedBox(); } - return AspectRatio( - aspectRatio: videoController!.value.aspectRatio, - child: VideoPlayer(videoController!), - ); + switch (widget.fit) { + case BoxFit.contain: + return Align( + child: AspectRatio( + aspectRatio: videoController!.value.aspectRatio, + child: VideoPlayer(videoController!), + ), + ); + default: + return AspectRatio( + aspectRatio: videoController!.value.aspectRatio, + child: VideoPlayer(videoController!), + ); + } default: throw Exception('Unknown memory type: ${widget.type}'); } diff --git a/lib/widgets/memory_page.dart b/lib/widgets/timeline_page.dart similarity index 57% rename from lib/widgets/memory_page.dart rename to lib/widgets/timeline_page.dart index 7bf212d..04e045a 100644 --- a/lib/widgets/memory_page.dart +++ b/lib/widgets/timeline_page.dart @@ -5,13 +5,13 @@ import 'package:share_location/controllers/memory_slide_controller.dart'; import 'package:share_location/foreign_types/memory.dart'; import 'package:share_location/widgets/memory_slide.dart'; -class MemoryPage extends StatefulWidget { +class TimelinePage extends StatefulWidget { final DateTime date; final List memories; final VoidCallback onPreviousTimeline; final VoidCallback onNextTimeline; - const MemoryPage({ + const TimelinePage({ Key? key, required this.date, required this.memories, @@ -20,10 +20,11 @@ class MemoryPage extends StatefulWidget { }) : super(key: key); @override - State createState() => _MemoryPageState(); + State createState() => _TimelinePageState(); } -class _MemoryPageState extends State { +class _TimelinePageState extends State { + final pageController = PageController(); late final MemorySlideController controller; @override @@ -34,8 +35,11 @@ class _MemoryPageState extends State { controller.addListener(() { if (controller.done) { controller.next(); - // Force UI update - setState(() {}); + + pageController.nextPage( + duration: const Duration(milliseconds: 200), + curve: Curves.linearToEaseOut, + ); } }, ['done']); controller.addListener(() { @@ -61,13 +65,39 @@ class _MemoryPageState extends State { onTapUp: (_) { controller.setPaused(false); }, + onTapCancel: () { + controller.setPaused(false); + }, + onHorizontalDragEnd: (details) { + if (details.primaryVelocity! < 0) { + controller.next(); + + pageController.nextPage( + duration: const Duration(milliseconds: 200), + curve: Curves.linearToEaseOut, + ); + } else if (details.primaryVelocity! > 0) { + controller.previous(); + + pageController.previousPage( + duration: const Duration(milliseconds: 200), + curve: Curves.linearToEaseOut, + ); + } + }, child: Stack( fit: StackFit.expand, children: [ - MemorySlide( - key: Key(controller.index.toString()), - controller: controller, - memory: widget.memories[controller.index], + PageView.builder( + controller: pageController, + physics: const NeverScrollableScrollPhysics(), + scrollDirection: Axis.horizontal, + itemBuilder: (_, __) => MemorySlide( + key: Key(controller.index.toString()), + controller: controller, + memory: widget.memories[controller.index], + ), + itemCount: widget.memories.length, ), Padding( padding: const EdgeInsets.only( diff --git a/lib/widgets/timeline_scroll.dart b/lib/widgets/timeline_scroll.dart index a108094..4016ae9 100644 --- a/lib/widgets/timeline_scroll.dart +++ b/lib/widgets/timeline_scroll.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:share_location/foreign_types/memory.dart'; import 'package:share_location/utils/loadable.dart'; -import 'package:share_location/widgets/memory_page.dart'; +import 'package:share_location/widgets/timeline_page.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; final supabase = Supabase.instance.client; @@ -71,7 +71,7 @@ class _TimelineScrollState extends State with Loadable { controller: pageController, scrollDirection: Axis.vertical, itemCount: timeline.length, - itemBuilder: (_, index) => MemoryPage( + itemBuilder: (_, index) => TimelinePage( date: DateTime.parse(timeline.keys.toList()[index]), memories: timeline.values.toList()[index], onNextTimeline: () { diff --git a/pubspec.lock b/pubspec.lock index b6f1a63..064b2ef 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -311,7 +311,7 @@ packages: source: hosted version: "1.0.2" path: - dependency: transitive + dependency: "direct main" description: name: path url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 776a427..cd9e5af 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,7 @@ dependencies: path_provider: ^2.0.11 intl: ^0.17.0 property_change_notifier: ^0.3.0 + path: ^1.8.1 dev_dependencies: flutter_test: