// Copyright 2014 The Flutter Authors. 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:math'; import 'package:dual_screen/dual_screen.dart'; import 'package:flutter/material.dart'; import '../constants.dart'; import '../gallery_localizations.dart'; import '../layout/adaptive.dart'; import 'home.dart'; const double homePeekDesktop = 210.0; const double homePeekMobile = 60.0; class SplashPageAnimation extends InheritedWidget { const SplashPageAnimation({ super.key, required this.isFinished, required super.child, }); final bool isFinished; static SplashPageAnimation? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } @override bool updateShouldNotify(SplashPageAnimation oldWidget) => true; } class SplashPage extends StatefulWidget { const SplashPage({ super.key, required this.child, }); final Widget child; @override State createState() => _SplashPageState(); } class _SplashPageState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late int _effect; final Random _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 Map _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: splashPageAnimationDuration, vsync: this) ..addListener(() { setState(() {}); }); } @override void dispose() { _controller.dispose(); super.dispose(); } Animation _getPanelAnimation( BuildContext context, BoxConstraints constraints, ) { final double height = constraints.biggest.height - (isDisplayDesktop(context) ? homePeekDesktop : homePeekMobile); return RelativeRectTween( begin: RelativeRect.fill, end: RelativeRect.fromLTRB(0, height, 0, 0), ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut)); } @override Widget build(BuildContext context) { return NotificationListener( onNotification: (_) { _controller.forward(); return true; }, child: SplashPageAnimation( isFinished: _controller.isDismissed, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final Animation animation = _getPanelAnimation(context, constraints); Widget frontLayer = widget.child; if (_isSplashVisible) { frontLayer = MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: _controller.reverse, onVerticalDragEnd: (DragEndDetails 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: const BorderRadius.vertical( top: Radius.circular(40), ), child: frontLayer, ), ); } if (isDisplayFoldable(context)) { return TwoPane( startPane: frontLayer, endPane: GestureDetector( onTap: () { if (_isSplashVisible) { _controller.reverse(); } else { _controller.forward(); } }, child: _SplashBackLayer( isSplashCollapsed: !_isSplashVisible, effect: _effect), ), ); } else { return Stack( children: [ _SplashBackLayer( isSplashCollapsed: !_isSplashVisible, effect: _effect, onTap: _controller.forward, ), PositionedTransition( rect: animation, child: frontLayer, ), ], ); } }, ), ), ); } } class _SplashBackLayer extends StatelessWidget { const _SplashBackLayer({ required this.isSplashCollapsed, required this.effect, this.onTap, }); final bool isSplashCollapsed; final int effect; final GestureTapCallback? onTap; @override Widget build(BuildContext context) { final String effectAsset = 'splash_effects/splash_effect_$effect.gif'; final Image flutterLogo = Image.asset( 'assets/logo/flutter_logo.png', package: 'flutter_gallery_assets', ); Widget? child; if (isSplashCollapsed) { if (isDisplayDesktop(context)) { child = Padding( padding: const EdgeInsets.only(top: 50), child: Align( alignment: Alignment.topCenter, child: MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: onTap, child: flutterLogo, ), ), ), ); } if (isDisplayFoldable(context)) { child = ColoredBox( color: Theme.of(context).colorScheme.background, child: Stack( children: [ Center( child: flutterLogo, ), Padding( padding: const EdgeInsets.only(top: 100.0), child: Center( child: Text( GalleryLocalizations.of(context)!.splashSelectDemo, ), ), ) ], ), ); } } else { child = Stack( children: [ Center( child: Image.asset( effectAsset, package: 'flutter_gallery_assets', ), ), Center(child: flutterLogo), ], ); } return ExcludeSemantics( child: Material( // This is the background color of the gifs. color: const Color(0xFF030303), child: Padding( padding: EdgeInsets.only( bottom: isDisplayDesktop(context) ? homePeekDesktop : isDisplayFoldable(context) ? 0 : homePeekMobile, ), child: child, ), ), ); } }