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 * @Author : Linloir
* @Date : 2022-03-05 20:56:05 * @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 * @Description : The display widget of the wordle game
*/ */
@ -35,7 +35,7 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
if(validation[i] != 1) { if(validation[i] != 1) {
result = false; result = false;
} }
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(milliseconds: 950));
} }
if(!onAnimation) { if(!onAnimation) {
return; return;
@ -45,7 +45,7 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
r++; r++;
c = 0; c = 0;
if(r == 6 || result == true) { if(r == 6 || result == true) {
mainBus.emit(event: "Result", args: result); mainBus.emit(event: "ValidationEnds", args: result);
acceptInput = false; acceptInput = false;
} }
} }
@ -135,7 +135,7 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
return AnimatedSwitcher( return AnimatedSwitcher(
duration: const Duration(milliseconds: 750), duration: const Duration(milliseconds: 700),
switchInCurve: Curves.easeOut, switchInCurve: Curves.easeOut,
reverseDuration: const Duration(milliseconds: 0), reverseDuration: const Duration(milliseconds: 0),
transitionBuilder: (child, animation) { transitionBuilder: (child, animation) {

View File

@ -1,12 +1,11 @@
/* /*
* @Author : Linloir * @Author : Linloir
* @Date : 2022-03-08 08:19:49 * @Date : 2022-03-08 08:19:49
* @LastEditTime : 2022-03-08 10:52:10 * @LastEditTime : 2022-03-08 11:27:41
* @Description : Instruction dialog * @Description : Instruction dialog
*/ */
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:math';
class DecoratedTitleWidget extends StatelessWidget { class DecoratedTitleWidget extends StatelessWidget {
const DecoratedTitleWidget({required this.title, Key? key}) : super(key: key); const DecoratedTitleWidget({required this.title, Key? key}) : super(key: key);

View File

@ -1,7 +1,7 @@
/* /*
* @Author : Linloir * @Author : Linloir
* @Date : 2022-03-05 20:41:41 * @Date : 2022-03-05 20:41:41
* @LastEditTime : 2022-03-08 10:28:17 * @LastEditTime : 2022-03-08 21:44:49
* @Description : Offline page * @Description : Offline page
*/ */
@ -12,6 +12,7 @@ import 'package:wordle/event_bus.dart';
import 'package:wordle/validation_provider.dart'; import 'package:wordle/validation_provider.dart';
import 'package:wordle/display_pannel.dart'; import 'package:wordle/display_pannel.dart';
import 'package:wordle/instruction_pannel.dart'; import 'package:wordle/instruction_pannel.dart';
import 'package:wordle/popper_generator.dart';
class OfflinePage extends StatefulWidget { class OfflinePage extends StatefulWidget {
const OfflinePage({Key? key}) : super(key: key); const OfflinePage({Key? key}) : super(key: key);
@ -21,75 +22,141 @@ class OfflinePage extends StatefulWidget {
} }
class _OfflinePageState extends State<OfflinePage> with TickerProviderStateMixin{ 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var mode = Theme.of(context).brightness; var mode = Theme.of(context).brightness;
return Scaffold( return Stack(
appBar: AppBar( children: [
backgroundColor: Theme.of(context).brightness == Brightness.dark ? Colors.grey[850] : Colors.white, Positioned.fill(
elevation: 0.0, child: Scaffold(
shadowColor: Colors.transparent, appBar: AppBar(
toolbarHeight: 80.0, backgroundColor: Theme.of(context).brightness == Brightness.dark ? Colors.grey[850] : Colors.white,
titleTextStyle: TextStyle( elevation: 0.0,
color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[100] : Colors.black, shadowColor: Colors.transparent,
fontWeight: FontWeight.bold, toolbarHeight: 80.0,
fontSize: 20.0, titleTextStyle: TextStyle(
), color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[100] : Colors.black,
title: const Text('WORDLE OFFLINE'), fontWeight: FontWeight.bold,
centerTitle: true, fontSize: 20.0,
//iconTheme: const IconThemeData(color: Colors.black), ),
actions: [ title: const Text('WORDLE OFFLINE'),
AnimatedSwitcher( centerTitle: true,
duration: const Duration(milliseconds: 750), //iconTheme: const IconThemeData(color: Colors.black),
reverseDuration: const Duration(milliseconds: 750), actions: [
switchInCurve: Curves.bounceOut, AnimatedSwitcher(
switchOutCurve: Curves.bounceIn, duration: const Duration(milliseconds: 750),
transitionBuilder: (child, animation) { reverseDuration: const Duration(milliseconds: 750),
var rotateAnimation = Tween<double>(begin: 0, end: 2 * pi).animate(animation); switchInCurve: Curves.bounceOut,
var opacAnimation = Tween<double>(begin: 0, end: 1).animate(animation); switchOutCurve: Curves.bounceIn,
return AnimatedBuilder( transitionBuilder: (child, animation) {
animation: rotateAnimation, var rotateAnimation = Tween<double>(begin: 0, end: 2 * pi).animate(animation);
builder: (context, child) { var opacAnimation = Tween<double>(begin: 0, end: 1).animate(animation);
return Transform( return AnimatedBuilder(
transform: Matrix4.rotationZ(rotateAnimation.status == AnimationStatus.reverse ? 2 * pi - rotateAnimation.value : rotateAnimation.value), animation: rotateAnimation,
alignment: Alignment.center, builder: (context, child) {
child: Opacity( return Transform(
opacity: opacAnimation.value, 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(
child: child, 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), IconButton(
onPressed: () => mainBus.emit(event: "ToggleTheme", args: []), 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 * @Author : Linloir
* @Date : 2022-03-05 21:40:51 * @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 * @Description : Validation Provider class
*/ */
@ -44,6 +44,7 @@ class _ValidationProviderState extends State<ValidationProvider> {
acceptInput = true; acceptInput = true;
answer = await Words.generateWord(); answer = await Words.generateWord();
answer = answer.toUpperCase(); answer = answer.toUpperCase();
//print(answer);
letterMap = {}; letterMap = {};
answer.split('').forEach((c) { answer.split('').forEach((c) {
letterMap[c] ??= 0; letterMap[c] ??= 0;

View File

@ -5,56 +5,56 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: async name: async
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.2" version: "2.8.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
characters: characters:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
name: charcode name: charcode
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.15.0" version: "1.15.0"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
name: cupertino_icons name: cupertino_icons
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
flutter: flutter:
@ -66,7 +66,7 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
flutter_test: flutter_test:
@ -78,35 +78,35 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.11" version: "0.12.11"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.3" version: "0.1.3"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.7.0"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.0"
sky_engine: sky_engine:
@ -118,56 +118,56 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.10.0" version: "1.10.0"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.8" version: "0.4.8"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
url: "https://pub.flutter-io.cn" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
sdks: 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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.3.7+1451 version: 0.3.8+2220
environment: environment:
sdk: ">=2.16.1 <3.0.0" sdk: ">=2.16.1 <3.0.0"