mirror of
https://github.com/Linloir/Flutter-Wordle.git
synced 2025-12-16 22:18:10 +08:00
Merge branch 'temp' into main
This commit is contained in:
commit
38ccdfa476
13866
assets/All.txt
Normal file
13866
assets/All.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
8028
assets/CET46.txt
Normal file
8028
assets/CET46.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-08 21:47:38
|
||||
* @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,7 +31,7 @@ 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];
|
||||
});
|
||||
@ -44,7 +47,7 @@ class _WordleDisplayWidgetState extends State<WordleDisplayWidget> with TickerPr
|
||||
onAnimation = false;
|
||||
r++;
|
||||
c = 0;
|
||||
if(r == 6 || result == true) {
|
||||
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) {
|
||||
@ -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;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 20:41:41
|
||||
* @LastEditTime : 2022-03-09 13:10:14
|
||||
* @LastEditTime : 2022-03-11 14:54:15
|
||||
* @Description : Offline page
|
||||
*/
|
||||
|
||||
@ -14,14 +14,19 @@ import 'package:wordle/display_pannel.dart';
|
||||
import 'package:wordle/instruction_pannel.dart';
|
||||
import 'package:wordle/popper_generator.dart';
|
||||
|
||||
class OfflinePage extends StatefulWidget {
|
||||
const OfflinePage({Key? key}) : super(key: key);
|
||||
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<OfflinePage> createState() => _OfflinePageState();
|
||||
State<GamePage> createState() => _GamePageState();
|
||||
}
|
||||
|
||||
class _OfflinePageState extends State<OfflinePage> with TickerProviderStateMixin{
|
||||
class _GamePageState extends State<GamePage> with TickerProviderStateMixin{
|
||||
late AnimationController _controller;
|
||||
|
||||
void _onGameEnd(dynamic args) {
|
||||
@ -68,7 +73,7 @@ class _OfflinePageState extends State<OfflinePage> with TickerProviderStateMixin
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
title: const Text('WORDLE OFFLINE'),
|
||||
title: const Text('WORDLE'),
|
||||
centerTitle: true,
|
||||
//iconTheme: const IconThemeData(color: Colors.black),
|
||||
actions: [
|
||||
@ -118,8 +123,12 @@ class _OfflinePageState extends State<OfflinePage> with TickerProviderStateMixin
|
||||
],
|
||||
),
|
||||
body: Container(
|
||||
child: const ValidationProvider(
|
||||
child: WordleDisplayWidget(),
|
||||
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,
|
||||
),
|
||||
@ -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;
|
||||
});
|
||||
} 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;
|
||||
}
|
||||
|
||||
static Future<String> generateWord() async {
|
||||
int bound = dataBase.length;
|
||||
if(bound == 0) {
|
||||
try{
|
||||
await Words.importWordsDatabase(length: _length);
|
||||
} catch (e) {
|
||||
return 'crash';
|
||||
}
|
||||
bound = dataBase.length;
|
||||
}
|
||||
var rng = Random();
|
||||
String res = dataBase.elementAt(rng.nextInt(bound));
|
||||
if(dictionary.lookup(res) == null) {
|
||||
dictionary.add(res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool isWordValidate(String word) {
|
||||
return dictionary.lookup(word.toLowerCase()) != null;
|
||||
}
|
||||
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());
|
||||
});
|
||||
return database;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
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();
|
||||
return database.keys.elementAt(rng.nextInt(database.length));
|
||||
}
|
||||
|
||||
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(() {
|
||||
_keyState[key] = value;
|
||||
setState(() {
|
||||
_cache.forEach((key, value) {
|
||||
if(_keyState[key] != 1){
|
||||
_keyState[key] = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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),),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
472
lib/main.dart
472
lib/main.dart
@ -1,15 +1,41 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-03-05 20:21:34
|
||||
* @LastEditTime : 2022-03-07 14:40:28
|
||||
* @LastEditTime : 2022-03-11 14:56:28
|
||||
* @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';
|
||||
|
||||
void main() {
|
||||
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,137 +94,352 @@ 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){
|
||||
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,
|
||||
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
|
||||
),
|
||||
),
|
||||
// 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
if(snapshot.connectionState == ConnectionState.done) {
|
||||
var mode = Theme.of(context).brightness;
|
||||
return Scaffold(
|
||||
body: Align(
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 960.0),
|
||||
child: Column(
|
||||
children: [
|
||||
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)),
|
||||
),
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
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);
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
const Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: OfflinePage(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
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(() {}),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('Ignore'),
|
||||
onPressed: () {
|
||||
setState((){
|
||||
_ignorance = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
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(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
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(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 60.0,
|
||||
width: 60.0,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 6.0,
|
||||
color: Colors.grey[850],
|
||||
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]!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(30.0),
|
||||
child: Text('Loading library',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[850]!,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
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.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],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Scaffold(
|
||||
// body: Center(
|
||||
// child: ElevatedButton(
|
||||
// child: const Text('Offline'),
|
||||
// onPressed: () => Navigator.of(context).pushNamed("/Offline"),
|
||||
// )
|
||||
// )
|
||||
// );
|
||||
}
|
||||
}
|
||||
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-08 22:19:08
|
||||
* @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,13 +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();
|
||||
//print(answer);
|
||||
answer = getNextWord(widget.database);
|
||||
if(!ValidationProvider.validationDatabase.contains(answer)){
|
||||
ValidationProvider.validationDatabase.add(answer);
|
||||
}
|
||||
letterMap = {};
|
||||
answer.split('').forEach((c) {
|
||||
letterMap[c] ??= 0;
|
||||
@ -73,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'),
|
||||
@ -113,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;
|
||||
@ -179,7 +186,7 @@ class _ValidationProviderState extends State<ValidationProvider> {
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(acceptInput && curAttempt.length < 5) {
|
||||
if(acceptInput && curAttempt.length < widget.wordLen) {
|
||||
curAttempt += noti.msg;
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
12
pubspec.yaml
12
pubspec.yaml
@ -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/CET46.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