Skip to content

Commit

Permalink
feat: zoom entire home screen on push route
Browse files Browse the repository at this point in the history
  • Loading branch information
jeroen-meijer committed Jul 19, 2020
1 parent 0c05024 commit 5ee8086
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 38 deletions.
5 changes: 5 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -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));
}
2 changes: 1 addition & 1 deletion lib/theme/theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,5 @@ abstract class AppTheme {

static const curveDefault = Curves.easeOutQuint;

static const durationAnimationDefault = Duration(milliseconds: 600);
static const durationAnimationDefault = Duration(milliseconds: 300);
}
1 change: 0 additions & 1 deletion lib/ui/main/home/widgets/home_menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
21 changes: 19 additions & 2 deletions lib/ui/main/main_container.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -10,12 +13,19 @@ class MainContainer extends StatelessWidget {

@override
Widget build(BuildContext context) {
final backgroundImage = Provider.of<ui.Image>(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 {
Expand All @@ -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(),
);
},
);
},
),
Expand Down
2 changes: 1 addition & 1 deletion lib/ui/main/widgets/widgets.dart
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export 'background.dart';
export 'zoom_container.dart';
Original file line number Diff line number Diff line change
@@ -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<ZoomPageRoute> routeNotifier;
final double scaleFactor;
final Widget child;

@override
_BackgroundState createState() => _BackgroundState();
_ZoomContainerState createState() => _ZoomContainerState();
}

class _BackgroundState extends State<Background>
class _ZoomContainerState extends State<ZoomContainer>
with
SingleTickerProviderStateMixin,
RouteNotifierStateMixin<ZoomPageRoute, Background> {
static const _curve = Curves.easeOutCubic;
RouteNotifierStateMixin<ZoomPageRoute, ZoomContainer> {
static const _zoomInCurve = Curves.easeInOut;
static const _zoomOutCurve = Curves.easeOutCubic;

AnimationController _animationController;
Animation<double> _animation;
Expand All @@ -47,8 +50,8 @@ class _BackgroundState extends State<Background>
);
_animation = CurvedAnimation(
parent: _animationController,
curve: _curve,
reverseCurve: _curve.flipped,
curve: _zoomInCurve,
reverseCurve: _zoomOutCurve.flipped,
);
}

Expand Down Expand Up @@ -107,21 +110,18 @@ class _BackgroundState extends State<Background>

@override
Widget build(BuildContext context) {
final backgroundImage = Provider.of<ui.Image>(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,
);
}
}
2 changes: 0 additions & 2 deletions lib/ui/routes/zoom_page_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down
76 changes: 64 additions & 12 deletions lib/utils/route_notifier.dart
Original file line number Diff line number Diff line change
@@ -1,56 +1,108 @@
import 'package:flutter/widgets.dart';

class RouteNotifier<R extends ModalRoute> 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 = <RouteNotifierListener>[];

@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<R extends ModalRoute, T extends StatefulWidget>
on State<T> {
RouteNotifierListener<R> _listener;

RouteNotifier<R> get routeNotifier;

@override
void initState() {
super.initState();

routeNotifier._didPop = didPop;
routeNotifier._didPush = didPush;
routeNotifier._didRemove = didRemove;
routeNotifier._didReplace = didReplace;
_listener = RouteNotifierListener<R>(
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);
void didPush(Route route, Route previousRoute);
void didRemove(Route route, Route previousRoute);
void didReplace({Route newRoute, Route oldRoute});
}

@immutable
class RouteNotifierListener<R extends ModalRoute> {
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<R> &&
other.didPop == didPop &&
other.didPush == didPush &&
other.didRemove == didRemove &&
other.didReplace == didReplace;
}

@override
int get hashCode =>
didPop.hashCode ^
didPush.hashCode ^
didRemove.hashCode & didReplace.hashCode;
}

0 comments on commit 5ee8086

Please sign in to comment.