From 5ee8086bc00d5c89b793bf975c3167f37cdacb77 Mon Sep 17 00:00:00 2001 From: Jeroen Meijer Date: Sun, 19 Jul 2020 13:35:54 +0200 Subject: [PATCH] feat: zoom entire home screen on push route --- lib/main.dart | 5 ++ lib/theme/theme.dart | 2 +- lib/ui/main/home/widgets/home_menu.dart | 1 - lib/ui/main/main_container.dart | 21 ++++- lib/ui/main/widgets/widgets.dart | 2 +- .../{background.dart => zoom_container.dart} | 38 +++++----- lib/ui/routes/zoom_page_route.dart | 2 - lib/utils/route_notifier.dart | 76 ++++++++++++++++--- 8 files changed, 109 insertions(+), 38 deletions(-) rename lib/ui/main/widgets/{background.dart => zoom_container.dart} (75%) diff --git a/lib/main.dart b/lib/main.dart index 80f417e..2b2e4fc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,15 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:shiritori/app/app.dart'; import 'assets/assets.dart'; void main() async { + log('Initializing binding...'); WidgetsFlutterBinding.ensureInitialized(); + log('Loading background image...'); final backgroundImage = await Images.loadBackground(); + log('Initialization done.'); runApp(AppRoot(backgroundImage: backgroundImage)); } diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index 8f5c081..8662cde 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -109,5 +109,5 @@ abstract class AppTheme { static const curveDefault = Curves.easeOutQuint; - static const durationAnimationDefault = Duration(milliseconds: 600); + static const durationAnimationDefault = Duration(milliseconds: 300); } diff --git a/lib/ui/main/home/widgets/home_menu.dart b/lib/ui/main/home/widgets/home_menu.dart index acb720a..8a20712 100644 --- a/lib/ui/main/home/widgets/home_menu.dart +++ b/lib/ui/main/home/widgets/home_menu.dart @@ -4,7 +4,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:shiritori/intl/intl.dart'; import 'package:shiritori/theme/theme.dart'; import 'package:shiritori/ui/main/quick_play/quick_play.dart'; -import 'package:shiritori/ui/routes/routes.dart'; import 'package:shiritori/ui/widgets/widgets.dart'; class HomeMenu extends StatelessWidget { diff --git a/lib/ui/main/main_container.dart b/lib/ui/main/main_container.dart index 5727218..fa8fdfb 100644 --- a/lib/ui/main/main_container.dart +++ b/lib/ui/main/main_container.dart @@ -1,4 +1,7 @@ +import 'dart:ui' as ui; + import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:shiritori/ui/main/main.dart'; import 'package:shiritori/ui/main/widgets/widgets.dart'; import 'package:shiritori/ui/routes/routes.dart'; @@ -10,12 +13,19 @@ class MainContainer extends StatelessWidget { @override Widget build(BuildContext context) { + final backgroundImage = Provider.of(context, listen: false); return Scaffold( body: Stack( fit: StackFit.expand, children: [ - Background( + ZoomContainer( + key: const Key('mainContainer_zoomContainer_background'), routeNotifier: _routeNotifier, + scaleFactor: 0.7, + child: RawImage( + image: backgroundImage, + fit: BoxFit.cover, + ), ), WillPopScope( onWillPop: () async { @@ -29,7 +39,14 @@ class MainContainer extends StatelessWidget { ], onGenerateRoute: (_) { return ZoomPageRoute( - builder: (_) => const HomeScreen(), + builder: (_) { + return ZoomContainer( + key: const Key('mainContainer_zoomContainer_homeScreen'), + routeNotifier: _routeNotifier, + scaleFactor: 1.0, + child: const HomeScreen(), + ); + }, ); }, ), diff --git a/lib/ui/main/widgets/widgets.dart b/lib/ui/main/widgets/widgets.dart index b6954a6..5ce45c4 100644 --- a/lib/ui/main/widgets/widgets.dart +++ b/lib/ui/main/widgets/widgets.dart @@ -1 +1 @@ -export 'background.dart'; +export 'zoom_container.dart'; diff --git a/lib/ui/main/widgets/background.dart b/lib/ui/main/widgets/zoom_container.dart similarity index 75% rename from lib/ui/main/widgets/background.dart rename to lib/ui/main/widgets/zoom_container.dart index 2cc3522..3a1eba2 100644 --- a/lib/ui/main/widgets/background.dart +++ b/lib/ui/main/widgets/zoom_container.dart @@ -1,31 +1,34 @@ import 'dart:developer'; -import 'dart:ui' as ui; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -// import 'package:shiritori/assets/assets.dart'; import 'package:shiritori/theme/theme.dart'; import 'package:shiritori/ui/routes/routes.dart'; import 'package:shiritori/utils/utils.dart'; -class Background extends StatefulWidget { - const Background({ +class ZoomContainer extends StatefulWidget { + const ZoomContainer({ Key key, @required this.routeNotifier, - }) : assert(routeNotifier != null), + this.scaleFactor = 1.0, + @required this.child, + }) : assert(scaleFactor != null), + assert(routeNotifier != null), super(key: key); final RouteNotifier routeNotifier; + final double scaleFactor; + final Widget child; @override - _BackgroundState createState() => _BackgroundState(); + _ZoomContainerState createState() => _ZoomContainerState(); } -class _BackgroundState extends State +class _ZoomContainerState extends State with SingleTickerProviderStateMixin, - RouteNotifierStateMixin { - static const _curve = Curves.easeOutCubic; + RouteNotifierStateMixin { + static const _zoomInCurve = Curves.easeInOut; + static const _zoomOutCurve = Curves.easeOutCubic; AnimationController _animationController; Animation _animation; @@ -47,8 +50,8 @@ class _BackgroundState extends State ); _animation = CurvedAnimation( parent: _animationController, - curve: _curve, - reverseCurve: _curve.flipped, + curve: _zoomInCurve, + reverseCurve: _zoomOutCurve.flipped, ); } @@ -107,21 +110,18 @@ class _BackgroundState extends State @override Widget build(BuildContext context) { - final backgroundImage = Provider.of(context, listen: false); return AnimatedBuilder( animation: _animation, builder: (context, child) { - final value = _animation.value; + // FIXME: Super hacky, but works ¯\_(ツ)_/¯ + final value = _animation.value % 1.0; return Transform.scale( - scale: 1.0 + (value * 0.6), + scale: 1.0 + ((value * 0.6) * widget.scaleFactor), child: child, ); }, - child: RawImage( - image: backgroundImage, - fit: BoxFit.cover, - ), + child: widget.child, ); } } diff --git a/lib/ui/routes/zoom_page_route.dart b/lib/ui/routes/zoom_page_route.dart index 0b3f3ea..f093f64 100644 --- a/lib/ui/routes/zoom_page_route.dart +++ b/lib/ui/routes/zoom_page_route.dart @@ -18,8 +18,6 @@ class ZoomPageRoute extends PageRouteBuilder { ); final opacityAnimationOut = CurvedAnimation( parent: animationOut.drive(Tween(begin: 1.0, end: 0.0)), - // TODO: Determine best looking approach. - // // Note: in practice, this curve is flipped because of how the // value is used within the transition. // diff --git a/lib/utils/route_notifier.dart b/lib/utils/route_notifier.dart index b7d81a2..2f96092 100644 --- a/lib/utils/route_notifier.dart +++ b/lib/utils/route_notifier.dart @@ -1,52 +1,70 @@ import 'package:flutter/widgets.dart'; class RouteNotifier extends NavigatorObserver { - void Function(Route route, Route previousRoute) _didPop; - void Function(Route route, Route previousRoute) _didPush; - void Function(Route route, Route previousRoute) _didRemove; - void Function({Route newRoute, Route oldRoute}) _didReplace; + final _listeners = []; @override void didPop(Route route, Route previousRoute) { if (route is R || previousRoute is R) { - _didPop?.call(route, previousRoute); + for (final listener in _listeners) { + listener.didPop?.call(route, previousRoute); + } } } @override void didPush(Route route, Route previousRoute) { if (route is R || previousRoute is R) { - _didPush?.call(route, previousRoute); + for (final listener in _listeners) { + listener.didPush?.call(route, previousRoute); + } } } @override void didRemove(Route route, Route previousRoute) { if (route is R || previousRoute is R) { - _didRemove?.call(route, previousRoute); + for (final listener in _listeners) { + listener.didRemove?.call(route, previousRoute); + } } } @override void didReplace({Route newRoute, Route oldRoute}) { if (newRoute is R || oldRoute is R) { - _didReplace?.call(newRoute: newRoute, oldRoute: oldRoute); + for (final listener in _listeners) { + listener.didReplace?.call(newRoute: newRoute, oldRoute: oldRoute); + } } } } mixin RouteNotifierStateMixin on State { + RouteNotifierListener _listener; + RouteNotifier get routeNotifier; @override void initState() { super.initState(); - routeNotifier._didPop = didPop; - routeNotifier._didPush = didPush; - routeNotifier._didRemove = didRemove; - routeNotifier._didReplace = didReplace; + _listener = RouteNotifierListener( + didPop: didPop, + didPush: didPush, + didRemove: didRemove, + didReplace: didReplace, + ); + routeNotifier._listeners.add(_listener); + } + + @override + void dispose() { + if (_listener != null) { + routeNotifier._listeners.remove(_listener); + } + super.dispose(); } void didPop(Route route, Route previousRoute); @@ -54,3 +72,37 @@ mixin RouteNotifierStateMixin void didRemove(Route route, Route previousRoute); void didReplace({Route newRoute, Route oldRoute}); } + +@immutable +class RouteNotifierListener { + const RouteNotifierListener({ + this.didPop, + this.didPush, + this.didRemove, + this.didReplace, + }); + + final void Function(Route route, Route previousRoute) didPop; + final void Function(Route route, Route previousRoute) didPush; + final void Function(Route route, Route previousRoute) didRemove; + final void Function({Route newRoute, Route oldRoute}) didReplace; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + return other is RouteNotifierListener && + other.didPop == didPop && + other.didPush == didPush && + other.didRemove == didRemove && + other.didReplace == didReplace; + } + + @override + int get hashCode => + didPop.hashCode ^ + didPush.hashCode ^ + didRemove.hashCode & didReplace.hashCode; +}