mirror of
https://github.com/Linloir/Flutter-Wordle.git
synced 2025-12-18 14:58:10 +08:00
Compare commits
28 Commits
Beta-0.3.7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d56485db0 | ||
|
|
e8ede4b5a1 | ||
|
|
5cce2c3e6b | ||
|
|
d33cd7565e | ||
|
|
a27135f7da | ||
| 4928e39c89 | |||
| 1d58daf4c1 | |||
|
|
56dc1deebf | ||
|
|
8406649a6d | ||
|
|
2d2bcd6fa9 | ||
|
|
3326f2323d | ||
|
|
fe1158340e | ||
|
|
7a9200eeba | ||
|
|
9c709ecccf | ||
|
|
b64dc0789a | ||
| 38ccdfa476 | |||
| 971de665c0 | |||
|
|
db1c442a89 | ||
| bb4cec06fa | |||
| a772a6a681 | |||
| 91dbf758c8 | |||
|
|
16b1c691b2 | ||
|
|
2d042c6deb | ||
|
|
4eb6779c42 | ||
|
|
5a6d2800c0 | ||
| b2f9c769d6 | |||
| 87b8bdc38b | |||
|
|
8e3b157ea0 |
48
README.md
48
README.md
@ -1,2 +1,48 @@
|
||||
# Flutter-Wordle
|
||||
A wordle game written by flutter
|
||||
|
||||
A wordle game written with flutter
|
||||
|
||||
## How to play
|
||||
|
||||
Thanks to the feature of Flutter, the game is available on all platforms
|
||||
|
||||
### Play on Web
|
||||
|
||||
Visit [Wordle on my server](http://wordle.linloir.cn) to play the online version
|
||||
|
||||
### Play on Android
|
||||
|
||||
Download `.apk` file in the release page, install & enjoy.
|
||||
|
||||
### Deploy a web version
|
||||
|
||||
- clone this repository
|
||||
- `flutter clean`
|
||||
- `flutter pub get`
|
||||
- `flutter build web --release --web-renderer html`
|
||||
|
||||
## What dictionary am I using
|
||||
|
||||
The hidden word is selected from one of the wordlists from [English wordlists for Chinese](https://github.com/mahavivo/english-wordlists)
|
||||
|
||||
The verification wordlist currently is an addup of all the wordlists available.
|
||||
|
||||
> Note that I'm changing the verification wordlist to [Letterpress wordlist](https://github.com/lorenbrichter/Words) lately.
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
||||
<p float="left">
|
||||
<img src="https://pic.linloir.cn/images/2022/03/08/IMG_20220308_220301.jpg" width="200">
|
||||
<img src="https://pic.linloir.cn/images/2022/03/08/IMG_20220308_220322.jpg" width="200">
|
||||
<img src="https://pic.linloir.cn/images/2022/03/08/IMG_20220308_220237.jpg" width="200">
|
||||
<img src="https://pic.linloir.cn/images/2022/03/08/IMG_20220308_220353.jpg" width="200">
|
||||
</p>
|
||||
|
||||
## Support Me
|
||||
|
||||
This will be a longterm project and there will be awsome features coming up (include history page, online version, ranking, sharing, versus mode etc)
|
||||
|
||||
P.S. Several new Projects are currently occupying the develop time of this app, so further development might be delayed. One of them is a [component repository](https://github.com/Linloir/LUI-Flutter_Gui_Kit) I used for my own flutter apps and you might want to have a look~
|
||||
|
||||
you can support me by simply **starring** this project
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.wordle">
|
||||
<application
|
||||
android:label="Wordle"
|
||||
android:label="Worfinite"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
|
||||
13866
assets/All.txt
Normal file
13866
assets/All.txt
Normal file
File diff suppressed because it is too large
Load Diff
8028
assets/CET4 + 6.txt
Normal file
8028
assets/CET4 + 6.txt
Normal file
File diff suppressed because it is too large
Load Diff
4615
assets/CET4.txt
Normal file
4615
assets/CET4.txt
Normal file
File diff suppressed because it is too large
Load Diff
2273
assets/CET6.txt
Normal file
2273
assets/CET6.txt
Normal file
File diff suppressed because it is too large
Load Diff
5987
assets/COCA-Slim.txt
Normal file
5987
assets/COCA-Slim.txt
Normal file
File diff suppressed because it is too large
Load Diff
4343
assets/GRE Slim.txt
Normal file
4343
assets/GRE Slim.txt
Normal file
File diff suppressed because it is too large
Load Diff
3469
assets/HighSchool.txt
Normal file
3469
assets/HighSchool.txt
Normal file
File diff suppressed because it is too large
Load Diff
1520
assets/TOEFL Slim.txt
Normal file
1520
assets/TOEFL Slim.txt
Normal file
File diff suppressed because it is too large
Load Diff
4516
assets/TOEFL.txt
Normal file
4516
assets/TOEFL.txt
Normal file
File diff suppressed because it is too large
Load Diff
79339
assets/ospd.txt
79339
assets/ospd.txt
File diff suppressed because it is too large
Load Diff
25321
assets/popular.txt
25321
assets/popular.txt
File diff suppressed because it is too large
Load Diff
235887
assets/unixWords.txt
235887
assets/unixWords.txt
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 20:56:05
|
||||
* @LastEditTime : 2022-03-07 14:37:33
|
||||
* @LastEditTime : 2022-03-11 14:54:04
|
||||
* @Description : The display widget of the wordle game
|
||||
*/
|
||||
|
||||
@ -12,7 +12,10 @@ import './event_bus.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
class WordleDisplayWidget extends StatefulWidget {
|
||||
const WordleDisplayWidget({Key? key}) : super(key: key);
|
||||
const WordleDisplayWidget({Key? key, required this.wordLen, required this.maxChances}) : super(key: key);
|
||||
|
||||
final int wordLen;
|
||||
final int maxChances;
|
||||
|
||||
@override
|
||||
State<WordleDisplayWidget> createState() => _WordleDisplayWidgetState();
|
||||
@ -28,14 +31,14 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
|
||||
void _validationAnimation(List<int> validation) async {
|
||||
onAnimation = true;
|
||||
bool result = true;
|
||||
for(int i = 0; i < 5 && onAnimation; i++) {
|
||||
for(int i = 0; i < widget.wordLen && onAnimation; i++) {
|
||||
setState((){
|
||||
inputs[r][i]["State"] = validation[i];
|
||||
});
|
||||
if(validation[i] != 1) {
|
||||
result = false;
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
await Future.delayed(const Duration(milliseconds: 950));
|
||||
}
|
||||
if(!onAnimation) {
|
||||
return;
|
||||
@ -44,8 +47,8 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
|
||||
onAnimation = false;
|
||||
r++;
|
||||
c = 0;
|
||||
if(r == 6 || result == true) {
|
||||
mainBus.emit(event: "Result", args: result);
|
||||
if(r == widget.maxChances || result == true) {
|
||||
mainBus.emit(event: "ValidationEnds", args: result);
|
||||
acceptInput = false;
|
||||
}
|
||||
}
|
||||
@ -61,8 +64,8 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
|
||||
c = 0;
|
||||
onAnimation = false;
|
||||
acceptInput = true;
|
||||
for(int i = 0; i < 6; i++) {
|
||||
for(int j = 0; j < 5; j++) {
|
||||
for(int i = 0; i < widget.maxChances; i++) {
|
||||
for(int j = 0; j < widget.wordLen; j++) {
|
||||
inputs[i][j]["Letter"] = "";
|
||||
inputs[i][j]["State"] = 0;
|
||||
}
|
||||
@ -74,9 +77,9 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
|
||||
void initState() {
|
||||
super.initState();
|
||||
inputs = [
|
||||
for(int i = 0; i < 6; i++)
|
||||
for(int i = 0; i < widget.maxChances; i++)
|
||||
[
|
||||
for(int j = 0; j < 5; j++)
|
||||
for(int j = 0; j < widget.wordLen; j++)
|
||||
{
|
||||
"Letter": "",
|
||||
"State": 0,
|
||||
@ -111,17 +114,17 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 5 / 6,
|
||||
aspectRatio: widget.wordLen / widget.maxChances,
|
||||
child: Column(
|
||||
//Column(
|
||||
children: [
|
||||
for(int i = 0; i < 6; i++)
|
||||
for(int i = 0; i < widget.maxChances; i++)
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
for (int j = 0; j < 5; j++)
|
||||
for (int j = 0; j < widget.wordLen; j++)
|
||||
AnimatedBuilder(
|
||||
animation: inputs[i][j]["InputAnimationController"],
|
||||
builder: (context, child) {
|
||||
@ -135,7 +138,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) {
|
||||
@ -206,7 +209,7 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
|
||||
),
|
||||
onNotification: (noti) {
|
||||
if(noti.type == InputType.singleCharacter) {
|
||||
if(r < 6 && c < 5 && !onAnimation && acceptInput) {
|
||||
if(r < widget.maxChances && c < widget.wordLen && !onAnimation && acceptInput) {
|
||||
setState((){
|
||||
inputs[r][c]["Letter"] = noti.msg;
|
||||
inputs[r][c]["State"] = 3;
|
||||
|
||||
174
lib/game_page.dart
Normal file
174
lib/game_page.dart
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 20:41:41
|
||||
* @LastEditTime : 2022-03-11 14:54:15
|
||||
* @Description : Offline page
|
||||
*/
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
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 GamePage extends StatefulWidget {
|
||||
const GamePage({Key? key, required this.database, required this.wordLen, required this.maxChances, required this.gameMode}) : super(key: key);
|
||||
|
||||
final Map<String, List<String>> database;
|
||||
final int wordLen;
|
||||
final int maxChances;
|
||||
final int gameMode;
|
||||
|
||||
@override
|
||||
State<GamePage> createState() => _GamePageState();
|
||||
}
|
||||
|
||||
class _GamePageState extends State<GamePage> 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);
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
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 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'),
|
||||
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: 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: ValidationProvider(
|
||||
database: widget.database,
|
||||
wordLen: widget.wordLen,
|
||||
maxChances: widget.maxChances,
|
||||
gameMode: widget.gameMode,
|
||||
child: WordleDisplayWidget(wordLen: widget.wordLen, maxChances: widget.maxChances),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,79 +1,58 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-06 15:03:57
|
||||
* @LastEditTime : 2022-03-07 15:55:48
|
||||
* @LastEditTime : 2022-03-11 13:56:33
|
||||
* @Description : Word generator
|
||||
*/
|
||||
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
abstract class Words {
|
||||
static Set<String> dataBase = <String>{};
|
||||
static Set<String> dictionary = <String>{};
|
||||
static int _length = 5;
|
||||
static String _cache = "";
|
||||
//static Map<String, String> explainations = {};
|
||||
|
||||
static Future<bool> importWordsDatabase({int length = 5}) async {
|
||||
//explainations.clear();
|
||||
if(length != _length || dataBase.isEmpty || dictionary.isEmpty){
|
||||
_length = length;
|
||||
dataBase.clear();
|
||||
_cache = "";
|
||||
try {
|
||||
var data = await rootBundle.loadString('assets/popular.txt');
|
||||
LineSplitter.split(data).forEach((line) {
|
||||
if(line.length == length && dataBase.lookup(line.substring(0,length - 1)) == null && dataBase.lookup(line.substring(0, length - 2)) == null) {
|
||||
dataBase.add(line.toLowerCase());
|
||||
}
|
||||
_cache = line;
|
||||
Future<Set<String>> generateDictionary() async {
|
||||
// Directory documentRoot = await getApplicationDocumentsDirectory();
|
||||
// String dicPath = documentRoot.path + Platform.pathSeparator + dicName + ".txt";
|
||||
// File dicFile = File(dicPath);
|
||||
String dicContents = await rootBundle.loadString("assets/All.txt");
|
||||
Set<String> database = {};
|
||||
LineSplitter.split(dicContents).forEach((line) {
|
||||
database.add(line.toUpperCase());
|
||||
});
|
||||
} catch (e) {
|
||||
throw "Failed loading question database";
|
||||
}
|
||||
try {
|
||||
var data = await rootBundle.loadString('assets/unixWords.txt');
|
||||
LineSplitter.split(data).forEach((line) {
|
||||
if(line.length == length && dictionary.lookup(line.substring(0,length - 1)) == null && dictionary.lookup(line.substring(0, length - 2)) == null) {
|
||||
dictionary.add(line.toLowerCase());
|
||||
}
|
||||
_cache = line;
|
||||
});
|
||||
} catch (e) {
|
||||
throw "Failed loading validation database";
|
||||
}
|
||||
}
|
||||
if(dataBase.isEmpty) {
|
||||
throw "Empty question database";
|
||||
}
|
||||
else if(dictionary.isEmpty) {
|
||||
throw "Empty validation database";
|
||||
}
|
||||
return true;
|
||||
return database;
|
||||
}
|
||||
|
||||
static Future<String> generateWord() async {
|
||||
int bound = dataBase.length;
|
||||
if(bound == 0) {
|
||||
try{
|
||||
await Words.importWordsDatabase(length: _length);
|
||||
} catch (e) {
|
||||
return 'crash';
|
||||
Future<Map<String, List<String>>> generateQuestionSet({required String dicName, required int wordLen}) async {
|
||||
// Directory documentRoot = await getApplicationDocumentsDirectory();
|
||||
// String dicPath = documentRoot.path + Platform.pathSeparator + dicName + ".txt";
|
||||
// File dicFile = File(dicPath);
|
||||
String dicContents = await rootBundle.loadString("assets/" + dicName + ".txt");
|
||||
Map<String, List<String>> database = {};
|
||||
LineSplitter.split(dicContents).forEach((line) {
|
||||
var vowelStart = line.indexOf('[');
|
||||
var vowelEnd = line.indexOf(']');
|
||||
var word = "";
|
||||
var vowel = "";
|
||||
var explain = "";
|
||||
word = line.substring(0, vowelStart == -1 ? null : vowelStart).trim().toUpperCase();
|
||||
if(vowelStart != -1){
|
||||
vowel = line.substring(vowelStart, vowelEnd + 1).trim();
|
||||
explain = line.substring(vowelEnd + 1).trim();
|
||||
}
|
||||
bound = dataBase.length;
|
||||
if(wordLen == -1 || word.length == wordLen) {
|
||||
database[word] = [vowel, explain];
|
||||
}
|
||||
});
|
||||
return database;
|
||||
}
|
||||
|
||||
Future<String> fetchOnlineWord() async {
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
return "BINGO";
|
||||
}
|
||||
|
||||
String getNextWord(Map<String, List<String>> database) {
|
||||
var rng = Random();
|
||||
String res = dataBase.elementAt(rng.nextInt(bound));
|
||||
if(dictionary.lookup(res) == null) {
|
||||
dictionary.add(res);
|
||||
return database.keys.elementAt(rng.nextInt(database.length));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool isWordValidate(String word) {
|
||||
return dictionary.lookup(word.toLowerCase()) != null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
27
lib/group_shared.dart
Normal file
27
lib/group_shared.dart
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-10 10:35:47
|
||||
* @LastEditTime : 2022-03-10 13:06:37
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SelectNotification extends Notification {
|
||||
const SelectNotification({required this.selection});
|
||||
|
||||
final int selection;
|
||||
}
|
||||
|
||||
class GroupSharedData extends InheritedWidget {
|
||||
const GroupSharedData({Key? key, required child, required this.selected}) : super(key: key, child: child);
|
||||
|
||||
final int selected;
|
||||
|
||||
static GroupSharedData? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<GroupSharedData>();
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(GroupSharedData oldWidget) {
|
||||
return oldWidget.selected != selected;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 20:55:53
|
||||
* @LastEditTime : 2022-03-07 08:07:14
|
||||
* @LastEditTime : 2022-03-11 11:05:10
|
||||
* @Description : Input pannel class
|
||||
*/
|
||||
|
||||
@ -30,9 +30,11 @@ class _InputPannelWidgetState extends State<InputPannelWidget> {
|
||||
}
|
||||
|
||||
void _onAnimationStops(dynamic args) {
|
||||
_cache.forEach((key, value) {
|
||||
setState(() {
|
||||
_cache.forEach((key, value) {
|
||||
if(_keyState[key] != 1){
|
||||
_keyState[key] = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
257
lib/instruction_pannel.dart
Normal file
257
lib/instruction_pannel.dart
Normal file
@ -0,0 +1,257 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-08 08:19:49
|
||||
* @LastEditTime : 2022-03-08 11:27:41
|
||||
* @Description : Instruction dialog
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DecoratedTitleWidget extends StatelessWidget {
|
||||
const DecoratedTitleWidget({required this.title, Key? key}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var mainTextColor = Theme.of(context).brightness == Brightness.dark ? Colors.white : Colors.grey[850];
|
||||
var mainDecorationColor = Theme.of(context).brightness == Brightness.dark ? Colors.white.withAlpha(100) : Colors.grey.withAlpha(100);
|
||||
return SizedBox(
|
||||
height: 50.0,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.directional(
|
||||
textDirection: TextDirection.ltr,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
color: mainDecorationColor,
|
||||
width: 50.0,
|
||||
height: 10.0,
|
||||
),
|
||||
),
|
||||
Positioned.directional(
|
||||
textDirection: TextDirection.ltr,
|
||||
bottom: 0,
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 25.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: mainTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DecoratedPlainText extends StatelessWidget {
|
||||
const DecoratedPlainText({required this.text, Key? key}) : super(key: key);
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var mainTextColor = Theme.of(context).brightness == Brightness.dark ? Colors.white : Colors.grey[850];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: mainTextColor,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DecoratedTextBox extends StatelessWidget {
|
||||
const DecoratedTextBox({required this.letter, required this.state, Key? key}) : super(key: key);
|
||||
|
||||
final String letter;
|
||||
final int state;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var mainTextColor = Theme.of(context).brightness == Brightness.dark ? Colors.white :
|
||||
state != 0 && state != 3 ? Colors.white : Colors.grey[850];
|
||||
var backGroundColor = state == 1 ? Colors.green[600]! :
|
||||
state == 2 ? Colors.yellow[800]! :
|
||||
state == -1 ? Colors.grey[700]! :
|
||||
Theme.of(context).brightness == Brightness.dark ? Colors.grey[850]! : Colors.white;
|
||||
var borderColor = state == 1 ? Colors.green[600]! :
|
||||
state == 2 ? Colors.yellow[800]! :
|
||||
state == 3 ? Theme.of(context).brightness == Brightness.dark ? Colors.grey[400]! : Colors.grey[850]! :
|
||||
state == -1 ? Colors.grey[700]! :
|
||||
Theme.of(context).brightness == Brightness.dark ? Colors.grey[700]! : Colors.grey[400]!;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 1.0),
|
||||
child: Container(
|
||||
width: 40.0,
|
||||
height: 40.0,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: borderColor,
|
||||
width: 2.0,
|
||||
),
|
||||
color: backGroundColor,
|
||||
),
|
||||
child: Text(
|
||||
letter,
|
||||
style: TextStyle(
|
||||
color: mainTextColor,
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showInstructionDialog({required context}) {
|
||||
return showGeneralDialog(
|
||||
transitionDuration: const Duration(milliseconds: 750),
|
||||
context: context,
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return SafeArea(
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return Dialog(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 10.0),
|
||||
child: Text(
|
||||
'How To Play',
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 20.0),
|
||||
alignment: Alignment.center,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
//# AIM OF THE GAME
|
||||
const DecoratedTitleWidget(title: "Aim Of The Game"),
|
||||
//Content of # AIM OF THE GAME
|
||||
const DecoratedPlainText(
|
||||
text: "The aim of the WORDLE game is to guess a word within six tries.",
|
||||
),
|
||||
// const DecoratedPlainText(
|
||||
// text: "By default, the hidden word is five characters long.",
|
||||
// ),
|
||||
const DecoratedPlainText(
|
||||
text: "Each guess should be a valid word which matches the length of the hidden word, length of which by default is five characters long.",
|
||||
),
|
||||
const DecoratedPlainText(
|
||||
text: "After each guess, the color of the tiles will change to infer how close your answer is to the word. The meaning of the colors is shown below.",
|
||||
),
|
||||
const DecoratedTitleWidget(title: "Read The Colors"),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: const [
|
||||
DecoratedTextBox(letter: "B", state: 1),
|
||||
DecoratedTextBox(letter: "I", state: 0),
|
||||
DecoratedTextBox(letter: "N", state: 0),
|
||||
DecoratedTextBox(letter: "G", state: 0),
|
||||
DecoratedTextBox(letter: "O", state: 0),
|
||||
],
|
||||
),
|
||||
),
|
||||
const DecoratedPlainText(
|
||||
text: "The green tile shows that letter B is in the word and it's in the right spot.",
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: const [
|
||||
DecoratedTextBox(letter: "S", state: 1),
|
||||
DecoratedTextBox(letter: "Y", state: 2),
|
||||
DecoratedTextBox(letter: "S", state: -1),
|
||||
DecoratedTextBox(letter: "U", state: 0),
|
||||
],
|
||||
),
|
||||
const DecoratedPlainText(
|
||||
text: "The yellow tile shows that letter Y is in the word but it's not in the right spot.",
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: const [
|
||||
DecoratedTextBox(letter: "O", state: -1),
|
||||
DecoratedTextBox(letter: "O", state: -1),
|
||||
DecoratedTextBox(letter: "P", state: -1),
|
||||
DecoratedTextBox(letter: "S", state: -1),
|
||||
],
|
||||
),
|
||||
const DecoratedPlainText(
|
||||
text: "A grey tile shows the letter is not in the word. For example, O, P, S are not in the word.",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: TextButton(
|
||||
child: Text('Got it!', style: TextStyle(color: Colors.grey[850]),),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
},
|
||||
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||
var scaleAnimation = Tween<double>(begin: 0, end: 1).animate(
|
||||
CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeOutExpo,
|
||||
)
|
||||
);
|
||||
var opacAnimation = Tween<double>(begin: 0, end: 1).animate(
|
||||
CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeOut,
|
||||
)
|
||||
);
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: opacAnimation.value,
|
||||
child: Transform.scale(
|
||||
scale: scaleAnimation.value,
|
||||
child: child,
|
||||
)
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
barrierDismissible: true,
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
);
|
||||
}
|
||||
70
lib/loading_page.dart
Normal file
70
lib/loading_page.dart
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-11 09:51:50
|
||||
* @LastEditTime : 2022-03-11 14:54:17
|
||||
* @Description : Game Page
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wordle/game_page.dart';
|
||||
import 'package:wordle/generator.dart';
|
||||
import 'package:wordle/validation_provider.dart';
|
||||
|
||||
class LoadingPage extends StatefulWidget {
|
||||
const LoadingPage({Key? key, required this.dicName, required this.wordLen, required this.maxChances, required this.gameMode}) : super(key: key);
|
||||
|
||||
final String dicName;
|
||||
final int wordLen;
|
||||
final int maxChances;
|
||||
final int gameMode;
|
||||
|
||||
@override
|
||||
State<LoadingPage> createState() => _LoadingPageState();
|
||||
}
|
||||
|
||||
class _LoadingPageState extends State<LoadingPage> {
|
||||
Future<Map<String, List<String>>> _loadDatabase({required String dicName, required int wordLen, required int gameMode}) async {
|
||||
var dataBase = await generateQuestionSet(dicName: dicName, wordLen: wordLen);
|
||||
if(ValidationProvider.validationDatabase.isEmpty) {
|
||||
ValidationProvider.validationDatabase = await generateDictionary();
|
||||
}
|
||||
return dataBase;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: _loadDatabase(dicName: widget.dicName, wordLen: widget.wordLen, gameMode: widget.wordLen),
|
||||
builder: (context, snapshot) {
|
||||
if(snapshot.connectionState == ConnectionState.done) {
|
||||
return GamePage(database: snapshot.data as Map<String, List<String>>, wordLen: widget.wordLen, maxChances: widget.maxChances, gameMode: widget.gameMode);
|
||||
}
|
||||
else {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 50.0,
|
||||
height: 50.0,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.teal[400],
|
||||
strokeWidth: 4.0,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15.0),
|
||||
child: Text('Loading Libraries', style: TextStyle(color: Colors.teal[400], fontWeight: FontWeight.bold, fontSize: 25.0),),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
448
lib/main.dart
448
lib/main.dart
@ -1,13 +1,39 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 20:21:34
|
||||
* @LastEditTime : 2022-03-07 14:40:28
|
||||
* @LastEditTime : 2022-03-14 18:01:15
|
||||
* @Description :
|
||||
*/
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:wordle/event_bus.dart';
|
||||
import './offline.dart';
|
||||
import './generator.dart';
|
||||
import 'package:wordle/loading_page.dart';
|
||||
import 'package:wordle/single_selection.dart';
|
||||
import 'package:wordle/selection_group.dart';
|
||||
import 'package:wordle/scroll_behav.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:wordle/instruction_pannel.dart';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
Future<void> loadSettings() async {
|
||||
Directory documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
String documentsPath = documentsDirectory.path + Platform.pathSeparator;
|
||||
File settings = File(documentsPath + "settings.txt");
|
||||
if(!(await settings.exists())) {
|
||||
var defaultSettings = "5\nCET4\nLight";
|
||||
settings.writeAsString(defaultSettings);
|
||||
}
|
||||
List<String> dicBooks = ["validation.txt", "CET4.txt"];
|
||||
for(String dicName in dicBooks) {
|
||||
if(!(await File(documentsPath + dicName).exists())) {
|
||||
//Copy file
|
||||
ByteData data = await rootBundle.load("assets/CET4.txt");
|
||||
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
|
||||
await File(documentsPath + dicName).writeAsBytes(bytes, flush: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main(){
|
||||
runApp(const MyApp());
|
||||
@ -33,6 +59,7 @@ class _MyAppState extends State<MyApp> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
//loadSettings();
|
||||
mainBus.onBus(event: "ToggleTheme", onEvent: _onThemeChange);
|
||||
}
|
||||
|
||||
@ -45,6 +72,7 @@ class _MyAppState extends State<MyApp> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
scrollBehavior: MyScrollBehavior(),
|
||||
title: 'Wordle',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.grey,
|
||||
@ -52,7 +80,6 @@ class _MyAppState extends State<MyApp> {
|
||||
),
|
||||
routes: {
|
||||
"/": (context) => const HomePage(),
|
||||
"/Offline": (context) => const OfflinePage(),
|
||||
},
|
||||
initialRoute: "/",
|
||||
);
|
||||
@ -67,120 +94,347 @@ class HomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
bool _ignorance = false;
|
||||
//bool _ignorance = false;
|
||||
|
||||
Future<void> readSettings() async {
|
||||
return;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: Words.importWordsDatabase(),
|
||||
future: readSettings(),
|
||||
builder: (context, snapshot) {
|
||||
if(snapshot.connectionState == ConnectionState.done || _ignorance) {
|
||||
if(snapshot.hasData || _ignorance){
|
||||
if(snapshot.connectionState == ConnectionState.done) {
|
||||
var mode = Theme.of(context).brightness;
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(50.0),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: OutlinedButton(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
body: Align(
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 960.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'OFFLINE PLAYGROUND',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).brightness == Brightness.dark ? Colors.white : Colors.grey[850]!,
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.bold
|
||||
Container(
|
||||
constraints: const BoxConstraints(minHeight: 100.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 30.0, bottom: 10.0),
|
||||
child: Text('WORDLE', style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.w300, color: mode == Brightness.light ? Colors.grey[850]! : Colors.white)),
|
||||
),
|
||||
),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
// child: Container(
|
||||
// width: 80.0,
|
||||
// height: 8.0,
|
||||
// decoration: BoxDecoration(
|
||||
// color: Colors.grey[800],
|
||||
// borderRadius: BorderRadius.circular(3.5),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
Text(
|
||||
'Play Wordle game offline using local word database',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[500],
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
style: ButtonStyle(
|
||||
shape: MaterialStateProperty.all<OutlinedBorder?>(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),
|
||||
padding: MaterialStateProperty.all<EdgeInsets?>(const EdgeInsets.all(0)),
|
||||
),
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pushNamed("/Offline");
|
||||
//Future.delayed(const Duration(milliseconds: 10)).then((e) => mainBus.emit(event: "NewGame", args: []));
|
||||
}
|
||||
)
|
||||
),
|
||||
)
|
||||
const Spacer(),
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 30.0, bottom: 10.0),
|
||||
child: Row(
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Container(
|
||||
color: Colors.black38,
|
||||
child: AlertDialog(
|
||||
title: const Text('Failure'),
|
||||
content: Text(snapshot.error as String),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('Retry'),
|
||||
onPressed: () => setState(() {}),
|
||||
},
|
||||
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: []),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('Ignore'),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.help_outline_outlined),
|
||||
//color: Colors.black,
|
||||
onPressed: (){
|
||||
setState((){
|
||||
_ignorance = true;
|
||||
});
|
||||
showInstructionDialog(context: context);
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: OfflinePage(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
return const Text('');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OfflinePage extends StatefulWidget {
|
||||
const OfflinePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<OfflinePage> createState() => _OfflinePageState();
|
||||
}
|
||||
|
||||
class _OfflinePageState extends State<OfflinePage> {
|
||||
int wordLen = 5;
|
||||
int maxChances = 6;
|
||||
int dicBookIndex = 0;
|
||||
var dicBook = [["All", "Full wordlist", "A", Colors.indigo], ["HighSchool", "HighSchool wordlist", "H", Colors.amber],
|
||||
["CET4", "CET4 wordlist", "4", Colors.green[400]], ["CET6", "CET6 wordlist", "6", Colors.teal[400]],
|
||||
["CET4 + 6", "CET4&6 wordlist", "46", Colors.teal[600]], ["TOEFL Slim", "TOEFL without CET4&6", "T", Colors.blue[400]],
|
||||
["TOEFL", "Full TOEFL wordlist", "T", Colors.cyan[400]], ["GRE Slim", "GRE without CET4&6", "G", Colors.pink[200]]];
|
||||
late final List<Widget> dicBookSelections;
|
||||
var wordLenSelectionColors = [Colors.green[300], Colors.green[500], Colors.teal[300], Colors.teal[500], Colors.pink[300]];
|
||||
var chancesSelectionColors = [Colors.deepOrange[600], Colors.orange[400], Colors.cyan, Colors.blue[400], Colors.blue[600], Colors.teal[400], Colors.teal[600], Colors.green[700]];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
dicBookSelections = [
|
||||
for(int i = 0; i < dicBook.length; i++)
|
||||
generateSelectionBox(
|
||||
id: i,
|
||||
width: 190,
|
||||
height: 240,
|
||||
padding: const EdgeInsets.fromLTRB(15.0, 25.0, 15.0, 0.0),
|
||||
color: dicBook[i][3]! as Color,
|
||||
primaryText: dicBook[i][0] as String,
|
||||
primaryTextSize: 20.0,
|
||||
secondaryText: dicBook[i][1] as String,
|
||||
secondaryTextSize: 14.0,
|
||||
decorationText: dicBook[i][2] as String,
|
||||
alignment: Alignment.topLeft,
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 60.0,
|
||||
width: 60.0,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 6.0,
|
||||
color: Colors.grey[850],
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 150.0,
|
||||
decoration: BoxDecoration(
|
||||
color: wordLenSelectionColors[wordLen - 4]!.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(15.0, 10.0, 0.0, 10.0),
|
||||
child: Text(
|
||||
'Word Length',
|
||||
style: TextStyle(
|
||||
fontSize: 22.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: wordLenSelectionColors[wordLen - 4]!,
|
||||
),
|
||||
),
|
||||
),
|
||||
SelectionGroupProvider(
|
||||
defaultSelection: 5,
|
||||
onChanged: (sel) => setState(() => wordLen = sel),
|
||||
selections: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
for(int i = 4; i <= 8; i++)
|
||||
generateSelectionBox(
|
||||
id: i,
|
||||
width: 80.0,
|
||||
height: 80.0,
|
||||
color: wordLenSelectionColors[i - 4]!,
|
||||
primaryText: '$i',
|
||||
primaryTextSize: 25.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 150.0,
|
||||
decoration: BoxDecoration(
|
||||
color: chancesSelectionColors[maxChances - 1]!.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(15.0, 10.0, 0.0, 10.0),
|
||||
child: Text(
|
||||
'Max Attempts',
|
||||
style: TextStyle(
|
||||
fontSize: 22.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: chancesSelectionColors[maxChances - 1]!,
|
||||
),
|
||||
),
|
||||
),
|
||||
SelectionGroupProvider(
|
||||
defaultSelection: 6,
|
||||
onChanged: (sel) => setState(() => maxChances = sel),
|
||||
selections: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
for(int i = 1; i <= 8; i++)
|
||||
generateSelectionBox(
|
||||
id: i,
|
||||
width: 80.0,
|
||||
height: 80.0,
|
||||
color: chancesSelectionColors[i - 1]!,
|
||||
primaryText: '$i',
|
||||
primaryTextSize: 25.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: (dicBook[dicBookIndex][3]! as Color).withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
margin: const EdgeInsets.all(10.0),
|
||||
padding: const EdgeInsets.all(15.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(15.0, 10.0, 0.0, 10.0),
|
||||
child: Text(
|
||||
'Word List',
|
||||
style: TextStyle(
|
||||
fontSize: 22.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: (dicBook[dicBookIndex][3]! as Color),
|
||||
),
|
||||
),
|
||||
),
|
||||
SelectionGroupProvider(
|
||||
defaultSelection: 0,
|
||||
onChanged: (sel) => setState(() => dicBookIndex = sel),
|
||||
selections: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: dicBookSelections,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(30.0),
|
||||
child: Text('Loading library',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[850]!,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
padding: const EdgeInsets.only(bottom: 10.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.teal.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
margin: const EdgeInsets.all(10.0),
|
||||
child: InkWell(
|
||||
onTap: (){
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
|
||||
return LoadingPage(dicName: dicBook[dicBookIndex][0] as String, wordLen: wordLen, maxChances: maxChances, gameMode: 0);
|
||||
}));
|
||||
},
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 30.0),
|
||||
child: const Text(
|
||||
'Start Normal',
|
||||
style: TextStyle(
|
||||
fontSize: 22.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.teal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Expanded(
|
||||
// child: Container(
|
||||
// decoration: BoxDecoration(
|
||||
// color: Colors.pink[200]!.withOpacity(0.2),
|
||||
// borderRadius: BorderRadius.circular(10.0),
|
||||
// ),
|
||||
// margin: const EdgeInsets.all(10.0),
|
||||
// child: InkWell(
|
||||
// onTap: (){},
|
||||
// borderRadius: BorderRadius.circular(10.0),
|
||||
// child: Container(
|
||||
// alignment: Alignment.center,
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 30.0),
|
||||
// child: Text(
|
||||
// 'Start Hard',
|
||||
// style: TextStyle(
|
||||
// fontSize: 22.0,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// color: Colors.pink[400],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -188,16 +442,4 @@ class _HomePageState extends State<HomePage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Scaffold(
|
||||
// body: Center(
|
||||
// child: ElevatedButton(
|
||||
// child: const Text('Offline'),
|
||||
// onPressed: () => Navigator.of(context).pushNamed("/Offline"),
|
||||
// )
|
||||
// )
|
||||
// );
|
||||
}
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 20:41:41
|
||||
* @LastEditTime : 2022-03-07 14:40:37
|
||||
* @Description : Offline page
|
||||
*/
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wordle/event_bus.dart';
|
||||
import 'package:wordle/validation_provider.dart';
|
||||
import './display_pannel.dart';
|
||||
|
||||
class OfflinePage extends StatefulWidget {
|
||||
const OfflinePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<OfflinePage> createState() => _OfflinePageState();
|
||||
}
|
||||
|
||||
class _OfflinePageState extends State<OfflinePage> with TickerProviderStateMixin{
|
||||
@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,
|
||||
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: []),
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
144
lib/popper_generator.dart
Normal file
144
lib/popper_generator.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
17
lib/scroll_behav.dart
Normal file
17
lib/scroll_behav.dart
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-10 22:07:28
|
||||
* @LastEditTime : 2022-03-10 22:07:29
|
||||
* @Description : Scroll behavior
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
|
||||
class MyScrollBehavior extends ScrollBehavior {
|
||||
@override
|
||||
Set<PointerDeviceKind> get dragDevices => {
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.mouse,
|
||||
};
|
||||
}
|
||||
52
lib/selection_group.dart
Normal file
52
lib/selection_group.dart
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-09 22:33:34
|
||||
* @LastEditTime : 2022-03-10 13:48:48
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import './group_shared.dart';
|
||||
|
||||
class SelectionGroupProvider extends StatefulWidget {
|
||||
const SelectionGroupProvider({
|
||||
Key? key,
|
||||
required this.selections,
|
||||
required this.onChanged,
|
||||
this.defaultSelection = 0,
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget selections;
|
||||
final int defaultSelection;
|
||||
final void Function(int) onChanged;
|
||||
|
||||
@override
|
||||
State<SelectionGroupProvider> createState() => _SelectionGroupProviderState();
|
||||
}
|
||||
|
||||
class _SelectionGroupProviderState extends State<SelectionGroupProvider> {
|
||||
int selected = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selected = widget.defaultSelection;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GroupSharedData(
|
||||
child: NotificationListener<SelectNotification>(
|
||||
child: widget.selections,
|
||||
onNotification: (noti) {
|
||||
setState(() {
|
||||
selected = noti.selection;
|
||||
widget.onChanged(selected);
|
||||
});
|
||||
return true;
|
||||
},
|
||||
),
|
||||
selected: selected,
|
||||
);
|
||||
}
|
||||
}
|
||||
362
lib/single_selection.dart
Normal file
362
lib/single_selection.dart
Normal file
@ -0,0 +1,362 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-09 22:33:41
|
||||
* @LastEditTime : 2022-03-11 14:01:14
|
||||
* @Description : Single Selection Box
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:math';
|
||||
import './group_shared.dart';
|
||||
|
||||
class SingleSelectionBox extends StatefulWidget {
|
||||
const SingleSelectionBox({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.unselectedChild,
|
||||
required this.selectedChild,
|
||||
//These two are displayed by animation widget so not include in the decoration parameter
|
||||
required this.selectedBackgroundColor,
|
||||
required this.unselectedBackgroundColor,
|
||||
this.shadowBlurRadius = 8,
|
||||
this.shadowSpreadRadius = 0,
|
||||
this.unselectedShadowColor = Colors.transparent,
|
||||
this.selectedShadowColor = Colors.transparent,
|
||||
this.borderRadius = 0.0,
|
||||
this.borderColor = Colors.transparent,
|
||||
this.borderWidth = 0.0,
|
||||
}):
|
||||
super(key: key);
|
||||
|
||||
final int id;
|
||||
final Widget unselectedChild;
|
||||
final Widget selectedChild;
|
||||
final Color selectedBackgroundColor;
|
||||
final Color unselectedBackgroundColor;
|
||||
final double shadowBlurRadius;
|
||||
final double shadowSpreadRadius;
|
||||
final Color unselectedShadowColor;
|
||||
final Color selectedShadowColor;
|
||||
final double borderRadius;
|
||||
final Color borderColor;
|
||||
final double borderWidth;
|
||||
|
||||
@override
|
||||
State<SingleSelectionBox> createState() => _SingleSelectionBoxState();
|
||||
}
|
||||
|
||||
class _SingleSelectionBoxState extends State<SingleSelectionBox> with TickerProviderStateMixin{
|
||||
bool selected = false;
|
||||
|
||||
//Record last pressed shift from center point
|
||||
double pressedX = 0;
|
||||
double pressedY = 0;
|
||||
|
||||
//Grow animation controller
|
||||
late final AnimationController _scaleController;
|
||||
late final AnimationController _opacController;
|
||||
late final AnimationController _shrinkController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scaleController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 750),
|
||||
);
|
||||
_opacController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 750),
|
||||
);
|
||||
_shrinkController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
);
|
||||
if(context.findAncestorWidgetOfExactType<GroupSharedData>()!.selected == widget.id) {
|
||||
_scaleController.value = _scaleController.upperBound;
|
||||
selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
var newData = context.findAncestorWidgetOfExactType<GroupSharedData>()!;
|
||||
if(newData.selected != widget.id && selected) {
|
||||
_opacController.forward();
|
||||
selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var data = GroupSharedData.of(context)!;
|
||||
//Use gesture detector to detect the position pressed for animations
|
||||
return LayoutBuilder(
|
||||
builder:(context, constraints) {
|
||||
var decoration = BoxDecoration(
|
||||
boxShadow: [BoxShadow(
|
||||
color: data.selected == widget.id ? widget.selectedShadowColor : widget.unselectedShadowColor,
|
||||
blurRadius: widget.shadowBlurRadius,
|
||||
spreadRadius: widget.shadowSpreadRadius,
|
||||
)],
|
||||
color: widget.unselectedBackgroundColor,
|
||||
border: widget.borderWidth > 0 ? Border.all(
|
||||
color: data.selected == widget.id ? widget.selectedBackgroundColor : widget.borderColor,
|
||||
width: widget.borderWidth
|
||||
) : null,
|
||||
borderRadius: BorderRadius.circular(widget.borderRadius),
|
||||
);
|
||||
return GestureDetector(
|
||||
//Stack best describes the relation between the animation and the container box
|
||||
child: AnimatedBuilder(
|
||||
animation: _shrinkController,
|
||||
builder: (context, child) {
|
||||
var _shrinkAnimation = Tween<double>(begin: 1, end: 0.9).animate(CurvedAnimation(
|
||||
parent: _shrinkController,
|
||||
curve: Curves.elasticOut,
|
||||
reverseCurve: Curves.elasticIn,
|
||||
));
|
||||
return Transform.scale(
|
||||
scale: _shrinkAnimation.value,
|
||||
alignment: Alignment.center,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
//Container
|
||||
Positioned.fill(
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 750),
|
||||
decoration: decoration,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
//Animation ( similar to ink well effect )
|
||||
Positioned.fill(
|
||||
child: AnimatedBuilder(
|
||||
animation: _scaleController,
|
||||
child: AnimatedBuilder(
|
||||
animation: _opacController,
|
||||
builder: (context, child) {
|
||||
num topLeftSquredDis = pow(pressedX, 2) + pow(pressedY, 2);
|
||||
num topRightSquredDis = pow(constraints.maxWidth - pressedX, 2) + pow(pressedY, 2);
|
||||
num botLeftSquredDis = pow(pressedX, 2) + pow(constraints.maxHeight - pressedY, 2);
|
||||
num botRightSquredDis = pow(constraints.maxWidth - pressedX, 2) + pow(constraints.maxHeight - pressedY, 2);
|
||||
num radius = sqrt(max(max(topLeftSquredDis, topRightSquredDis), max(botLeftSquredDis, botRightSquredDis)));
|
||||
var _opacityAnimation = Tween<double>(begin: 1, end: 0).animate(_opacController);
|
||||
return OverflowBox(
|
||||
maxHeight: radius * 2,
|
||||
maxWidth: radius * 2,
|
||||
child: SizedBox(
|
||||
child: Opacity(
|
||||
opacity: _opacityAnimation.value,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: widget.selectedBackgroundColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
width: radius * 2,
|
||||
height: radius * 2,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
builder: (context, child) {
|
||||
var _scaleAnimation = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(
|
||||
parent: _scaleController,
|
||||
curve: Curves.easeInOutCubic,
|
||||
));
|
||||
return ClipRRect(
|
||||
child: Transform.translate(
|
||||
child: Transform.scale(
|
||||
scale: _scaleAnimation.value,
|
||||
child: child,
|
||||
),
|
||||
offset: Offset(pressedX - constraints.maxWidth / 2, pressedY - constraints.maxHeight / 2),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
borderRadius: BorderRadius.circular(widget.borderRadius),
|
||||
);
|
||||
},
|
||||
)
|
||||
),
|
||||
Positioned(
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 750),
|
||||
child: data.selected == widget.id ? widget.selectedChild : widget.unselectedChild,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTapUp: (detail) {
|
||||
setState(() {
|
||||
selected = true;
|
||||
//Notify the group manager
|
||||
SelectNotification(selection: widget.id).dispatch(context);
|
||||
//Record the coordinate and start the animation
|
||||
pressedX = detail.localPosition.dx;
|
||||
pressedY = detail.localPosition.dy;
|
||||
_opacController.reset();
|
||||
_scaleController.reset();
|
||||
_scaleController.forward();
|
||||
_shrinkController.reverse();
|
||||
});
|
||||
},
|
||||
onTapDown: ((detail) {
|
||||
_shrinkController.value = _shrinkController.upperBound;
|
||||
}),
|
||||
onTapCancel: (() {
|
||||
_shrinkController.reverse();
|
||||
}),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget generateSelectionBox({
|
||||
required int id,
|
||||
required double width,
|
||||
required double height,
|
||||
required Color color,
|
||||
required String primaryText,
|
||||
required double primaryTextSize,
|
||||
String? secondaryText,
|
||||
double? secondaryTextSize,
|
||||
String? decorationText,
|
||||
Alignment alignment = Alignment.center,
|
||||
EdgeInsets padding = EdgeInsets.zero,
|
||||
}) {
|
||||
return Container(
|
||||
width: width,
|
||||
height: height,
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: SingleSelectionBox(
|
||||
id: id,
|
||||
unselectedBackgroundColor: Colors.white.withOpacity(0.6),
|
||||
selectedBackgroundColor: color,
|
||||
//unselectedShadowColor: Colors.grey[400]!,
|
||||
//selectedShadowColor: color,
|
||||
borderRadius: 10.0,
|
||||
borderWidth: 0.0,
|
||||
unselectedChild: Stack(
|
||||
key: const ValueKey(0),
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Align(
|
||||
alignment: alignment,
|
||||
child: Padding(
|
||||
padding: padding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: Text(primaryText, style: TextStyle(
|
||||
color: Colors.grey[850],
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: primaryTextSize,
|
||||
),),
|
||||
),
|
||||
if(secondaryText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: Text(secondaryText, style: TextStyle(
|
||||
color: Colors.grey[850]!.withOpacity(0.8),
|
||||
fontSize: secondaryTextSize,
|
||||
),),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if(decorationText != null)
|
||||
Positioned.fill(
|
||||
child: ClipRRect(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Transform.translate(
|
||||
offset: const Offset(15, 35),
|
||||
child: Text(
|
||||
decorationText,
|
||||
style: TextStyle(
|
||||
color: color.withOpacity(0.3),
|
||||
fontSize: 120.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
selectedChild: Stack(
|
||||
key: const ValueKey(1),
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Align(
|
||||
alignment: alignment,
|
||||
child: Padding(
|
||||
padding: padding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: Text(primaryText, style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: primaryTextSize,
|
||||
),),
|
||||
),
|
||||
if(secondaryText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: Text(secondaryText, style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
fontSize: secondaryTextSize,
|
||||
),),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if(decorationText != null)
|
||||
Positioned.fill(
|
||||
child: ClipRRect(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Transform.translate(
|
||||
offset: const Offset(15, 35),
|
||||
child: Text(
|
||||
decorationText,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.3),
|
||||
fontSize: 120.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 21:40:51
|
||||
* @LastEditTime : 2022-03-07 14:40:46
|
||||
* @LastEditTime : 2022-03-11 14:54:02
|
||||
* @Description : Validation Provider class
|
||||
*/
|
||||
|
||||
@ -19,9 +19,15 @@ class InputNotification extends Notification {
|
||||
}
|
||||
|
||||
class ValidationProvider extends StatefulWidget {
|
||||
const ValidationProvider({Key? key, required this.child}) : super(key: key);
|
||||
static Set<String> validationDatabase = <String>{};
|
||||
|
||||
const ValidationProvider({Key? key, required this.child, required this.database, required this.wordLen, required this.maxChances, required this.gameMode}) : super(key: key);
|
||||
|
||||
final Widget child;
|
||||
final Map<String, List<String>> database;
|
||||
final int wordLen;
|
||||
final int maxChances;
|
||||
final int gameMode;
|
||||
|
||||
@override
|
||||
State<ValidationProvider> createState() => _ValidationProviderState();
|
||||
@ -38,12 +44,14 @@ class _ValidationProviderState extends State<ValidationProvider> {
|
||||
_newGame();
|
||||
}
|
||||
|
||||
void _newGame() async {
|
||||
void _newGame() {
|
||||
curAttempt = "";
|
||||
curAttemptCount = 0;
|
||||
acceptInput = true;
|
||||
answer = await Words.generateWord();
|
||||
answer = answer.toUpperCase();
|
||||
answer = getNextWord(widget.database);
|
||||
if(!ValidationProvider.validationDatabase.contains(answer)){
|
||||
ValidationProvider.validationDatabase.add(answer);
|
||||
}
|
||||
letterMap = {};
|
||||
answer.split('').forEach((c) {
|
||||
letterMap[c] ??= 0;
|
||||
@ -72,7 +80,7 @@ class _ValidationProviderState extends State<ValidationProvider> {
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Result'),
|
||||
content: Text(result ? "Won" : "Lost, answer is $answer"),
|
||||
content: Text(result ? "Won" : "Lost, answer is ${answer.toLowerCase()}"),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('Back'),
|
||||
@ -112,25 +120,25 @@ class _ValidationProviderState extends State<ValidationProvider> {
|
||||
child: widget.child,
|
||||
onNotification: (noti) {
|
||||
if(noti.type == InputType.inputConfirmation) {
|
||||
if(curAttempt.length < 5) {
|
||||
if(curAttempt.length < widget.wordLen) {
|
||||
//Not enough
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
//Check validation
|
||||
if(Words.isWordValidate(curAttempt)) {
|
||||
if(ValidationProvider.validationDatabase.lookup(curAttempt) != null) {
|
||||
//Generate map
|
||||
Map<String, int> leftWordMap = Map.from(letterMap);
|
||||
var positionValRes = <int>[for(int i = 0; i < 5; i++) -1];
|
||||
var positionValRes = <int>[for(int i = 0; i < widget.wordLen; i++) -1];
|
||||
var letterValRes = <String, int>{};
|
||||
for(int i = 0; i < 5; i++) {
|
||||
for(int i = 0; i < widget.wordLen; i++) {
|
||||
if(curAttempt[i] == answer[i]) {
|
||||
positionValRes[i] = 1;
|
||||
leftWordMap[curAttempt[i]] = leftWordMap[curAttempt[i]]! - 1;
|
||||
letterValRes[curAttempt[i]] = 1;
|
||||
}
|
||||
}
|
||||
for(int i = 0; i < 5; i++) {
|
||||
for(int i = 0; i < widget.wordLen; i++) {
|
||||
if(curAttempt[i] != answer[i] && leftWordMap[curAttempt[i]] != null && leftWordMap[curAttempt[i]]! > 0) {
|
||||
positionValRes[i] = 2;
|
||||
leftWordMap[curAttempt[i]] = leftWordMap[curAttempt[i]]! - 1;
|
||||
@ -178,7 +186,7 @@ class _ValidationProviderState extends State<ValidationProvider> {
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(acceptInput && curAttempt.length < 5) {
|
||||
if(acceptInput && curAttempt.length < widget.wordLen) {
|
||||
curAttempt += noti.msg;
|
||||
}
|
||||
}
|
||||
|
||||
56
privacy.md
Normal file
56
privacy.md
Normal file
@ -0,0 +1,56 @@
|
||||
**Privacy Policy**
|
||||
|
||||
Linloir built the Wordle app as an Open Source app. This SERVICE is provided by Linloir at no cost and is intended for use as is.
|
||||
|
||||
This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.
|
||||
|
||||
If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.
|
||||
|
||||
The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which are accessible at Wordle unless otherwise defined in this Privacy Policy.
|
||||
|
||||
**Information Collection and Use**
|
||||
|
||||
For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information. The information that I request will be retained on your device and is not collected by me in any way.
|
||||
|
||||
**Log Data**
|
||||
|
||||
I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third-party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics.
|
||||
|
||||
**Cookies**
|
||||
|
||||
Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.
|
||||
|
||||
This Service does not use these “cookies” explicitly. However, the app may use third-party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.
|
||||
|
||||
**Service Providers**
|
||||
|
||||
I may employ third-party companies and individuals due to the following reasons:
|
||||
|
||||
* To facilitate our Service;
|
||||
* To provide the Service on our behalf;
|
||||
* To perform Service-related services; or
|
||||
* To assist us in analyzing how our Service is used.
|
||||
|
||||
I want to inform users of this Service that these third parties have access to their Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.
|
||||
|
||||
**Security**
|
||||
|
||||
I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security.
|
||||
|
||||
**Links to Other Sites**
|
||||
|
||||
This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
|
||||
|
||||
**Children’s Privacy**
|
||||
|
||||
I do not knowingly collect personally identifiable information from children. I encourage all children to never submit any personally identifiable information through the Application and/or Services. I encourage parents and legal guardians to monitor their children's Internet usage and to help enforce this Policy by instructing their children never to provide personally identifiable information through the Application and/or Services without their permission. If you have reason to believe that a child has provided personally identifiable information to us through the Application and/or Services, please contact us. You must also be at least 16 years of age to consent to the processing of your personally identifiable information in your country (in some countries we may allow your parent or guardian to do so on your behalf).
|
||||
|
||||
**Changes to This Privacy Policy**
|
||||
|
||||
I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page.
|
||||
|
||||
This policy is effective as of 2022-03-10
|
||||
|
||||
**Contact Us**
|
||||
|
||||
If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at zhangchy88@mail2.sysu.edu.cn.
|
||||
99
pubspec.lock
99
pubspec.lock
@ -57,6 +57,20 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -109,6 +123,76 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.9"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.12"
|
||||
path_provider_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_ios
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -170,5 +254,20 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0+1"
|
||||
sdks:
|
||||
dart: ">=2.16.1 <3.0.0"
|
||||
flutter: ">=2.8.0"
|
||||
|
||||
14
pubspec.yaml
14
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: 1.0.0315+20220315
|
||||
|
||||
environment:
|
||||
sdk: ">=2.16.1 <3.0.0"
|
||||
@ -34,6 +34,7 @@ dependencies:
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
path_provider: ^2.0.9
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -62,9 +63,14 @@ flutter:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
assets:
|
||||
- assets/unixWords.txt
|
||||
- assets/ospd.txt
|
||||
- assets/popular.txt
|
||||
- 'assets/All.txt'
|
||||
- 'assets/CET4.txt'
|
||||
- 'assets/CET6.txt'
|
||||
- 'assets/CET4 + 6.txt'
|
||||
- 'assets/GRE Slim.txt'
|
||||
- 'assets/TOEFL Slim.txt'
|
||||
- 'assets/TOEFL.txt'
|
||||
- 'assets/HighSchool.txt'
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user