diff --git a/packages/flame_splash_screen/.metadata b/packages/flame_splash_screen/.metadata new file mode 100644 index 00000000000..162b83e084b --- /dev/null +++ b/packages/flame_splash_screen/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 68841bdab9840b5d72499f9cdd80b69894046ed0 + channel: master + +project_type: package diff --git a/packages/flame_splash_screen/CHANGELOG.md b/packages/flame_splash_screen/CHANGELOG.md new file mode 100644 index 00000000000..990027d8268 --- /dev/null +++ b/packages/flame_splash_screen/CHANGELOG.md @@ -0,0 +1,8 @@ +## 0.1.0 + +- Added non nullability support +- Update to new logo + +## 0.0.1 + +- Initial implementation with theme, controller and of course, the splash screen widget. diff --git a/packages/flame_splash_screen/LICENSE b/packages/flame_splash_screen/LICENSE new file mode 100644 index 00000000000..3897c4d092d --- /dev/null +++ b/packages/flame_splash_screen/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 Blue Fire + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/packages/flame_splash_screen/README.md b/packages/flame_splash_screen/README.md new file mode 100644 index 00000000000..1a839a2deae --- /dev/null +++ b/packages/flame_splash_screen/README.md @@ -0,0 +1,147 @@ + +

+ + flame + +

