blob: e4e913cc6b2595ae7a248145ce8a33eb3c3a5099 [file] [log] [blame]
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:gallery/constants.dart';
import 'package:gallery/layout/adaptive.dart';
import 'package:gallery/pages/home.dart';
const homePeekDesktop = 210.0;
const homePeekMobile = 60.0;
class SplashPageAnimation extends InheritedWidget {
const SplashPageAnimation({
Key key,
@required this.isFinished,
@required Widget child,
}) : assert(child != null),
super(key: key, child: child);
final bool isFinished;
static SplashPageAnimation of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
@override
bool updateShouldNotify(SplashPageAnimation old) => true;
}
class SplashPage extends StatefulWidget {
const SplashPage({
Key key,
this.isAnimated = true,
@required this.child,
}) : super(key: key);
final bool isAnimated;
final Widget child;
@override
_SplashPageState createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Timer _launchTimer;
int _effect;
final _random = Random();
// A map of the effect index to its duration. This duration is used to
// determine how long to display the splash animation at launch.
//
// If a new effect is added, this map should be updated.
final _effectDurations = {
1: 5,
2: 4,
3: 4,
4: 5,
5: 5,
6: 4,
7: 4,
8: 4,
9: 3,
10: 6,
};
bool get _isSplashVisible {
return _controller.status == AnimationStatus.completed ||
_controller.status == AnimationStatus.forward;
}
@override
void initState() {
super.initState();
// If the number of included effects changes, this number should be changed.
_effect = _random.nextInt(_effectDurations.length) + 1;
_controller = AnimationController(
duration: Duration(
milliseconds: splashPageAnimationDurationInMilliseconds,
),
value: 1,
vsync: this)
..addListener(() {
this.setState(() {});
});
if (widget.isAnimated) {
_launchTimer = Timer(
Duration(seconds: _effectDurations[_effect]),
() {
_controller.fling(velocity: -1);
},
);
} else {
_controller.value = 0;
}
}
@override
void dispose() {
_controller.dispose();
_launchTimer?.cancel();
_launchTimer = null;
super.dispose();
}
Animation<RelativeRect> _getPanelAnimation(
BuildContext context,
BoxConstraints constraints,
) {
final double height = constraints.biggest.height -
(isDisplayDesktop(context) ? homePeekDesktop : homePeekMobile);
return RelativeRectTween(
begin: const RelativeRect.fromLTRB(0, 0, 0, 0),
end: RelativeRect.fromLTRB(0, height, 0, 0),
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
}
@override
Widget build(BuildContext context) {
return NotificationListener<ToggleSplashNotification>(
onNotification: (_) {
_controller.forward();
return true;
},
child: SplashPageAnimation(
isFinished: _controller.status == AnimationStatus.dismissed,
child: LayoutBuilder(
builder: (context, constraints) {
final Animation<RelativeRect> animation =
_getPanelAnimation(context, constraints);
Widget frontLayer = widget.child;
if (_isSplashVisible) {
frontLayer = GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
_controller.reverse();
},
onVerticalDragEnd: (details) {
if (details.velocity.pixelsPerSecond.dy < -200) {
_controller.reverse();
}
},
child: IgnorePointer(child: frontLayer),
);
}
if (isDisplayDesktop(context)) {
frontLayer = Padding(
padding: const EdgeInsets.only(top: 136),
child: ClipRRect(
borderRadius: BorderRadius.vertical(
top: Radius.circular(40),
),
child: frontLayer,
),
);
}
return Stack(
children: [
_SplashBackLayer(
isSplashCollapsed: !_isSplashVisible,
effect: _effect,
onTap: () {
_controller.forward();
},
),
PositionedTransition(
rect: animation,
child: frontLayer,
),
],
);
},
),
),
);
}
}
class _SplashBackLayer extends StatelessWidget {
_SplashBackLayer({
Key key,
@required this.isSplashCollapsed,
this.effect,
this.onTap,
}) : super(key: key);
final bool isSplashCollapsed;
final int effect;
final GestureTapCallback onTap;
@override
Widget build(BuildContext context) {
var effectAsset = 'assets/splash_effects/splash_effect_$effect.gif';
final flutterLogo = Image.asset('assets/logo/flutter_logo.png');
Widget child;
if (isSplashCollapsed) {
child = isDisplayDesktop(context)
? Padding(
padding: const EdgeInsets.only(top: 50),
child: Align(
alignment: Alignment.topCenter,
child: GestureDetector(
onTap: onTap,
child: flutterLogo,
),
),
)
: null;
} else {
child = Stack(
children: [
Center(child: Image.asset(effectAsset)),
Center(child: flutterLogo),
],
);
}
return ExcludeSemantics(
child: Container(
color: Color(0xFF030303), // This is the background color of the gifs.
padding: EdgeInsets.only(
bottom: isDisplayDesktop(context) ? homePeekDesktop : homePeekMobile,
),
child: child,
),
);
}
}