From 91dbf758c837990e920396b3614480db649d799e Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Tue, 8 Mar 2022 22:21:05 +0800 Subject: [PATCH] Add PartyPopper Effect --- lib/display_pannel.dart | 8 +- lib/instruction_pannel.dart | 3 +- lib/offline.dart | 193 +++++++++++++++++++++++------------ lib/popper_generator.dart | 144 ++++++++++++++++++++++++++ lib/validation_provider.dart | 3 +- pubspec.lock | 44 ++++---- pubspec.yaml | 2 +- 7 files changed, 304 insertions(+), 93 deletions(-) create mode 100644 lib/popper_generator.dart diff --git a/lib/display_pannel.dart b/lib/display_pannel.dart index 7ed381d..ef961e0 100644 --- a/lib/display_pannel.dart +++ b/lib/display_pannel.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-03-05 20:56:05 - * @LastEditTime : 2022-03-07 14:37:33 + * @LastEditTime : 2022-03-08 21:47:38 * @Description : The display widget of the wordle game */ @@ -35,7 +35,7 @@ class _WordleDisplayWidgetState extends State with TickerPr if(validation[i] != 1) { result = false; } - await Future.delayed(const Duration(seconds: 1)); + await Future.delayed(const Duration(milliseconds: 950)); } if(!onAnimation) { return; @@ -45,7 +45,7 @@ class _WordleDisplayWidgetState extends State with TickerPr r++; c = 0; if(r == 6 || result == true) { - mainBus.emit(event: "Result", args: result); + mainBus.emit(event: "ValidationEnds", args: result); acceptInput = false; } } @@ -135,7 +135,7 @@ class _WordleDisplayWidgetState extends State with TickerPr child: LayoutBuilder( builder: (context, constraints) { return AnimatedSwitcher( - duration: const Duration(milliseconds: 750), + duration: const Duration(milliseconds: 700), switchInCurve: Curves.easeOut, reverseDuration: const Duration(milliseconds: 0), transitionBuilder: (child, animation) { diff --git a/lib/instruction_pannel.dart b/lib/instruction_pannel.dart index 1c9d15c..9ea7320 100644 --- a/lib/instruction_pannel.dart +++ b/lib/instruction_pannel.dart @@ -1,12 +1,11 @@ /* * @Author : Linloir * @Date : 2022-03-08 08:19:49 - * @LastEditTime : 2022-03-08 10:52:10 + * @LastEditTime : 2022-03-08 11:27:41 * @Description : Instruction dialog */ import 'package:flutter/material.dart'; -import 'dart:math'; class DecoratedTitleWidget extends StatelessWidget { const DecoratedTitleWidget({required this.title, Key? key}) : super(key: key); diff --git a/lib/offline.dart b/lib/offline.dart index 13d17e2..66a5e46 100644 --- a/lib/offline.dart +++ b/lib/offline.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-03-05 20:41:41 - * @LastEditTime : 2022-03-08 10:28:17 + * @LastEditTime : 2022-03-08 21:44:49 * @Description : Offline page */ @@ -12,6 +12,7 @@ import 'package:wordle/event_bus.dart'; import 'package:wordle/validation_provider.dart'; import 'package:wordle/display_pannel.dart'; import 'package:wordle/instruction_pannel.dart'; +import 'package:wordle/popper_generator.dart'; class OfflinePage extends StatefulWidget { const OfflinePage({Key? key}) : super(key: key); @@ -21,75 +22,141 @@ class OfflinePage extends StatefulWidget { } class _OfflinePageState extends State with TickerProviderStateMixin{ + late AnimationController _controller; + + void _onGameEnd(dynamic args) { + var result = args as bool; + if(result == true) { + _controller.forward().then((v) { + _controller.reset(); + mainBus.emit(event: "Result", args: result); + } + ); + } + } + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 2000)); + mainBus.onBus(event: "ValidationEnds", onEvent: _onGameEnd); + } + + @override + void dispose() { + mainBus.offBus(event: "ValidationEnds", callBack: _onGameEnd); + super.dispose(); + } + @override Widget build(BuildContext context) { var mode = Theme.of(context).brightness; - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).brightness == Brightness.dark ? Colors.grey[850] : Colors.white, - elevation: 0.0, - shadowColor: Colors.transparent, - toolbarHeight: 80.0, - titleTextStyle: TextStyle( - color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[100] : Colors.black, - fontWeight: FontWeight.bold, - fontSize: 20.0, - ), - title: const Text('WORDLE OFFLINE'), - centerTitle: true, - //iconTheme: const IconThemeData(color: Colors.black), - actions: [ - AnimatedSwitcher( - duration: const Duration(milliseconds: 750), - reverseDuration: const Duration(milliseconds: 750), - switchInCurve: Curves.bounceOut, - switchOutCurve: Curves.bounceIn, - transitionBuilder: (child, animation) { - var rotateAnimation = Tween(begin: 0, end: 2 * pi).animate(animation); - var opacAnimation = Tween(begin: 0, end: 1).animate(animation); - return AnimatedBuilder( - animation: rotateAnimation, - builder: (context, child) { - return Transform( - transform: Matrix4.rotationZ(rotateAnimation.status == AnimationStatus.reverse ? 2 * pi - rotateAnimation.value : rotateAnimation.value), - alignment: Alignment.center, - child: Opacity( - opacity: opacAnimation.value, + return Stack( + children: [ + Positioned.fill( + child: Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).brightness == Brightness.dark ? Colors.grey[850] : Colors.white, + elevation: 0.0, + shadowColor: Colors.transparent, + toolbarHeight: 80.0, + titleTextStyle: TextStyle( + color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[100] : Colors.black, + fontWeight: FontWeight.bold, + fontSize: 20.0, + ), + title: const Text('WORDLE OFFLINE'), + centerTitle: true, + //iconTheme: const IconThemeData(color: Colors.black), + actions: [ + AnimatedSwitcher( + duration: const Duration(milliseconds: 750), + reverseDuration: const Duration(milliseconds: 750), + switchInCurve: Curves.bounceOut, + switchOutCurve: Curves.bounceIn, + transitionBuilder: (child, animation) { + var rotateAnimation = Tween(begin: 0, end: 2 * pi).animate(animation); + var opacAnimation = Tween(begin: 0, end: 1).animate(animation); + return AnimatedBuilder( + animation: rotateAnimation, + builder: (context, child) { + return Transform( + transform: Matrix4.rotationZ(rotateAnimation.status == AnimationStatus.reverse ? 2 * pi - rotateAnimation.value : rotateAnimation.value), + alignment: Alignment.center, + child: Opacity( + opacity: opacAnimation.value, + child: child, + ), + ); + }, child: child, - ), - ); - }, - child: child, - ); - }, - child: IconButton( - key: ValueKey(mode), - icon: mode == Brightness.light ? const Icon(Icons.dark_mode_outlined) : const Icon(Icons.dark_mode), - onPressed: () => mainBus.emit(event: "ToggleTheme", args: []), + ); + }, + child: IconButton( + key: ValueKey(mode), + icon: mode == Brightness.light ? const Icon(Icons.dark_mode_outlined) : const Icon(Icons.dark_mode), + onPressed: () => mainBus.emit(event: "ToggleTheme", args: []), + ), + ), + IconButton( + icon: const Icon(Icons.help_outline_outlined), + //color: Colors.black, + onPressed: (){ + showInstructionDialog(context: context); + }, + ), + IconButton( + icon: const Icon(Icons.refresh_rounded), + //color: Colors.black, + onPressed: (){ + mainBus.emit(event: "NewGame", args: []); + }, + ), + ], + ), + body: Container( + child: const ValidationProvider( + child: WordleDisplayWidget(), + ), + color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[850] : Colors.white, ), ), - IconButton( - icon: const Icon(Icons.help_outline_outlined), - //color: Colors.black, - onPressed: (){ - showInstructionDialog(context: context); - }, - ), - IconButton( - icon: const Icon(Icons.refresh_rounded), - //color: Colors.black, - onPressed: (){ - mainBus.emit(event: "NewGame", args: []); - }, - ), - ], - ), - body: Container( - child: const ValidationProvider( - child: WordleDisplayWidget(), ), - color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[850] : Colors.white, - ), + Positioned.fill( + child: PartyPopperGenerator( + direction: PopDirection.fowardX, + motionCurveX: FunctionCurve(func: (t) { + return - t * t / 2 + t; + }), + motionCurveY: FunctionCurve(func: (t) { + return 4 / 3 * t * t - t / 3; + }), + numbers: 50, + posX: -60.0, + posY: 30.0, + pieceHeight: 15.0, + pieceWidth: 30.0, + controller: _controller, + ), + ), + Positioned.fill( + child: PartyPopperGenerator( + direction: PopDirection.backwardX, + motionCurveX: FunctionCurve(func: (t) { + return - t * t / 2 + t; + }), + motionCurveY: FunctionCurve(func: (t) { + return 4 / 3 * t * t - t / 3; + }), + numbers: 50, + posX: -60.0, + posY: 30.0, + pieceHeight: 15.0, + pieceWidth: 30.0, + controller: _controller, + ), + ), + ], ); } } diff --git a/lib/popper_generator.dart b/lib/popper_generator.dart new file mode 100644 index 0000000..b51b50d --- /dev/null +++ b/lib/popper_generator.dart @@ -0,0 +1,144 @@ +/* + * @Author : Linloir + * @Date : 2022-03-08 16:43:19 + * @LastEditTime : 2022-03-08 21:20:21 + * @Description : Party popper effect generator + */ + +import 'package:flutter/material.dart'; +import 'dart:math'; + +enum PopDirection { fowardX, backwardX } + +class FunctionCurve extends Curve { + const FunctionCurve({required this.func}); + + final double Function(double) func; + + @override + double transform(double t) { + return func(t); + } +} + +class PartyPopperGenerator extends StatefulWidget { + + const PartyPopperGenerator({ + Key? key, + this.numbers = 100, + required this.posX, + required this.posY, + required this.direction, + required this.motionCurveX, + required this.motionCurveY, + required this.controller, + this.pieceWidth = 15.0, + this.pieceHeight = 5.0, + }) : + assert(numbers > 0 && numbers < 500), + assert(pieceWidth > 0), + assert(pieceHeight > 0), + super(key: key); + + //Controls the popping parameters + final double posX; + final double posY; + final PopDirection direction; + final Curve motionCurveX; + final Curve motionCurveY; + final AnimationController controller; + + //Controls the number of pieces + final int numbers; + final double pieceWidth; + final double pieceHeight; + + @override + State createState() => _PartyPopperGeneratorState(); +} + +class _PartyPopperGeneratorState extends State with SingleTickerProviderStateMixin{ + final randoms = >[]; + final colors = [Colors.orange[800], + Colors.green[800], + Colors.red[800], + Colors.orange[900], + Colors.yellow[800], + Colors.green[400], + Colors.blue[800], + Colors.blue[700], + Colors.teal[800], + Colors.purple, + Colors.brown, + Colors.yellow, + Colors.red[400], + Colors.pink]; + + @override + void initState() { + var rng = Random(); + for(int i = 0; i < widget.numbers; i++) { + var randHorizontalStartShift = (rng.nextDouble() - 0.5) * 50; + var randVerticalStartShift = (rng.nextDouble() - 0.5) * 150; + var randHorizontalEndShift = (rng.nextDouble() - 0.5) * 900; + var randVerticalEndShift = rng.nextDouble() * 1000; + var randRotateCirclesX = rng.nextInt(7) + 1; + var randRotateCirclesY = rng.nextInt(7) + 1; + var randRotateCirclesZ = rng.nextInt(5) + 1; + randoms.add([randHorizontalStartShift, randVerticalStartShift, randHorizontalEndShift, randVerticalEndShift, randRotateCirclesX, randRotateCirclesY, randRotateCirclesZ]); + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder:(context, constraints) { + return Stack( + children: [ + for(int i = 0; i < widget.numbers; i++) + AnimatedBuilder( + animation: widget.controller, + builder: (context, child) { + var horizontalAnimation = Tween( + begin: (widget.direction == PopDirection.fowardX ? widget.posX + randoms[i][0]: constraints.maxWidth - widget.posX - randoms[i][0]), + end: (widget.direction == PopDirection.fowardX ? constraints.maxWidth + randoms[i][2] : 0.0 - randoms[i][2]), + ).animate( + CurvedAnimation( + parent: widget.controller, + curve: widget.motionCurveX, + ), + ); + var verticalAnimation = Tween(begin: widget.posY + randoms[i][1], end: constraints.maxHeight + randoms[i][3]).animate( + CurvedAnimation( + parent: widget.controller, + curve: widget.motionCurveY, + ), + ); + var rotationXAnimation = Tween(begin: 0, end: pi * randoms[i][4]).animate(widget.controller); + var rotationYAnimation = Tween(begin: 0, end: pi * randoms[i][5]).animate(widget.controller); + var rotationZAnimation = Tween(begin: 0, end: pi * randoms[i][6]).animate(widget.controller); + return Positioned( + left: horizontalAnimation.value, + width: widget.pieceWidth, + top: verticalAnimation.value, + height: widget.pieceHeight, + child: Transform( + transform: Matrix4.rotationX(rotationXAnimation.value)..rotateY(rotationYAnimation.value)..rotateZ(rotationZAnimation.value), + alignment: Alignment.center, + child: child, + ), + ); + }, + child: Container( + width: widget.pieceWidth, + height: widget.pieceHeight, + color: colors[Random().nextInt(colors.length)]!.withAlpha(Random().nextInt(55) + 200), + ), + ), + ], + ); + } + ); + } +} \ No newline at end of file diff --git a/lib/validation_provider.dart b/lib/validation_provider.dart index 1280439..2aa3519 100644 --- a/lib/validation_provider.dart +++ b/lib/validation_provider.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-03-05 21:40:51 - * @LastEditTime : 2022-03-07 14:40:46 + * @LastEditTime : 2022-03-08 22:19:08 * @Description : Validation Provider class */ @@ -44,6 +44,7 @@ class _ValidationProviderState extends State { acceptInput = true; answer = await Words.generateWord(); answer = answer.toUpperCase(); + //print(answer); letterMap = {}; answer.split('').forEach((c) { letterMap[c] ??= 0; diff --git a/pubspec.lock b/pubspec.lock index 9b41ab4..7e78bae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,56 +5,56 @@ packages: dependency: transitive description: name: async - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.8.2" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.0" characters: dependency: transitive description: name: characters - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.2.0" charcode: dependency: transitive description: name: charcode - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.3.1" clock: dependency: transitive description: name: clock - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.1.0" collection: dependency: transitive description: name: collection - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.15.0" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.4" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.2.0" flutter: @@ -66,7 +66,7 @@ packages: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.4" flutter_test: @@ -78,35 +78,35 @@ packages: dependency: transitive description: name: lints - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.0.1" matcher: dependency: transitive description: name: matcher - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.12.11" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.1.3" meta: dependency: transitive description: name: meta - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.7.0" path: dependency: transitive description: name: path - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.8.0" sky_engine: @@ -118,56 +118,56 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.8.1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.2.0" test_api: dependency: transitive description: name: test_api - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "0.4.8" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "1.3.0" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.flutter-io.cn" + url: "https://pub.dartlang.org" source: hosted version: "2.1.1" sdks: diff --git a/pubspec.yaml b/pubspec.yaml index 3622126..67553ad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.3.7+1451 +version: 0.3.8+2220 environment: sdk: ">=2.16.1 <3.0.0"