+ +Style your [Flame](https://github.com/flame-engine/flame) game with a beautiful splash screen. + +This package includes a `FlameSplashScreen` widget. + +

+ + + + +

+ +

+ +

+ + +## Install + +Add `flame_splash_screen` as a dependency to +[your pubspec.yaml file](https://pub.dev/packages/flame_splash_screen/install). + +Import the widget: + +```dart +import 'package:flame_splash_screen/flame_splash_screen.dart'; +``` + + +## Usage + +The splash screen is a widget that can be used to show the splash screen. + + +### Simple usage + +There is just two required params: + +- `onFinish`, a callback that is executed when all animations from the splash screen is over. +- `theme`, than can be either `FlameSplashTheme.dark` or `FlameSplashTheme.white`. + +```dart +FlameSplashScreen( + theme: FlameSplashTheme.dark, + onFinish: (BuildContext context) => Navigator.pushNamed(context, '/your-game-initial-screen'), +) +``` + + +#### Adding your own content + +You can pass your own logo (or/and anything else) to be shown before or after the Flame's logo. + +```dart +FlameSplashScreen( + theme: FlameSplashTheme.dark, + showBefore: (BuildContext context) { + return Text("To be shown before flame animation"); + }, + onFinish: (BuildContext context) => Navigator.pushNamed(context, '/your-game-initial-screen'), +) +``` + +```dart +FlameSplashScreen( + theme: FlameSplashTheme.dark, + showAfter: (BuildContext context) { + return Text("To be shown after flame animation"); + }, + onFinish: (BuildContext context) => Navigator.pushNamed(context, '/your-game-initial-screen'), +) +``` + +Remember: you can also specify both `showBefore` and `showAfter` at the same time. + + +#### Changing theme + +By default the splash screen has a dark background. You can change it by specifying the `white` +theme. + +Aside from `FlameSplashTheme.dark`, you can pass `FlameSplashTheme.white` for a white background. + +```dart +FlameSplashScreen( + theme: FlameSplashTheme.white, + onFinish: (BuildContext context) => Navigator.pushNamed(context, '/your-game-initial-screen'), +) +``` + +You can create your own theme passing a custom logo builder (changing flames logo for another one) +and a background decoration + + +### Usage with controller + +Controller enables `FlameSplashScreen` to be customized regarding animation duration and when it +starts. + +There is duration params and `autoStart` (which is true by default). + +To use it, make the controller lives as much as a widget state: + +```dart +class SplashScreenGameState extends State { + FlameSplashController controller; + @override + void initState() { + super.initState(); + controller = FlameSplashController( + fadeInDuration: Duration(seconds: 1), + fadeOutDuration: Duration(milliseconds: 250), + waitDuration: Duration(seconds: 2), + autoStart: false, + ); + } + + @override + void dispose() { + controller.dispose(); // dispose it when necessary + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: FlameSplashScreen( + showBefore: (BuildContext context) { + return Text("Before the logo"); + }, + showAfter: (BuildContext context) { + return Text("After the logo"); + }, + theme: FlameSplashTheme.white, + onFinish: (context) => Navigator.pushNamed(context, '/the-game-initial-screen'), + controller: controller, + ), + ); + } +} +``` diff --git a/packages/flame_splash_screen/analysis_options.yaml b/packages/flame_splash_screen/analysis_options.yaml new file mode 100644 index 00000000000..ba5631f3b8a --- /dev/null +++ b/packages/flame_splash_screen/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flame_lint/analysis_options.yaml \ No newline at end of file diff --git a/packages/flame_splash_screen/assets/layer1.png b/packages/flame_splash_screen/assets/layer1.png new file mode 100644 index 00000000000..25e9a072f5b Binary files /dev/null and b/packages/flame_splash_screen/assets/layer1.png differ diff --git a/packages/flame_splash_screen/assets/layer2.png b/packages/flame_splash_screen/assets/layer2.png new file mode 100644 index 00000000000..29f4eb134b5 Binary files /dev/null and b/packages/flame_splash_screen/assets/layer2.png differ diff --git a/packages/flame_splash_screen/assets/layer3.png b/packages/flame_splash_screen/assets/layer3.png new file mode 100644 index 00000000000..777e240ee86 Binary files /dev/null and b/packages/flame_splash_screen/assets/layer3.png differ diff --git a/packages/flame_splash_screen/demo.gif b/packages/flame_splash_screen/demo.gif new file mode 100644 index 00000000000..81c4ef7668e Binary files /dev/null and b/packages/flame_splash_screen/demo.gif differ diff --git a/packages/flame_splash_screen/example/.metadata b/packages/flame_splash_screen/example/.metadata new file mode 100644 index 00000000000..5caddb32abe --- /dev/null +++ b/packages/flame_splash_screen/example/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "12fccda598477eddd19f93040a1dba24f915b9be" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 12fccda598477eddd19f93040a1dba24f915b9be + base_revision: 12fccda598477eddd19f93040a1dba24f915b9be + - platform: linux + create_revision: 12fccda598477eddd19f93040a1dba24f915b9be + base_revision: 12fccda598477eddd19f93040a1dba24f915b9be + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/flame_splash_screen/example/README.md b/packages/flame_splash_screen/example/README.md new file mode 100644 index 00000000000..1b3bb74fdf1 --- /dev/null +++ b/packages/flame_splash_screen/example/README.md @@ -0,0 +1,3 @@ +# Splash screen example app + +Example of usage diff --git a/packages/flame_splash_screen/example/analysis_options.yaml b/packages/flame_splash_screen/example/analysis_options.yaml new file mode 100644 index 00000000000..ba5631f3b8a --- /dev/null +++ b/packages/flame_splash_screen/example/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flame_lint/analysis_options.yaml \ No newline at end of file diff --git a/packages/flame_splash_screen/example/lib/main.dart b/packages/flame_splash_screen/example/lib/main.dart new file mode 100644 index 00000000000..c846560af2b --- /dev/null +++ b/packages/flame_splash_screen/example/lib/main.dart @@ -0,0 +1,68 @@ +import 'package:flame_splash_screen/flame_splash_screen.dart'; +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: const SplashScreenGame(), + theme: ThemeData.dark(), + debugShowCheckedModeBanner: false, + ); + } +} + +class OtherScreen extends StatelessWidget { + const OtherScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: ElevatedButton( + child: const Text('Come again'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const SplashScreenGame()), + ); + }, + ), + ), + ); + } +} + +class SplashScreenGame extends StatefulWidget { + const SplashScreenGame({super.key}); + + @override + SplashScreenGameState createState() => SplashScreenGameState(); +} + +class SplashScreenGameState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: FlameSplashScreen( + showBefore: (BuildContext context) { + return const Text('Before logo'); + }, + showAfter: (BuildContext context) { + return const Text('After logo'); + }, + theme: FlameSplashTheme.dark, + onFinish: (context) => Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const OtherScreen()), + ), + ), + ); + } +} diff --git a/packages/flame_splash_screen/example/pubspec.yaml b/packages/flame_splash_screen/example/pubspec.yaml new file mode 100644 index 00000000000..22cadbb3543 --- /dev/null +++ b/packages/flame_splash_screen/example/pubspec.yaml @@ -0,0 +1,20 @@ +name: flame_splash_screen_example +description: Flame Splash Screen Example +publish_to: none + +version: 1.0.0+1 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.13.0" + +dependencies: + flame_splash_screen: ^0.1.0 + flutter: + sdk: flutter + +dev_dependencies: + flame_lint: ^1.1.1 + flutter_test: + sdk: flutter + diff --git a/packages/flame_splash_screen/lib/flame_splash_screen.dart b/packages/flame_splash_screen/lib/flame_splash_screen.dart new file mode 100644 index 00000000000..efc690a8031 --- /dev/null +++ b/packages/flame_splash_screen/lib/flame_splash_screen.dart @@ -0,0 +1,3 @@ +export 'src/controller.dart'; +export 'src/splash.dart'; +export 'src/theme.dart'; diff --git a/packages/flame_splash_screen/lib/src/controller.dart b/packages/flame_splash_screen/lib/src/controller.dart new file mode 100644 index 00000000000..4999928b23a --- /dev/null +++ b/packages/flame_splash_screen/lib/src/controller.dart @@ -0,0 +1,120 @@ +import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart'; + +/// Controller enables you to start the animation whenever you want with +/// [autoStart] option and customize animation duration as well. +class FlameSplashController { + FlameSplashController({ + Duration fadeInDuration = const Duration(milliseconds: 750), + Duration waitDuration = const Duration(seconds: 2), + Duration fadeOutDuration = const Duration(milliseconds: 450), + this.autoStart = true, + }) : stepController = FlameSplashControllerStep(0), + durations = FlameSplashDurations( + fadeInDuration, + waitDuration, + fadeOutDuration, + ); + + /// Defines if you want to start the animations right after widget mount. + final bool autoStart; + + @internal + final FlameSplashDurations durations; + + @internal + final FlameSplashControllerStep stepController; + + FlameSplashControllerState _state = FlameSplashControllerState.idle; + bool _hasSetup = false; + int _stepsAmount = 0; + late void Function() _onFinish; + + /// Displays the actual state of the controller regarding the animation. + FlameSplashControllerState get state => _state; + @internal + set state(FlameSplashControllerState newState) => _state = newState; + + /// Method used to start the animation, do not call if you set [autoStart] to + /// true. + void start() { + assert( + _hasSetup, + 'This controller is not being used by any FlameSplashScreen widget. ' + 'Start it only after widget mount.', + ); + assert( + _state != FlameSplashControllerState.started, + 'This controller has been already started, verify if autoStart has been ' + 'specified', + ); + + _state = FlameSplashControllerState.started; + _tickStep(0); + } + + /// Called by the [start] method; this is only exposed for testing purposes. + @internal + void setup( + int steps, + void Function() onFinish, + ) { + _onFinish = onFinish; + _stepsAmount = steps; + _hasSetup = true; + if (autoStart) { + start(); + } + } + + Future _tickStep(int index) async { + stepController.value = index; + await Future.delayed(durations.total); + final finished = index >= _stepsAmount - 1; + if (finished) { + _state = FlameSplashControllerState.finished; + _onFinish(); + return; + } + _tickStep(index + 1); + } + + /// Properly disposes of this controller. + /// Must be called after no longer used. + void dispose() { + stepController.dispose(); + } +} + +/// Represents the state of the splash screen. +enum FlameSplashControllerState { + /// Not started yet, but ready to start. + /// Note that if autoStart is set, this stage will be skipped. + idle, + + /// Started and currently running through the steps. + started, + + /// Finished to run through all steps. + finished, +} + +class FlameSplashControllerStep extends ValueNotifier { + FlameSplashControllerStep(super.value); +} + +class FlameSplashDurations { + const FlameSplashDurations( + this.fadeInDuration, + this.waitDuration, + this.fadeOutDuration, + ); + + final Duration fadeInDuration; + final Duration waitDuration; + final Duration fadeOutDuration; + + Duration get total { + return fadeInDuration + fadeOutDuration + waitDuration; + } +} diff --git a/packages/flame_splash_screen/lib/src/splash.dart b/packages/flame_splash_screen/lib/src/splash.dart new file mode 100644 index 00000000000..b94d86451d7 --- /dev/null +++ b/packages/flame_splash_screen/lib/src/splash.dart @@ -0,0 +1,187 @@ +import 'package:flame_splash_screen/flame_splash_screen.dart'; +import 'package:flutter/widgets.dart'; + +/// A stateful widget to show a splash screen animation for flame games +class FlameSplashScreen extends StatefulWidget { + /// Creates a [FlameSplashScreen]. + const FlameSplashScreen({ + required this.onFinish, + required this.theme, + this.showBefore, + this.showAfter, + this.controller, + super.key, + }); + + /// Gives extra controller over the splash animation. + final FlameSplashController? controller; + + /// Enables to set a different theme other than the default. + final FlameSplashTheme theme; + + /// The only required option, callback to be invoked when animation finished + final ValueChanged onFinish; + + /// Adds an extra step to the animation showing a widget, can be other logo. + /// + /// Shown before flame logo. + final WidgetBuilder? showBefore; + + /// Adds an extra step to the animation showing a widget, can be other logo. + /// + /// Shown after flame logo. + final WidgetBuilder? showAfter; + + @override + FlameSplashScreenState createState() => FlameSplashScreenState(); +} + +/// The state for the [FlameSplashScreen] that holds the [controller] for +/// controlling the animation durations and whether it should automatically +/// start. Also contains the list of steps that it should animate through. +class FlameSplashScreenState extends State { + /// The [controller] for controlling the animation durations and whether it + /// should automatically start. + late FlameSplashController controller; + + /// The list of steps that it should animate through. + late List steps; + bool _externallyControlled = false; + + @override + void initState() { + super.initState(); + + _externallyControlled = widget.controller != null; + controller = widget.controller ?? FlameSplashController(); + + _computeSteps(); + controller.setup(steps.length, () => widget.onFinish(context)); + } + + void _computeSteps() { + setState(() { + steps = [ + if (widget.showBefore != null) widget.showBefore!, + widget.theme.logoBuilder, + if (widget.showAfter != null) widget.showAfter!, + ]; + }); + } + + @override + void didUpdateWidget(FlameSplashScreen oldWidget) { + super.didUpdateWidget(oldWidget); + final hasStepsChanged = widget.showBefore != oldWidget.showBefore || + widget.showAfter != oldWidget.showAfter || + widget.theme.logoBuilder != oldWidget.theme.logoBuilder; + if (hasStepsChanged && + controller.state != FlameSplashControllerState.started) { + _computeSteps(); + controller.start(); + } + } + + @override + void dispose() { + if (!_externallyControlled) { + controller.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + constraints: widget.theme.constraints, + decoration: widget.theme.backgroundDecoration, + child: Center( + child: ValueListenableBuilder( + valueListenable: controller.stepController, + builder: (context, currentStep, _) { + return _SplashScreenStep( + builder: steps[currentStep], + durations: controller.durations, + key: ObjectKey(currentStep), + ); + }, + ), + ), + ); + } +} + +class _SplashScreenStep extends StatefulWidget { + const _SplashScreenStep({ + required this.builder, + required this.durations, + super.key, + }); + + final WidgetBuilder builder; + final FlameSplashDurations durations; + + @override + __SplashScreenStepState createState() => __SplashScreenStepState(); +} + +class __SplashScreenStepState extends State<_SplashScreenStep> + with TickerProviderStateMixin { + late AnimationController controller; + late Animation opacityAnimation; + double opacity = 0.0; + + @override + void initState() { + super.initState(); + controller = AnimationController( + vsync: this, + )..addListener(handleAnimation); + opacityAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(controller); + startAnimation(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + void handleAnimation() { + setState(() { + opacity = opacityAnimation.value; + }); + } + + Future startAnimation() async { + // fade in + controller + ..value = 0.0 + ..duration = widget.durations.fadeInDuration; + await controller.forward(); + await Future.delayed(widget.durations.waitDuration); + controller + ..value = 1.0 + ..duration = widget.durations.fadeOutDuration + ..reverse(); + } + + @override + void didUpdateWidget(_SplashScreenStep oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.key != widget.key) { + startAnimation(); + } + } + + @override + Widget build(BuildContext context) { + return Opacity( + opacity: opacity, + child: widget.builder(context), + ); + } +} diff --git a/packages/flame_splash_screen/lib/src/theme.dart b/packages/flame_splash_screen/lib/src/theme.dart new file mode 100644 index 00000000000..c443d72432f --- /dev/null +++ b/packages/flame_splash_screen/lib/src/theme.dart @@ -0,0 +1,138 @@ +import 'package:flutter/widgets.dart'; + +/// This widget builds up the Flame logo composed of 3 layers, +/// that are rendered via separate PNG files under the assets directory. +class AnimatedLogo extends AnimatedWidget { + /// Create this widget providing the animation parameter to control + /// the opacity of the flame. + const AnimatedLogo({ + required Animation animation, + super.key, + }) : super(listenable: animation); + + @override + Widget build(BuildContext context) { + final animation = listenable as Animation; + return Stack( + fit: StackFit.expand, + alignment: Alignment.center, + children: [ + Image.asset( + 'assets/layer1.png', + package: 'flame_splash_screen', + ), + Opacity( + opacity: animation.value, + child: Image.asset( + 'assets/layer2.png', + package: 'flame_splash_screen', + ), + ), + Image.asset( + 'assets/layer3.png', + package: 'flame_splash_screen', + ), + ], + ); + } +} + +/// Creates and controls an [AnimatedLogo], making sure to provide the required +/// animation and to properly dispose of itself after usage. +class LogoComposite extends StatefulWidget { + /// Creates a [LogoComposite]. + const LogoComposite({super.key}); + + @override + LogoCompositeState createState() => LogoCompositeState(); +} + +/// The state holding the state of the animated logo and its controller. +class LogoCompositeState extends State + with SingleTickerProviderStateMixin { + /// The state of the animated logo. + late Animation animation; + + /// The controller for the animated logo. + late AnimationController controller; + + @override + void initState() { + super.initState(); + controller = AnimationController( + duration: const Duration(milliseconds: 500), + vsync: this, + ); + animation = Tween(begin: 0.0, end: 1.0).animate(controller); + + controller.addStatusListener((status) { + if (status == AnimationStatus.completed) { + controller.reverse(); + } else if (status == AnimationStatus.dismissed) { + controller.forward(); + } + }); + + controller.forward(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => AnimatedLogo(animation: animation); +} + +Widget _logoBuilder(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + return FractionalTranslation( + translation: const Offset(0, -0.25), + child: ConstrainedBox( + constraints: BoxConstraints.loose(const Size(300, 300)), + child: const LogoComposite(), + ), + ); + }, + ); +} + +/// Wraps the splash screen layout options. +/// There is two predefined themes [FlameSplashTheme.dark] and +/// [FlameSplashTheme.white]. +class FlameSplashTheme { + /// Creates a customized theme. [logoBuilder] returns the widget that will be + /// rendered in place of main step of the animation. + const FlameSplashTheme({ + required this.backgroundDecoration, + required this.logoBuilder, + this.constraints = const BoxConstraints.expand(), + }); + + /// Decoration to be applied to the widget underneath the Flame logo. + /// It can be used to set the background color, among other parameters. + final BoxDecoration backgroundDecoration; + + /// A lambda to build the widget representing the logo itself. + /// By default this will be wired to use the [LogoComposite] widget. + final WidgetBuilder logoBuilder; + + /// Th constraints of the outside box, defaults to [BoxConstraints.expand]. + final BoxConstraints constraints; + + /// One of the two default themes provided; this is optimal of light mode + /// apps. + static FlameSplashTheme white = const FlameSplashTheme( + backgroundDecoration: BoxDecoration(color: Color(0xFFFFFFFF)), + logoBuilder: _logoBuilder, + ); + + /// One of the two default themes provided; this is optimal of dark mode apps. + static FlameSplashTheme dark = const FlameSplashTheme( + backgroundDecoration: BoxDecoration(color: Color(0xFF000000)), + logoBuilder: _logoBuilder, + ); +} diff --git a/packages/flame_splash_screen/pubspec.yaml b/packages/flame_splash_screen/pubspec.yaml new file mode 100644 index 00000000000..c939bffdd08 --- /dev/null +++ b/packages/flame_splash_screen/pubspec.yaml @@ -0,0 +1,27 @@ +name: flame_splash_screen +description: Style your flame game with a beautiful splash screen with logo reveal. Simple to use but still customizable. +version: 0.1.0 +homepage: https://github.com/flame-engine/flame/tree/main/packages/flame_splash_screen +funding: + - https://opencollective.com/blue-fire + - https://github.com/sponsors/bluefireteam + - https://patreon.com/bluefireoss + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.13.0" + +dependencies: + flutter: + sdk: flutter + meta: ^1.9.1 + +dev_dependencies: + flame_lint: ^1.1.1 + flutter_test: + sdk: flutter + mockito: ^5.4.2 + +flutter: + assets: + - assets/ diff --git a/packages/flame_splash_screen/test/controller_test.dart b/packages/flame_splash_screen/test/controller_test.dart new file mode 100644 index 00000000000..4aa158e1b4d --- /dev/null +++ b/packages/flame_splash_screen/test/controller_test.dart @@ -0,0 +1,70 @@ +import 'package:flame_splash_screen/flame_splash_screen.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +class OnFinishContainer { + void onFinish() {} +} + +class MockOnFinish extends Mock implements OnFinishContainer {} + +void main() { + group('Without autostart', () { + late FlameSplashController controller; + setUp(() { + controller = FlameSplashController( + fadeInDuration: const Duration(milliseconds: 500), + fadeOutDuration: const Duration(milliseconds: 500), + waitDuration: const Duration(milliseconds: 500), + autoStart: false, + ); + }); + test('Do not let it start before setup', () async { + expect(() => controller.start(), throwsAssertionError); + controller.setup(1, () {}); + controller.start(); + }); + test('Idle after setup', () { + controller.setup(1, () {}); + expect(controller.state, FlameSplashControllerState.idle); + }); + test('Started after .start()', () { + controller.setup(1, () {}); + expect(controller.state, FlameSplashControllerState.idle); + controller.start(); + expect(controller.state, FlameSplashControllerState.started); + }); + test('Calls onFinish after steps', () async { + final onFinishContainer = MockOnFinish(); + controller.setup(3, onFinishContainer.onFinish); + controller.start(); + await untilCalled(onFinishContainer.onFinish()); + expect(controller.state, FlameSplashControllerState.finished); + }); + }); + group('With autostart', () { + late FlameSplashController controller; + setUp(() { + controller = FlameSplashController( + fadeInDuration: const Duration(milliseconds: 500), + fadeOutDuration: const Duration(milliseconds: 500), + waitDuration: const Duration(milliseconds: 500), + ); + }); + test('Started automatically', () { + controller.setup(1, () {}); + expect(controller.state, FlameSplashControllerState.started); + }); + test('Calls onFinish after steps', () async { + final onFinishContainer = MockOnFinish(); + controller.setup(3, onFinishContainer.onFinish); + await untilCalled(onFinishContainer.onFinish()); + expect(controller.state, FlameSplashControllerState.finished); + }); + + test('Do not let it start again', () async { + controller.setup(1, () {}); + expect(() => controller.start(), throwsAssertionError); + }); + }); +} diff --git a/packages/flame_splash_screen/test/theme_test.dart b/packages/flame_splash_screen/test/theme_test.dart new file mode 100644 index 00000000000..e4bd4596ab6 --- /dev/null +++ b/packages/flame_splash_screen/test/theme_test.dart @@ -0,0 +1,26 @@ +import 'package:flame_splash_screen/flame_splash_screen.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('white', () { + final theme = FlameSplashTheme.white; + expect( + theme.constraints, + const BoxConstraints.expand(), + ); + expect( + theme.backgroundDecoration, + const BoxDecoration(color: Color(0xFFFFFFFF)), + ); + }); + + test('dark', () { + final theme = FlameSplashTheme.dark; + expect(theme.constraints, const BoxConstraints.expand()); + expect( + theme.backgroundDecoration, + const BoxDecoration(color: Color(0xFF000000)), + ); + }); +}