diff --git a/lib/models/timeline_overlay.dart b/lib/models/timeline_overlay.dart new file mode 100644 index 0000000..10b6500 --- /dev/null +++ b/lib/models/timeline_overlay.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class TimelineOverlay extends ChangeNotifier { + bool _showOverlay = true; + + bool get showOverlay => _showOverlay; + + void hideOverlay() => setShowOverlay(false); + void restoreOverlay() => setShowOverlay(true); + + void setShowOverlay(bool showOverlay) { + _showOverlay = showOverlay; + notifyListeners(); + } +} diff --git a/lib/widgets/memory_slide.dart b/lib/widgets/memory_slide.dart index 46dd8a7..1413cc0 100644 --- a/lib/widgets/memory_slide.dart +++ b/lib/widgets/memory_slide.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:share_location/controllers/memory_slide_controller.dart'; import 'package:share_location/controllers/status_controller.dart'; import 'package:share_location/enums.dart'; import 'package:share_location/foreign_types/memory.dart'; +import 'package:share_location/models/timeline_overlay.dart'; import 'package:share_location/widgets/status.dart'; import 'memory.dart'; @@ -66,35 +68,38 @@ class _MemorySlideState extends State @override Widget build(BuildContext context) { - return Status( - controller: controller, - child: MemoryView( - creationDate: widget.memory.creationDate, - location: widget.memory.location, - filename: widget.memory.filename, - loopVideo: false, - onFileDownloaded: () { - if (widget.memory.type == MemoryType.photo) { - initializeAnimation(DEFAULT_IMAGE_DURATION); - } - }, - onVideoControllerInitialized: (controller) { - if (mounted) { - initializeAnimation(controller.value.duration); + return Consumer( + builder: (context, overlayController, _) => Status( + controller: controller, + disabled: !overlayController.showOverlay, + child: MemoryView( + creationDate: widget.memory.creationDate, + location: widget.memory.location, + filename: widget.memory.filename, + loopVideo: false, + onFileDownloaded: () { + if (widget.memory.type == MemoryType.photo) { + initializeAnimation(DEFAULT_IMAGE_DURATION); + } + }, + onVideoControllerInitialized: (controller) { + if (mounted) { + initializeAnimation(controller.value.duration); - widget.controller.addListener(() { - if (!mounted) { - return; - } + widget.controller.addListener(() { + if (!mounted) { + return; + } - if (widget.controller.paused) { - controller.pause(); - } else { - controller.play(); - } - }); - } - }, + if (widget.controller.paused) { + controller.pause(); + } else { + controller.play(); + } + }); + } + }, + ), ), ); } diff --git a/lib/widgets/status.dart b/lib/widgets/status.dart index 7561363..ec4fc61 100644 --- a/lib/widgets/status.dart +++ b/lib/widgets/status.dart @@ -7,10 +7,12 @@ const BAR_HEIGHT = 4.0; class Status extends StatefulWidget { final StatusController? controller; final Widget child; + final bool disabled; const Status({ Key? key, required this.child, + this.disabled = false, this.controller, }) : super(key: key); @@ -29,6 +31,12 @@ class _StatusState extends State with TickerProviderStateMixin { if (widget.controller != null && animationController == null) { initializeAnimation(); } + + if (widget.disabled) { + animationController?.stop(); + } else { + animationController?.forward(); + } } @override @@ -75,28 +83,33 @@ class _StatusState extends State with TickerProviderStateMixin { height: BAR_HEIGHT, child: Padding( padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE), - child: (widget.controller == null) - ? ClipRRect( - borderRadius: BorderRadius.circular(HUGE_SPACE), - child: LinearProgressIndicator( - value: null, - valueColor: AlwaysStoppedAnimation( - Colors.white.withOpacity(.3)), - backgroundColor: Colors.white.withOpacity(0.1), - ), - ) - : AnimatedBuilder( - animation: animation, - builder: (_, __) => ClipRRect( + child: AnimatedOpacity( + duration: const Duration(milliseconds: 500), + curve: Curves.linearToEaseOut, + opacity: widget.disabled ? 0.0 : 1.0, + child: (widget.controller == null) + ? ClipRRect( borderRadius: BorderRadius.circular(HUGE_SPACE), child: LinearProgressIndicator( - value: animation.value, - valueColor: - const AlwaysStoppedAnimation(Colors.white), + value: null, + valueColor: AlwaysStoppedAnimation( + Colors.white.withOpacity(.3)), backgroundColor: Colors.white.withOpacity(0.1), ), + ) + : AnimatedBuilder( + animation: animation, + builder: (_, __) => ClipRRect( + borderRadius: BorderRadius.circular(HUGE_SPACE), + child: LinearProgressIndicator( + value: animation.value, + valueColor: + const AlwaysStoppedAnimation(Colors.white), + backgroundColor: Colors.white.withOpacity(0.1), + ), + ), ), - ), + ), ), ), ], diff --git a/lib/widgets/timeline_page.dart b/lib/widgets/timeline_page.dart index 04e045a..a065feb 100644 --- a/lib/widgets/timeline_page.dart +++ b/lib/widgets/timeline_page.dart @@ -1,8 +1,12 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; import 'package:share_location/constants/spacing.dart'; import 'package:share_location/controllers/memory_slide_controller.dart'; import 'package:share_location/foreign_types/memory.dart'; +import 'package:share_location/models/timeline_overlay.dart'; import 'package:share_location/widgets/memory_slide.dart'; class TimelinePage extends StatefulWidget { @@ -24,9 +28,12 @@ class TimelinePage extends StatefulWidget { } class _TimelinePageState extends State { + final timelineOverlayController = TimelineOverlay(); final pageController = PageController(); late final MemorySlideController controller; + Timer? overlayRemover; + @override void initState() { super.initState(); @@ -52,6 +59,7 @@ class _TimelinePageState extends State { @override void dispose() { controller.dispose(); + timelineOverlayController.dispose(); super.dispose(); } @@ -61,11 +69,23 @@ class _TimelinePageState extends State { return GestureDetector( onTapDown: (_) { controller.setPaused(true); + + overlayRemover = Timer( + const Duration(milliseconds: 200), + timelineOverlayController.hideOverlay, + ); }, onTapUp: (_) { + overlayRemover?.cancel(); + controller.setPaused(false); + + timelineOverlayController.restoreOverlay(); }, onTapCancel: () { + overlayRemover?.cancel(); + + timelineOverlayController.restoreOverlay(); controller.setPaused(false); }, onHorizontalDragEnd: (details) { @@ -85,30 +105,43 @@ class _TimelinePageState extends State { ); } }, - child: Stack( - fit: StackFit.expand, - children: [ - PageView.builder( - controller: pageController, - physics: const NeverScrollableScrollPhysics(), - scrollDirection: Axis.horizontal, - itemBuilder: (_, __) => MemorySlide( - key: Key(controller.index.toString()), - controller: controller, - memory: widget.memories[controller.index], + child: ChangeNotifierProvider( + create: (_) => timelineOverlayController, + child: Stack( + fit: StackFit.expand, + children: [ + 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, ), - itemCount: widget.memories.length, - ), - Padding( - padding: const EdgeInsets.only( - top: LARGE_SPACE, left: MEDIUM_SPACE, right: MEDIUM_SPACE), - child: Text( - DateFormat('dd. MMMM yyyy').format(widget.date), - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headline1, + Padding( + padding: const EdgeInsets.only( + top: LARGE_SPACE, + left: MEDIUM_SPACE, + right: MEDIUM_SPACE, + ), + child: Consumer( + builder: (context, overlayController, _) => AnimatedOpacity( + duration: const Duration(milliseconds: 500), + curve: Curves.linearToEaseOut, + opacity: overlayController.showOverlay ? 1.0 : 0.0, + child: Text( + DateFormat('dd. MMMM yyyy').format(widget.date), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headline1, + ), + ), + ), ), - ), - ], + ], + ), ), ); } diff --git a/pubspec.lock b/pubspec.lock index 064b2ef..d313794 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -310,6 +310,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" path: dependency: "direct main" description: @@ -457,6 +464,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.0" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.3" quiver: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cd9e5af..c89f180 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,7 @@ dependencies: intl: ^0.17.0 property_change_notifier: ^0.3.0 path: ^1.8.1 + provider: ^6.0.3 dev_dependencies: flutter_test: