Add PartyPopper Effect

This commit is contained in:
Linloir 2022-03-08 22:21:05 +08:00
parent b2f9c769d6
commit 91dbf758c8
7 changed files with 304 additions and 93 deletions

View File

@ -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<WordleDisplayWidget> 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<WordleDisplayWidget> 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<WordleDisplayWidget> 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) {

View File

@ -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);

View File

@ -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<OfflinePage> 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<double>(begin: 0, end: 2 * pi).animate(animation);
var opacAnimation = Tween<double>(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<double>(begin: 0, end: 2 * pi).animate(animation);
var opacAnimation = Tween<double>(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,
),
),
],
);
}
}

144
lib/popper_generator.dart Normal file
View File

@ -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<PartyPopperGenerator> createState() => _PartyPopperGeneratorState();
}
class _PartyPopperGeneratorState extends State<PartyPopperGenerator> with SingleTickerProviderStateMixin{
final randoms = <List<dynamic>>[];
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<double>(
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<double>(begin: widget.posY + randoms[i][1], end: constraints.maxHeight + randoms[i][3]).animate(
CurvedAnimation(
parent: widget.controller,
curve: widget.motionCurveY,
),
);
var rotationXAnimation = Tween<double>(begin: 0, end: pi * randoms[i][4]).animate(widget.controller);
var rotationYAnimation = Tween<double>(begin: 0, end: pi * randoms[i][5]).animate(widget.controller);
var rotationZAnimation = Tween<double>(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),
),
),
],
);
}
);
}
}

View File

@ -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<ValidationProvider> {
acceptInput = true;
answer = await Words.generateWord();
answer = answer.toUpperCase();
//print(answer);
letterMap = {};
answer.split('').forEach((c) {
letterMap[c] ??= 0;

View File

@ -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:

View File

@ -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"