mirror of
https://forgejo.allbyte.fr/nono/flutter_wordle
synced 2026-03-14 21:15:45 +01:00
Initial commit
This commit is contained in:
commit
d0dff4834a
53 changed files with 3156 additions and 0 deletions
43
.gitignore
vendored
Normal file
43
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
30
.metadata
Normal file
30
.metadata
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
- platform: android
|
||||
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
16
README.md
Normal file
16
README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# wordle
|
||||
|
||||
Wordle
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
28
analysis_options.yaml
Normal file
28
analysis_options.yaml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
13
android/.gitignore
vendored
Normal file
13
android/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
44
android/app/build.gradle
Normal file
44
android/app/build.gradle
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "fr.achevalm.wordle"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "fr.achevalm.wordle"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
7
android/app/src/debug/AndroidManifest.xml
Normal file
7
android/app/src/debug/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
45
android/app/src/main/AndroidManifest.xml
Normal file
45
android/app/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="wordle"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package fr.achevalm.wordle
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity()
|
||||
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
18
android/app/src/main/res/values-night/styles.xml
Normal file
18
android/app/src/main/res/values-night/styles.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
18
android/app/src/main/res/values/styles.xml
Normal file
18
android/app/src/main/res/values/styles.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
7
android/app/src/profile/AndroidManifest.xml
Normal file
7
android/app/src/profile/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
18
android/build.gradle
Normal file
18
android/build.gradle
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
3
android/gradle.properties
Normal file
3
android/gradle.properties
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
|
||||
25
android/settings.gradle
Normal file
25
android/settings.gradle
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.1.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
1
assets/english_words.json
Normal file
1
assets/english_words.json
Normal file
File diff suppressed because one or more lines are too long
1
assets/french_words.json
Normal file
1
assets/french_words.json
Normal file
File diff suppressed because one or more lines are too long
1
assets/spanish_words.json
Normal file
1
assets/spanish_words.json
Normal file
File diff suppressed because one or more lines are too long
4
devtools_options.yaml
Normal file
4
devtools_options.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
- provider: true
|
||||
36
lib/LetterColoration.dart
Normal file
36
lib/LetterColoration.dart
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class LetterColoration {
|
||||
static List<Color> getColorFeedback(
|
||||
String guess, String targetWord, bool submitted) {
|
||||
if (!submitted) {
|
||||
return List.filled(targetWord.length, Colors.grey[800]!);
|
||||
}
|
||||
List<Color> feedbackColors =
|
||||
List.filled(targetWord.length, Colors.grey[700]!);
|
||||
List<bool> targetMatched = List.filled(targetWord.length, false);
|
||||
|
||||
for (int i = 0; i < guess.length; i++) {
|
||||
if (guess[i] == targetWord[i]) {
|
||||
feedbackColors[i] = Colors.green;
|
||||
targetMatched[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < guess.length; i++) {
|
||||
if (feedbackColors[i] == Colors.green) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < targetWord.length; j++) {
|
||||
if (!targetMatched[j] && guess[i] == targetWord[j]) {
|
||||
feedbackColors[i] = Colors.orange;
|
||||
targetMatched[j] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return feedbackColors;
|
||||
}
|
||||
}
|
||||
68
lib/classic_game_history.dart
Normal file
68
lib/classic_game_history.dart
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wordle/db/classic_game.dart';
|
||||
|
||||
import 'db/classic_game_provider.dart';
|
||||
import 'game_screen.dart';
|
||||
|
||||
class ClassicGameHistoryScreen extends StatelessWidget {
|
||||
const ClassicGameHistoryScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Classic Game History'),
|
||||
),
|
||||
body: FutureBuilder<List<DbClassicGame>>(
|
||||
future: Provider.of<DbClassicGameProvider>(context, listen: false)
|
||||
.getAllClassicGames(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return const Center(child: Text('No classic games found.'));
|
||||
} else {
|
||||
final List<DbClassicGame> games = snapshot.data!;
|
||||
return ListView.builder(
|
||||
itemCount: games.length,
|
||||
itemBuilder: (context, index) {
|
||||
final game = games[index];
|
||||
return Card(
|
||||
margin:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
child: ListTile(
|
||||
title: Text('Game on ${game.date.toString()}'),
|
||||
subtitle: Text(
|
||||
'Word: ${game.board.correctWord}, Max Guesses: ${game.board.maxGuesses}, Has Won: ${game.board.hasWon ? "Yes" : "No"}'),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
game.board.correctWord.toUpperCase()),
|
||||
),
|
||||
body: Center(
|
||||
child: GameScreen(
|
||||
guesses: game.board.guesses,
|
||||
maxGuesses: game.board.maxGuesses,
|
||||
correctWord: game.board.correctWord,
|
||||
readOnly: true,
|
||||
),
|
||||
),
|
||||
)));
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
125
lib/classic_options_screen.dart
Normal file
125
lib/classic_options_screen.dart
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:wordle/game_screen.dart';
|
||||
import 'package:wordle/validated_number_input.dart';
|
||||
import 'package:wordle/word_loader.dart';
|
||||
|
||||
class ClassicGame extends StatefulWidget {
|
||||
const ClassicGame({super.key});
|
||||
|
||||
@override
|
||||
_ClassicGameState createState() => _ClassicGameState();
|
||||
}
|
||||
|
||||
class _ClassicGameState extends State<ClassicGame> {
|
||||
int selectedWordLength = 5;
|
||||
int selectedMaxGuesses = 6;
|
||||
bool isWordLengthValid = true;
|
||||
bool isMaxGuessesValid = true;
|
||||
late String correctWord;
|
||||
|
||||
void _resetDefaults() {
|
||||
setState(() {
|
||||
selectedWordLength = 5;
|
||||
selectedMaxGuesses = 6;
|
||||
});
|
||||
}
|
||||
|
||||
bool get _canStartGame => isWordLengthValid && isMaxGuessesValid;
|
||||
|
||||
Future<void> _startGame() async {
|
||||
String? word = await WordLoader.getWordOfLength(selectedWordLength);
|
||||
if (word == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'No words available for the selected length. Please choose another length.'),
|
||||
),
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
correctWord = word;
|
||||
}
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Classic"),
|
||||
),
|
||||
body: Center(
|
||||
child: GameScreen(
|
||||
maxGuesses: selectedMaxGuesses,
|
||||
correctWord: correctWord,
|
||||
))),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Game Options"),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
"Word Length",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
ValidatedNumberInput(
|
||||
defaultValue: selectedWordLength,
|
||||
minValue: 5,
|
||||
maxValue: 25,
|
||||
onValueChanged: (value) {
|
||||
setState(() {
|
||||
selectedWordLength = value;
|
||||
});
|
||||
},
|
||||
onValidityChanged: (isValid) {
|
||||
setState(() {
|
||||
isWordLengthValid = isValid;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24.0),
|
||||
const Text(
|
||||
"Number of Guesses",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
ValidatedNumberInput(
|
||||
defaultValue: selectedMaxGuesses,
|
||||
minValue: 2,
|
||||
onValueChanged: (value) {
|
||||
setState(() {
|
||||
selectedMaxGuesses = value;
|
||||
});
|
||||
},
|
||||
onValidityChanged: (isValid) {
|
||||
setState(() {
|
||||
isMaxGuessesValid = isValid;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 32.0),
|
||||
ElevatedButton(
|
||||
onPressed: _canStartGame ? _startGame : null,
|
||||
child: const Text("Start Game"),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
ElevatedButton(
|
||||
onPressed: _resetDefaults,
|
||||
child: const Text("Reset to Default Values"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
42
lib/db/board.dart
Normal file
42
lib/db/board.dart
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
const String columnId = 'id';
|
||||
const String tableBoard = 'board';
|
||||
const String columnCorrectWord = 'correctWord';
|
||||
const String columnMaxGuesses = 'maxGuesses';
|
||||
const String columnGuesses = 'guesses';
|
||||
const String columnHasWon = 'hasWon';
|
||||
|
||||
class DbBoard {
|
||||
int? id;
|
||||
String correctWord;
|
||||
int maxGuesses;
|
||||
List<String> guesses;
|
||||
bool hasWon;
|
||||
|
||||
DbBoard({
|
||||
this.id,
|
||||
required this.correctWord,
|
||||
required this.maxGuesses,
|
||||
required this.guesses,
|
||||
required this.hasWon,
|
||||
});
|
||||
|
||||
Map<String, Object?> toMap() {
|
||||
return {
|
||||
columnCorrectWord: correctWord,
|
||||
columnMaxGuesses: maxGuesses,
|
||||
columnGuesses: guesses.join(","),
|
||||
columnHasWon: hasWon ? 1 : 0,
|
||||
if (id != null) columnId: id,
|
||||
};
|
||||
}
|
||||
|
||||
factory DbBoard.fromMap(Map<String, Object?> map) {
|
||||
return DbBoard(
|
||||
id: map[columnId] as int?,
|
||||
correctWord: map[columnCorrectWord] as String,
|
||||
maxGuesses: map[columnMaxGuesses] as int,
|
||||
guesses: (map[columnGuesses] as String).split(","),
|
||||
hasWon: (map[columnHasWon] as int) == 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
37
lib/db/classic_game.dart
Normal file
37
lib/db/classic_game.dart
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'board.dart';
|
||||
|
||||
const String tableClassicHistory = 'classic_game_history';
|
||||
const String columnBoardId = 'board_id';
|
||||
const String columnId = 'id';
|
||||
const String columnDate = 'date';
|
||||
|
||||
// DbClassicGame class
|
||||
class DbClassicGame {
|
||||
int? id;
|
||||
DateTime date;
|
||||
DbBoard board;
|
||||
|
||||
DbClassicGame({
|
||||
this.id,
|
||||
required this.date,
|
||||
required this.board,
|
||||
});
|
||||
|
||||
Map<String, Object?> toMap() {
|
||||
return {
|
||||
if (id != null) columnId: id,
|
||||
columnDate: date.toIso8601String(),
|
||||
'board': jsonEncode(board.toMap()),
|
||||
};
|
||||
}
|
||||
|
||||
factory DbClassicGame.fromMap(Map<String, Object?> map, DbBoard board) {
|
||||
return DbClassicGame(
|
||||
id: map[columnId] as int?,
|
||||
date: DateTime.parse(map[columnDate] as String),
|
||||
board: board,
|
||||
);
|
||||
}
|
||||
}
|
||||
48
lib/db/classic_game_provider.dart
Normal file
48
lib/db/classic_game_provider.dart
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
import 'board.dart';
|
||||
import 'classic_game.dart';
|
||||
|
||||
class DbClassicGameProvider extends ChangeNotifier {
|
||||
Database? _db;
|
||||
|
||||
void initialize(Database db) {
|
||||
_db = db;
|
||||
}
|
||||
|
||||
Future<void> insertClassicGame(DbClassicGame game) async {
|
||||
if (_db == null) throw Exception('Database has not been initialized');
|
||||
|
||||
int boardId = await _db!.insert('board', game.board.toMap());
|
||||
game.board.id = boardId;
|
||||
|
||||
await _db!.insert('classic_game_history', {
|
||||
'date': game.date.toIso8601String(),
|
||||
'board_id': boardId,
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<List<DbClassicGame>> getAllClassicGames() async {
|
||||
if (_db == null) throw Exception('Database has not been initialized');
|
||||
|
||||
final List<Map<String, Object?>> gameMaps =
|
||||
await _db!.query('classic_game_history');
|
||||
List<DbClassicGame> games = [];
|
||||
for (var gameMap in gameMaps) {
|
||||
final boardId = gameMap['board_id'] as int;
|
||||
final List<Map<String, Object?>> boardMaps =
|
||||
await _db!.query('board', where: 'id = ?', whereArgs: [boardId]);
|
||||
if (boardMaps.isNotEmpty) {
|
||||
final board = DbBoard.fromMap(boardMaps.first);
|
||||
games.add(DbClassicGame(
|
||||
id: gameMap['id'] as int?,
|
||||
date: DateTime.parse(gameMap['date'] as String),
|
||||
board: board,
|
||||
));
|
||||
}
|
||||
}
|
||||
return games;
|
||||
}
|
||||
}
|
||||
35
lib/db/duel_game.dart
Normal file
35
lib/db/duel_game.dart
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import 'board.dart';
|
||||
|
||||
class DbDuelGame {
|
||||
int? id;
|
||||
DateTime date;
|
||||
int winner; // (0 for player 1, 1 for player 2)
|
||||
int numberOfTurns;
|
||||
int player1score;
|
||||
int player2score;
|
||||
List<DbBoard> player1games;
|
||||
List<DbBoard> player2games;
|
||||
|
||||
DbDuelGame({
|
||||
this.id,
|
||||
required this.date,
|
||||
required this.winner,
|
||||
required this.numberOfTurns,
|
||||
required this.player1score,
|
||||
required this.player2score,
|
||||
required this.player1games,
|
||||
required this.player2games,
|
||||
});
|
||||
|
||||
// Convert to a Map for inserting into SQLite (only metadata)
|
||||
Map<String, Object?> toMap() {
|
||||
return {
|
||||
'date': date.toIso8601String(),
|
||||
'winner': winner,
|
||||
'numberOfTurns': numberOfTurns,
|
||||
'player1score': player1score,
|
||||
'player2score': player2score,
|
||||
if (id != null) 'id': id,
|
||||
};
|
||||
}
|
||||
}
|
||||
92
lib/db/duel_game_provider.dart
Normal file
92
lib/db/duel_game_provider.dart
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:wordle/db/board.dart';
|
||||
import 'package:wordle/db/duel_game.dart';
|
||||
|
||||
class DbDuelGameProvider extends ChangeNotifier {
|
||||
Database? _db;
|
||||
|
||||
void initialize(Database db) {
|
||||
_db = db;
|
||||
}
|
||||
|
||||
Future<void> insert(DbDuelGame duelGame) async {
|
||||
if (_db == null) {
|
||||
throw Exception("Database has not been initialized");
|
||||
}
|
||||
|
||||
// Insert the duel game metadata
|
||||
int duelGameId = await _db!.insert('duel_game_history', duelGame.toMap());
|
||||
|
||||
// Insert each board for player 1 and player 2
|
||||
for (DbBoard board in duelGame.player1games) {
|
||||
await _db!.insert('duel_game_boards', {
|
||||
'duel_game_id': duelGameId,
|
||||
'player': 1,
|
||||
...board.toMap(),
|
||||
});
|
||||
}
|
||||
|
||||
for (DbBoard board in duelGame.player2games) {
|
||||
await _db!.insert('duel_game_boards', {
|
||||
'duel_game_id': duelGameId,
|
||||
'player': 2,
|
||||
...board.toMap(),
|
||||
});
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<List<DbDuelGame>> getAllDuelGames() async {
|
||||
if (_db == null) {
|
||||
throw Exception("Database has not been initialized");
|
||||
}
|
||||
|
||||
// Query duel games
|
||||
List<Map<String, dynamic>> duelGameMaps =
|
||||
await _db!.query('duel_game_history');
|
||||
List<DbDuelGame> duelGames = [];
|
||||
|
||||
// For each duel game, retrieve the boards associated with it
|
||||
for (var duelGameMap in duelGameMaps) {
|
||||
int duelGameId = duelGameMap['id'];
|
||||
|
||||
// Query boards associated with this duel game
|
||||
List<Map<String, dynamic>> boardMaps = await _db!.query(
|
||||
'duel_game_boards',
|
||||
where: 'duel_game_id = ?',
|
||||
whereArgs: [duelGameId],
|
||||
);
|
||||
|
||||
// Split boards into player 1 and player 2
|
||||
List<DbBoard> player1games = [];
|
||||
List<DbBoard> player2games = [];
|
||||
|
||||
for (var boardMap in boardMaps) {
|
||||
DbBoard board = DbBoard.fromMap(boardMap);
|
||||
if (boardMap['player'] == 1) {
|
||||
player1games.add(board);
|
||||
} else {
|
||||
player2games.add(board);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the DbDuelGame object
|
||||
DbDuelGame duelGame = DbDuelGame(
|
||||
id: duelGameMap['id'],
|
||||
date: DateTime.parse(duelGameMap['date']),
|
||||
winner: duelGameMap['winner'],
|
||||
numberOfTurns: duelGameMap['numberOfTurns'],
|
||||
player1score: duelGameMap['player1score'],
|
||||
player2score: duelGameMap['player2score'],
|
||||
player1games: player1games,
|
||||
player2games: player2games,
|
||||
);
|
||||
|
||||
duelGames.add(duelGame);
|
||||
}
|
||||
|
||||
return duelGames;
|
||||
}
|
||||
}
|
||||
40
lib/db/survival_game.dart
Normal file
40
lib/db/survival_game.dart
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'board.dart';
|
||||
|
||||
const String tableSurvivalHistory = 'survival_game_history';
|
||||
|
||||
class DbSurvivalGame {
|
||||
int? id;
|
||||
DateTime date;
|
||||
int maxLevelReached;
|
||||
List<DbBoard> boards;
|
||||
|
||||
DbSurvivalGame({
|
||||
this.id,
|
||||
required this.date,
|
||||
required this.maxLevelReached,
|
||||
required this.boards,
|
||||
});
|
||||
|
||||
Map<String, Object?> toMap() {
|
||||
return {
|
||||
'date': date.toIso8601String(),
|
||||
'maxLevelReached': maxLevelReached,
|
||||
'boards': jsonEncode(boards.map((board) => board.toMap()).toList()),
|
||||
if (id != null) columnId: id,
|
||||
};
|
||||
}
|
||||
|
||||
factory DbSurvivalGame.fromMap(Map<String, Object?> map) {
|
||||
List<dynamic> boardsJson = jsonDecode(map['boards'] as String);
|
||||
return DbSurvivalGame(
|
||||
id: map[columnId] as int?,
|
||||
date: DateTime.parse(map['date'] as String),
|
||||
maxLevelReached: map['maxLevelReached'] as int,
|
||||
boards: boardsJson
|
||||
.map((item) => DbBoard.fromMap(Map<String, Object?>.from(item)))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
66
lib/db/survival_game_provider.dart
Normal file
66
lib/db/survival_game_provider.dart
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:wordle/db/survival_game.dart';
|
||||
|
||||
import 'board.dart';
|
||||
|
||||
class DbSurvivalGameProvider extends ChangeNotifier {
|
||||
Database? _db;
|
||||
|
||||
void initialize(Database db) {
|
||||
_db = db;
|
||||
}
|
||||
|
||||
Future<void> insertSurvivalGame(DbSurvivalGame game) async {
|
||||
if (_db == null) throw Exception('Database has not been initialized');
|
||||
|
||||
// Insert survival game
|
||||
int survivalGameId = await _db!.insert('survival_game_history', {
|
||||
'date': game.date.toIso8601String(),
|
||||
'maxLevelReached': game.maxLevelReached,
|
||||
});
|
||||
|
||||
// Insert boards associated with the survival game
|
||||
for (var board in game.boards) {
|
||||
int boardId = await _db!.insert('board', board.toMap());
|
||||
await _db!.insert('survival_game_boards', {
|
||||
'survival_game_id': survivalGameId,
|
||||
'board_id': boardId,
|
||||
});
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<List<DbSurvivalGame>> getAllSurvivalGames() async {
|
||||
if (_db == null) throw Exception('Database has not been initialized');
|
||||
|
||||
final List<Map<String, Object?>> gameMaps =
|
||||
await _db!.query('survival_game_history');
|
||||
List<DbSurvivalGame> games = [];
|
||||
for (var gameMap in gameMaps) {
|
||||
int survivalGameId = gameMap['id'] as int;
|
||||
List<DbBoard> boards = [];
|
||||
|
||||
// Fetch boards associated with the survival game
|
||||
final List<Map<String, Object?>> boardMaps = await _db!.query(
|
||||
'survival_game_boards',
|
||||
where: 'survival_game_id = ?',
|
||||
whereArgs: [survivalGameId]);
|
||||
for (var boardMap in boardMaps) {
|
||||
final List<Map<String, Object?>> boardDetails = await _db!
|
||||
.query('board', where: 'id = ?', whereArgs: [boardMap['board_id']]);
|
||||
if (boardDetails.isNotEmpty) {
|
||||
boards.add(DbBoard.fromMap(boardDetails.first));
|
||||
}
|
||||
}
|
||||
|
||||
games.add(DbSurvivalGame(
|
||||
id: survivalGameId,
|
||||
date: DateTime.parse(gameMap['date'] as String),
|
||||
maxLevelReached: gameMap['maxLevelReached'] as int,
|
||||
boards: boards,
|
||||
));
|
||||
}
|
||||
return games;
|
||||
}
|
||||
}
|
||||
95
lib/db/wordle_database.dart
Normal file
95
lib/db/wordle_database.dart
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import 'package:path/path.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
class WordleDatabase {
|
||||
static final WordleDatabase _instance = WordleDatabase._internal();
|
||||
Database? _db;
|
||||
|
||||
factory WordleDatabase() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
WordleDatabase._internal();
|
||||
|
||||
Future<Database> get database async {
|
||||
if (_db != null) {
|
||||
return _db!;
|
||||
}
|
||||
|
||||
_db = await _initializeDatabase();
|
||||
return _db!;
|
||||
}
|
||||
|
||||
Future<Database> _initializeDatabase() async {
|
||||
// Get the default database path
|
||||
String path = join(await getDatabasesPath(), 'wordle_game.db');
|
||||
|
||||
return await openDatabase(
|
||||
path,
|
||||
version: 1,
|
||||
onCreate: (db, version) async {
|
||||
// Create tables for each game mode
|
||||
await db.execute('''
|
||||
CREATE TABLE board (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
correctWord TEXT NOT NULL,
|
||||
maxGuesses INTEGER NOT NULL,
|
||||
guesses TEXT NOT NULL,
|
||||
hasWon INTEGER NOT NULL
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE classic_game_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
date TEXT NOT NULL,
|
||||
board_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (board_id) REFERENCES board (id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE duel_game_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
date TEXT NOT NULL,
|
||||
winner INTEGER NOT NULL CHECK (winner IN (0, 1)),
|
||||
numberOfTurns INTEGER NOT NULL,
|
||||
player1score INTEGER NOT NULL,
|
||||
player2score INTEGER NOT NULL
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE duel_game_boards (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
duel_game_id INTEGER NOT NULL,
|
||||
player INTEGER NOT NULL CHECK (player IN (1, 2)),
|
||||
correctWord TEXT NOT NULL,
|
||||
maxGuesses INTEGER NOT NULL,
|
||||
guesses TEXT NOT NULL,
|
||||
hasWon INTEGER NOT NULL,
|
||||
FOREIGN KEY (duel_game_id) REFERENCES duel_game_history (id)
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE survival_game_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
date TEXT NOT NULL,
|
||||
maxLevelReached INTEGER NOT NULL
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE survival_game_boards (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
survival_game_id INTEGER NOT NULL,
|
||||
board_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (survival_game_id) REFERENCES survival_game_history (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (board_id) REFERENCES board (id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
142
lib/duel_game_history.dart
Normal file
142
lib/duel_game_history.dart
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wordle/db/duel_game.dart';
|
||||
|
||||
import 'db/duel_game_provider.dart';
|
||||
import 'game_screen.dart';
|
||||
|
||||
class DuelGameHistoryScreen extends StatelessWidget {
|
||||
const DuelGameHistoryScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Duel Game History'),
|
||||
),
|
||||
body: FutureBuilder<List<DbDuelGame>>(
|
||||
future: Provider.of<DbDuelGameProvider>(context, listen: false)
|
||||
.getAllDuelGames(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return const Center(child: Text('No duel games found.'));
|
||||
} else {
|
||||
final List<DbDuelGame> games = snapshot.data!;
|
||||
return ListView.builder(
|
||||
itemCount: games.length,
|
||||
itemBuilder: (context, index) {
|
||||
final game = games[index];
|
||||
return Card(
|
||||
margin:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
child: ListTile(
|
||||
title: Text('Game on ${game.date.toString()}'),
|
||||
subtitle: Text(
|
||||
'Player 1 Score: ${game.player1score}, Player 2 Score: ${game.player2score}, Winner: Player ${game.winner + 1}'),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title:
|
||||
Text('Duel Game on ${game.date.toString()}'),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Text('Player 1 Games',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold)),
|
||||
),
|
||||
...game.player1games.map((board) => Card(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 16),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
'Word: ${board.correctWord}, Max Guesses: ${board.maxGuesses}, Has Won: ${board.hasWon ? "Yes" : "No"}'),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(board.correctWord
|
||||
.toUpperCase()),
|
||||
),
|
||||
body: Center(
|
||||
child: GameScreen(
|
||||
guesses: board.guesses,
|
||||
maxGuesses:
|
||||
board.maxGuesses,
|
||||
correctWord:
|
||||
board.correctWord,
|
||||
readOnly: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)),
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Text('Player 2 Games',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold)),
|
||||
),
|
||||
...game.player2games.map((board) => Card(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 16),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
'Word: ${board.correctWord}, Max Guesses: ${board.maxGuesses}, Has Won: ${board.hasWon ? "Yes" : "No"}'),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(board.correctWord
|
||||
.toUpperCase()),
|
||||
),
|
||||
body: Center(
|
||||
child: GameScreen(
|
||||
guesses: board.guesses,
|
||||
maxGuesses:
|
||||
board.maxGuesses,
|
||||
correctWord:
|
||||
board.correctWord,
|
||||
readOnly: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
217
lib/duel_game_screen.dart
Normal file
217
lib/duel_game_screen.dart
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wordle/db/duel_game.dart';
|
||||
import 'package:wordle/db/duel_game_provider.dart';
|
||||
import 'package:wordle/word_loader.dart';
|
||||
|
||||
import 'db/board.dart';
|
||||
import 'game_screen.dart';
|
||||
|
||||
class DuelGameScreen extends StatefulWidget {
|
||||
final int numberOfTurns;
|
||||
|
||||
const DuelGameScreen({super.key, required this.numberOfTurns});
|
||||
|
||||
@override
|
||||
_DuelGameScreenState createState() => _DuelGameScreenState();
|
||||
}
|
||||
|
||||
class _DuelGameScreenState extends State<DuelGameScreen> {
|
||||
final _wordController = TextEditingController();
|
||||
String? _errorMessage;
|
||||
int _currentPlayer = 1;
|
||||
final List<String> _playerWords = [];
|
||||
int _player1Score = 0;
|
||||
int _player2Score = 0;
|
||||
int _turnsLeft;
|
||||
bool _isGameStarted = false;
|
||||
String _currentCorrectWord = "";
|
||||
final List<DbBoard> _player1Games = [];
|
||||
final List<DbBoard> _player2Games = [];
|
||||
|
||||
_DuelGameScreenState() : _turnsLeft = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_turnsLeft = widget.numberOfTurns;
|
||||
}
|
||||
|
||||
void _submitWord() async {
|
||||
String enteredWord = _wordController.text.trim().toUpperCase();
|
||||
bool validWord = await WordLoader.isValidWord(enteredWord);
|
||||
if (validWord) {
|
||||
setState(() {
|
||||
_playerWords.add(enteredWord);
|
||||
_wordController.clear();
|
||||
_errorMessage = null;
|
||||
if (_playerWords.length == 2) {
|
||||
_currentPlayer = 1;
|
||||
_startGame();
|
||||
} else {
|
||||
_currentPlayer = 2;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_errorMessage = "Invalid word. Please enter a valid word.";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _startGame() {
|
||||
setState(() {
|
||||
_isGameStarted = true;
|
||||
_turnsLeft--;
|
||||
_playTurn();
|
||||
});
|
||||
}
|
||||
|
||||
void _playTurn() {
|
||||
setState(() {
|
||||
_currentCorrectWord =
|
||||
_currentPlayer == 1 ? _playerWords[1] : _playerWords[0];
|
||||
});
|
||||
}
|
||||
|
||||
void _onGuessSubmitted(GameData data) async {
|
||||
DbBoard currentBoard = DbBoard(
|
||||
correctWord: data.correctWord,
|
||||
maxGuesses: data.maxGuesses,
|
||||
guesses: data.guesses,
|
||||
hasWon: data.hasWon,
|
||||
);
|
||||
|
||||
if (_currentPlayer == 1) {
|
||||
_player1Games.add(currentBoard);
|
||||
} else {
|
||||
_player2Games.add(currentBoard);
|
||||
}
|
||||
|
||||
await _showTurnEndDialog(data.hasWon);
|
||||
setState(() {
|
||||
if (_currentPlayer == 1) {
|
||||
if (data.hasWon) _player1Score += _playerWords[0].length;
|
||||
_currentPlayer = 2;
|
||||
_playTurn();
|
||||
} else {
|
||||
if (data.hasWon) _player2Score += _playerWords[1].length;
|
||||
_currentPlayer = 1;
|
||||
|
||||
if (_turnsLeft > 0) {
|
||||
_turnsLeft--;
|
||||
_playerWords.clear();
|
||||
_isGameStarted = false;
|
||||
} else {
|
||||
_saveGameHistory();
|
||||
_showEndGameDialog();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _saveGameHistory() async {
|
||||
final duelGame = DbDuelGame(
|
||||
date: DateTime.now(),
|
||||
winner: _player1Score > _player2Score ? 0 : 1,
|
||||
numberOfTurns: widget.numberOfTurns,
|
||||
player1score: _player1Score,
|
||||
player2score: _player2Score,
|
||||
player1games: _player1Games,
|
||||
player2games: _player2Games,
|
||||
);
|
||||
final provider = Provider.of<DbDuelGameProvider>(context, listen: false);
|
||||
await provider.insert(duelGame);
|
||||
}
|
||||
|
||||
Future<void> _showTurnEndDialog(bool hasWon) async {
|
||||
String message = hasWon
|
||||
? "Congratulations, Player $_currentPlayer! You guessed the correct word."
|
||||
: "Player $_currentPlayer, you did not guess the correct word.";
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Turn Over'),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("OK"),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showEndGameDialog() async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Game Over'),
|
||||
content: Text(
|
||||
'The duel has ended. Player 1 score: $_player1Score, Player 2 score: $_player2Score. Thanks for playing!'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Return to Main Menu"),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Player $_currentPlayer - Enter a Word'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: !_isGameStarted
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Player $_currentPlayer, enter the word you want the other player to guess:',
|
||||
style: const TextStyle(fontSize: 18),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextField(
|
||||
controller: _wordController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Enter a word',
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _errorMessage,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: _submitWord,
|
||||
child: const Text('Submit Word'),
|
||||
),
|
||||
],
|
||||
)
|
||||
: GameScreen(
|
||||
maxGuesses: 6,
|
||||
correctWord: _currentCorrectWord,
|
||||
readOnly: false,
|
||||
onGameEnd: _onGuessSubmitted,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
71
lib/duel_setup_screen.dart
Normal file
71
lib/duel_setup_screen.dart
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'duel_game_screen.dart';
|
||||
|
||||
class DuelSetupScreen extends StatefulWidget {
|
||||
const DuelSetupScreen({super.key});
|
||||
|
||||
@override
|
||||
_DuelSetupScreenState createState() => _DuelSetupScreenState();
|
||||
}
|
||||
|
||||
class _DuelSetupScreenState extends State<DuelSetupScreen> {
|
||||
int? _numberOfTurns;
|
||||
String? _errorMessage;
|
||||
|
||||
void _submitTurns() {
|
||||
if (_numberOfTurns != null && _numberOfTurns! > 0) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DuelGameScreen(numberOfTurns: _numberOfTurns!),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
setState(() {
|
||||
_errorMessage = "Please enter a valid number of turns.";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Duel Setup'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'Enter the number of turns for the duel:',
|
||||
style: TextStyle(fontSize: 18),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextField(
|
||||
keyboardType: TextInputType.number,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_numberOfTurns = int.tryParse(value);
|
||||
});
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Number of turns',
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _errorMessage,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: _submitTurns,
|
||||
child: const Text('Submit Turns'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
33
lib/game_mode_row.dart
Normal file
33
lib/game_mode_row.dart
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class GameModeRow extends StatelessWidget {
|
||||
final VoidCallback historyCallback;
|
||||
final VoidCallback playCallback;
|
||||
final String label;
|
||||
|
||||
const GameModeRow({
|
||||
super.key,
|
||||
required this.historyCallback,
|
||||
required this.playCallback,
|
||||
required this.label,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.menu_book),
|
||||
iconSize: 32,
|
||||
onPressed: historyCallback,
|
||||
),
|
||||
const SizedBox(width: 16.0),
|
||||
ElevatedButton(
|
||||
onPressed: playCallback,
|
||||
child: Text(label),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
272
lib/game_screen.dart
Normal file
272
lib/game_screen.dart
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
import 'package:diacritic/diacritic.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wordle/db/board.dart';
|
||||
import 'package:wordle/db/classic_game.dart';
|
||||
import 'package:wordle/db/classic_game_provider.dart';
|
||||
import 'package:wordle/word_loader.dart';
|
||||
|
||||
import 'guess_row.dart';
|
||||
import 'letter_keyboard.dart';
|
||||
|
||||
class GameData {
|
||||
final String correctWord;
|
||||
final int maxGuesses;
|
||||
final List<String> guesses;
|
||||
final bool hasWon;
|
||||
|
||||
const GameData({
|
||||
required this.correctWord,
|
||||
required this.maxGuesses,
|
||||
required this.guesses,
|
||||
required this.hasWon,
|
||||
});
|
||||
}
|
||||
|
||||
class GameScreen extends StatefulWidget {
|
||||
final int maxGuesses;
|
||||
final List<String>? guesses;
|
||||
final String correctWord;
|
||||
final bool readOnly;
|
||||
final void Function(GameData data)? onGameEnd;
|
||||
|
||||
const GameScreen({
|
||||
super.key,
|
||||
required this.maxGuesses,
|
||||
required this.correctWord,
|
||||
this.guesses,
|
||||
this.readOnly = false,
|
||||
this.onGameEnd,
|
||||
});
|
||||
|
||||
@override
|
||||
_GameScreenState createState() => _GameScreenState();
|
||||
}
|
||||
|
||||
class _GameScreenState extends State<GameScreen> {
|
||||
late List<String> guesses;
|
||||
String currentGuess = "";
|
||||
int currentRowIndex = 0;
|
||||
bool gameEnded = false;
|
||||
String? _errorMessage;
|
||||
late ScrollController _scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController = ScrollController();
|
||||
|
||||
if (widget.readOnly) {
|
||||
guesses = widget.guesses ?? [];
|
||||
currentRowIndex = guesses.length;
|
||||
gameEnded = true;
|
||||
} else {
|
||||
guesses = List.generate(widget.maxGuesses, (_) => "");
|
||||
}
|
||||
}
|
||||
|
||||
void _resetGame() {
|
||||
setState(() {
|
||||
guesses = List.generate(widget.maxGuesses, (_) => "");
|
||||
currentRowIndex = 0;
|
||||
currentGuess = "";
|
||||
gameEnded = false;
|
||||
_errorMessage = null;
|
||||
});
|
||||
}
|
||||
|
||||
void onLetterPressed(String letter) {
|
||||
if (currentGuess.length < widget.correctWord.length && !gameEnded) {
|
||||
setState(() {
|
||||
currentGuess += letter;
|
||||
guesses[currentRowIndex] = currentGuess;
|
||||
_errorMessage = null;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToCurrentGuess();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void onBackspacePressed() {
|
||||
if (currentGuess.isNotEmpty && !gameEnded) {
|
||||
setState(() {
|
||||
currentGuess = currentGuess.substring(0, currentGuess.length - 1);
|
||||
guesses[currentRowIndex] = currentGuess;
|
||||
_errorMessage = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void onSubmitGuess() async {
|
||||
if (currentGuess.length == widget.correctWord.length && !gameEnded) {
|
||||
bool isValidWord = await WordLoader.isValidWord(currentGuess);
|
||||
if (!isValidWord) {
|
||||
setState(() {
|
||||
_errorMessage = 'Invalid word. Please enter a valid word.';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
guesses[currentRowIndex] = currentGuess;
|
||||
|
||||
if (currentGuess.toUpperCase() ==
|
||||
removeDiacritics(widget.correctWord.toUpperCase())) {
|
||||
gameEnded = true;
|
||||
_handleGameEnd(GameData(
|
||||
correctWord: widget.correctWord,
|
||||
maxGuesses: widget.maxGuesses,
|
||||
guesses: guesses,
|
||||
hasWon: true));
|
||||
} else if (currentRowIndex == widget.maxGuesses - 1) {
|
||||
gameEnded = true;
|
||||
_handleGameEnd(GameData(
|
||||
correctWord: widget.correctWord,
|
||||
maxGuesses: widget.maxGuesses,
|
||||
guesses: guesses,
|
||||
hasWon: false));
|
||||
} else {
|
||||
currentRowIndex++;
|
||||
currentGuess = "";
|
||||
_errorMessage = null;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToCurrentGuess();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveGame(GameData data) async {
|
||||
final game = DbClassicGame(
|
||||
date: DateTime.now(),
|
||||
board: DbBoard(
|
||||
correctWord: data.correctWord,
|
||||
maxGuesses: data.maxGuesses,
|
||||
guesses: data.guesses,
|
||||
hasWon: data.hasWon),
|
||||
);
|
||||
|
||||
final gameProvider =
|
||||
Provider.of<DbClassicGameProvider>(context, listen: false);
|
||||
|
||||
await gameProvider.insertClassicGame(game);
|
||||
}
|
||||
|
||||
void _handleGameEnd(GameData data) {
|
||||
if (widget.onGameEnd != null) {
|
||||
widget.onGameEnd!(data);
|
||||
} else {
|
||||
saveGame(data);
|
||||
showDefaultEndGameDialog(
|
||||
data.hasWon ? "You Win!" : "Game Over",
|
||||
data.hasWon
|
||||
? "Congratulations, you guessed the correct word!"
|
||||
: "You've used all your guesses. The correct word was \"${widget.correctWord}\".",
|
||||
"Return to the main screen",
|
||||
);
|
||||
}
|
||||
_resetGame();
|
||||
}
|
||||
|
||||
Future<void> _scrollToCurrentGuess() async {
|
||||
double rowHeight = 70.0;
|
||||
double offset = (currentRowIndex * rowHeight) -
|
||||
(MediaQuery.of(context).size.height / 2) +
|
||||
(rowHeight / 2);
|
||||
|
||||
offset = offset < 0 ? 0 : offset;
|
||||
|
||||
_scrollController.animateTo(
|
||||
offset,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
|
||||
void showDefaultEndGameDialog(String title, String message, String btnText) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(btnText),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
itemCount: guesses.length,
|
||||
itemBuilder: (context, index) {
|
||||
return GuessRow(
|
||||
guess: guesses[index],
|
||||
correctWord: widget.correctWord,
|
||||
evaluateColors: index < currentRowIndex || widget.readOnly,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (_errorMessage != null) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Text(
|
||||
_errorMessage!,
|
||||
style: const TextStyle(color: Colors.red, fontSize: 16),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (!widget.readOnly) ...[
|
||||
ElevatedButton(
|
||||
onPressed:
|
||||
currentGuess.length == widget.correctWord.length && !gameEnded
|
||||
? onSubmitGuess
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor:
|
||||
currentGuess.length == widget.correctWord.length && !gameEnded
|
||||
? const Color(0xFF4F545C)
|
||||
: const Color(0xFF2C2F33),
|
||||
),
|
||||
child: const Text("Submit Guess"),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
||||
child: LetterKeyboard(
|
||||
onLetterPressed: onLetterPressed,
|
||||
onBackspacePressed: onBackspacePressed,
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
54
lib/guess_row.dart
Normal file
54
lib/guess_row.dart
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import 'package:diacritic/diacritic.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'LetterColoration.dart';
|
||||
|
||||
class GuessRow extends StatelessWidget {
|
||||
final String guess;
|
||||
final String correctWord;
|
||||
final bool evaluateColors;
|
||||
|
||||
const GuessRow({
|
||||
super.key,
|
||||
required this.guess,
|
||||
required this.correctWord,
|
||||
required this.evaluateColors,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
int wordLength = correctWord.length;
|
||||
double screenWidth = MediaQuery.of(context).size.width;
|
||||
double totalMargin = 2 * wordLength * 2;
|
||||
double boxSize = (screenWidth - 32 - totalMargin) / wordLength;
|
||||
double fontSize = boxSize * 0.6;
|
||||
String normalizedCorrectWord = removeDiacritics(correctWord).toUpperCase();
|
||||
List<Color> colors = LetterColoration.getColorFeedback(
|
||||
guess, normalizedCorrectWord, evaluateColors);
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(wordLength, (index) {
|
||||
String letter = guess.length > index ? guess[index] : '';
|
||||
return _buildLetterTile(letter, colors[index], boxSize, fontSize);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLetterTile(String letter, Color color, boxSize, fontSize) {
|
||||
return Container(
|
||||
width: boxSize,
|
||||
height: boxSize,
|
||||
margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
letter.toUpperCase(),
|
||||
style: TextStyle(fontSize: fontSize, color: Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
64
lib/letter_keyboard.dart
Normal file
64
lib/letter_keyboard.dart
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class LetterKeyboard extends StatelessWidget {
|
||||
final Function(String) onLetterPressed;
|
||||
final Function() onBackspacePressed;
|
||||
|
||||
const LetterKeyboard({
|
||||
super.key,
|
||||
required this.onLetterPressed,
|
||||
required this.onBackspacePressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
final List<String> firstRow = letters.substring(0, 10).split('');
|
||||
final List<String> secondRow = letters.substring(10, 20).split('');
|
||||
final List<String> thirdRow = letters.substring(20).split('') + ["⌫"];
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildKeyboardRow(firstRow),
|
||||
_buildKeyboardRow(secondRow),
|
||||
_buildKeyboardRow(thirdRow),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildKeyboardRow(List<String> keys) {
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: keys.map((key) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
width: 28,
|
||||
height: 28,
|
||||
margin: const EdgeInsets.all(1),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF4F545C),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () =>
|
||||
key == "⌫" ? onBackspacePressed() : onLetterPressed(key),
|
||||
child: Center(
|
||||
child: Text(
|
||||
key,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
55
lib/main.dart
Normal file
55
lib/main.dart
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wordle/db/classic_game_provider.dart';
|
||||
import 'package:wordle/db/duel_game_provider.dart';
|
||||
import 'package:wordle/db/survival_game_provider.dart';
|
||||
|
||||
import 'db/wordle_database.dart';
|
||||
import 'main_menu.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
final wordleDatabase = WordleDatabase();
|
||||
final db = await wordleDatabase.database;
|
||||
|
||||
runApp(MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider<DbClassicGameProvider>(
|
||||
create: (_) {
|
||||
final provider = DbClassicGameProvider();
|
||||
provider.initialize(db);
|
||||
return provider;
|
||||
},
|
||||
),
|
||||
ChangeNotifierProvider<DbDuelGameProvider>(
|
||||
create: (_) {
|
||||
final provider = DbDuelGameProvider();
|
||||
provider.initialize(db);
|
||||
return provider;
|
||||
},
|
||||
),
|
||||
ChangeNotifierProvider<DbSurvivalGameProvider>(
|
||||
create: (_) {
|
||||
final provider = DbSurvivalGameProvider();
|
||||
provider.initialize(db);
|
||||
return provider;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: const MyApp(),
|
||||
));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Wordle App',
|
||||
theme: ThemeData.dark(),
|
||||
home: const MainMenu(),
|
||||
);
|
||||
}
|
||||
}
|
||||
176
lib/main_menu.dart
Normal file
176
lib/main_menu.dart
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
import 'package:country_flags/country_flags.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:wordle/classic_game_history.dart';
|
||||
import 'package:wordle/classic_options_screen.dart';
|
||||
import 'package:wordle/duel_game_history.dart';
|
||||
import 'package:wordle/duel_setup_screen.dart';
|
||||
import 'package:wordle/survival_game_history.dart';
|
||||
import 'package:wordle/survival_game_screen.dart';
|
||||
|
||||
import 'game_mode_row.dart';
|
||||
|
||||
class MainMenu extends StatefulWidget {
|
||||
const MainMenu({super.key});
|
||||
|
||||
@override
|
||||
_MainMenuState createState() => _MainMenuState();
|
||||
}
|
||||
|
||||
class _MainMenuState extends State<MainMenu> {
|
||||
String _selectedLanguage = 'english';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadLanguage();
|
||||
}
|
||||
|
||||
void _loadLanguage() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
setState(() {
|
||||
_selectedLanguage = prefs.getString('selectedLanguage') ?? 'english';
|
||||
});
|
||||
}
|
||||
|
||||
void _setLanguage(String language) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
setState(() {
|
||||
_selectedLanguage = language;
|
||||
prefs.setString('selectedLanguage', _selectedLanguage);
|
||||
});
|
||||
}
|
||||
|
||||
String getCountryCode() {
|
||||
switch (_selectedLanguage) {
|
||||
case 'french':
|
||||
return 'FR';
|
||||
case 'spanish':
|
||||
return 'ES';
|
||||
case 'english':
|
||||
default:
|
||||
return 'GB';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Wordle Game'),
|
||||
actions: [
|
||||
PopupMenuButton<String>(
|
||||
icon: CountryFlag.fromCountryCode(
|
||||
getCountryCode(),
|
||||
height: 24,
|
||||
width: 24,
|
||||
shape: const RoundedRectangle(6),
|
||||
),
|
||||
onSelected: (String value) {
|
||||
_setLanguage(value);
|
||||
},
|
||||
itemBuilder: (BuildContext context) => [
|
||||
PopupMenuItem(
|
||||
value: 'english',
|
||||
child: Row(
|
||||
children: [
|
||||
CountryFlag.fromCountryCode('GB', height: 20, width: 20),
|
||||
const SizedBox(width: 8),
|
||||
const Text('English'),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'french',
|
||||
child: Row(
|
||||
children: [
|
||||
CountryFlag.fromCountryCode('FR', height: 20, width: 20),
|
||||
const SizedBox(width: 8),
|
||||
const Text('Français'),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'spanish',
|
||||
child: Row(
|
||||
children: [
|
||||
CountryFlag.fromCountryCode('ES', height: 20, width: 20),
|
||||
const SizedBox(width: 8),
|
||||
const Text('Español'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
GameModeRow(
|
||||
historyCallback: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ClassicGameHistoryScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
playCallback: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ClassicGame(),
|
||||
),
|
||||
);
|
||||
},
|
||||
label: "Classic Mode",
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
GameModeRow(
|
||||
historyCallback: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const DuelGameHistoryScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
playCallback: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const DuelSetupScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
label: "Duel Mode",
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
GameModeRow(
|
||||
historyCallback: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SurvivalGameHistoryScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
playCallback: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SurvivalGameScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
label: "Survival Mode",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
101
lib/survival_game_history.dart
Normal file
101
lib/survival_game_history.dart
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wordle/db/survival_game.dart';
|
||||
|
||||
import 'db/survival_game_provider.dart';
|
||||
import 'game_screen.dart';
|
||||
|
||||
class SurvivalGameHistoryScreen extends StatelessWidget {
|
||||
const SurvivalGameHistoryScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Survival Game History'),
|
||||
),
|
||||
body: FutureBuilder<List<DbSurvivalGame>>(
|
||||
future: Provider.of<DbSurvivalGameProvider>(context, listen: false)
|
||||
.getAllSurvivalGames(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return const Center(child: Text('No survival history available.'));
|
||||
}
|
||||
|
||||
final histories = snapshot.data!;
|
||||
return ListView.builder(
|
||||
itemCount: histories.length,
|
||||
itemBuilder: (context, index) {
|
||||
final history = histories[index];
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(8.0),
|
||||
child: ListTile(
|
||||
title: Text('Date: ${history.date.toLocal()}'),
|
||||
subtitle: Text('Max Level: ${history.maxLevelReached}'),
|
||||
onTap: () => _showHistoryDetails(context, history),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showHistoryDetails(BuildContext context, DbSurvivalGame history) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
'Survival Game Details (Level ${history.maxLevelReached})'),
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
children: [
|
||||
Text('Date: ${history.date.toLocal()}'),
|
||||
const SizedBox(height: 10),
|
||||
Text('Max Level Reached: ${history.maxLevelReached}'),
|
||||
const SizedBox(height: 10),
|
||||
const Text('Words Guessed:',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 10),
|
||||
...history.boards.map((board) => Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
'Word: ${board.correctWord}, Max Guesses: ${board.maxGuesses}, Has Won: ${board.hasWon ? "Yes" : "No"}'),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(board.correctWord.toUpperCase()),
|
||||
),
|
||||
body: Center(
|
||||
child: GameScreen(
|
||||
guesses: board.guesses,
|
||||
maxGuesses: board.maxGuesses,
|
||||
correctWord: board.correctWord,
|
||||
readOnly: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
112
lib/survival_game_screen.dart
Normal file
112
lib/survival_game_screen.dart
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wordle/word_loader.dart';
|
||||
|
||||
import 'db/board.dart';
|
||||
import 'db/survival_game.dart';
|
||||
import 'db/survival_game_provider.dart';
|
||||
import 'game_screen.dart';
|
||||
|
||||
class SurvivalGameScreen extends StatefulWidget {
|
||||
const SurvivalGameScreen({super.key});
|
||||
|
||||
@override
|
||||
SurvivalGameScreenState createState() => SurvivalGameScreenState();
|
||||
}
|
||||
|
||||
class SurvivalGameScreenState extends State<SurvivalGameScreen> {
|
||||
late Future<String?> futureWord;
|
||||
int _currentLvl = 1;
|
||||
int wordLength = 5;
|
||||
int maxGuesses = 6;
|
||||
List<DbBoard> boards = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadWord();
|
||||
}
|
||||
|
||||
void _loadWord() {
|
||||
futureWord = WordLoader.getWordOfLength(wordLength);
|
||||
}
|
||||
|
||||
void _nextLvl(GameData data) {
|
||||
if (data.hasWon) {
|
||||
setState(() {
|
||||
boards.add(DbBoard(
|
||||
correctWord: data.correctWord,
|
||||
maxGuesses: data.maxGuesses,
|
||||
guesses: data.guesses,
|
||||
hasWon: data.hasWon,
|
||||
));
|
||||
_currentLvl++;
|
||||
wordLength++;
|
||||
if (_currentLvl % 2 == 0) maxGuesses++;
|
||||
_loadWord();
|
||||
});
|
||||
} else {
|
||||
_saveGameHistory();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
void _saveGameHistory() async {
|
||||
final survivalHistory = DbSurvivalGame(
|
||||
date: DateTime.now(),
|
||||
maxLevelReached: _currentLvl,
|
||||
boards: boards,
|
||||
);
|
||||
final provider =
|
||||
Provider.of<DbSurvivalGameProvider>(context, listen: false);
|
||||
await provider.insertSurvivalGame(survivalHistory);
|
||||
}
|
||||
|
||||
void showEndGameDialog(GameData data) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: Text(data.hasWon ? "Congrats!" : "You lost"),
|
||||
content: Text("The word was '${data.correctWord}'"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
_nextLvl(data);
|
||||
},
|
||||
child: Text(data.hasWon ? "Next" : "Back to main menu"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Survival - Level $_currentLvl'),
|
||||
),
|
||||
body: FutureBuilder<String?>(
|
||||
future: futureWord,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
} else if (snapshot.hasData) {
|
||||
return GameScreen(
|
||||
maxGuesses: maxGuesses,
|
||||
correctWord: snapshot.data!,
|
||||
readOnly: false,
|
||||
onGameEnd: showEndGameDialog,
|
||||
);
|
||||
} else {
|
||||
return const Center(child: Text('Unable to load the word.'));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
147
lib/validated_number_input.dart
Normal file
147
lib/validated_number_input.dart
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ValidatedNumberInput extends StatefulWidget {
|
||||
final int? minValue;
|
||||
final int? maxValue;
|
||||
final ValueChanged<int>? onValueChanged;
|
||||
final int? defaultValue;
|
||||
final ValueChanged<bool>? onValidityChanged;
|
||||
|
||||
const ValidatedNumberInput({
|
||||
super.key,
|
||||
this.minValue,
|
||||
this.maxValue,
|
||||
this.onValueChanged,
|
||||
this.defaultValue,
|
||||
this.onValidityChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
_ValidatedNumberInputState createState() => _ValidatedNumberInputState();
|
||||
}
|
||||
|
||||
class _ValidatedNumberInputState extends State<ValidatedNumberInput> {
|
||||
late int currentValue;
|
||||
late TextEditingController _controller;
|
||||
bool isValid = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
currentValue = widget.defaultValue ?? 0;
|
||||
_controller = TextEditingController(text: currentValue.toString());
|
||||
_validateInput(currentValue);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant ValidatedNumberInput oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.defaultValue != null && widget.defaultValue != currentValue) {
|
||||
setState(() {
|
||||
currentValue = widget.defaultValue!;
|
||||
_controller.text = currentValue.toString();
|
||||
_validateInput(currentValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _incrementValue() {
|
||||
setState(() {
|
||||
currentValue++;
|
||||
_controller.text = currentValue.toString();
|
||||
_validateInput(currentValue);
|
||||
widget.onValueChanged?.call(currentValue);
|
||||
});
|
||||
}
|
||||
|
||||
void _decrementValue() {
|
||||
setState(() {
|
||||
currentValue--;
|
||||
_controller.text = currentValue.toString();
|
||||
_validateInput(currentValue);
|
||||
widget.onValueChanged?.call(currentValue);
|
||||
});
|
||||
}
|
||||
|
||||
void _updateValueFromText(String value) {
|
||||
String sanitizedValue = value.replaceAll(RegExp(r'[^0-9]'), '');
|
||||
int? newValue = int.tryParse(sanitizedValue);
|
||||
if (newValue != null) {
|
||||
setState(() {
|
||||
currentValue = newValue;
|
||||
_controller.text = currentValue.toString();
|
||||
_controller.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: _controller.text.length),
|
||||
);
|
||||
_validateInput(currentValue);
|
||||
widget.onValueChanged?.call(currentValue);
|
||||
});
|
||||
} else {
|
||||
_controller.text = currentValue.toString();
|
||||
_controller.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: _controller.text.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _validateInput(int value) {
|
||||
int minValue = widget.minValue ?? double.negativeInfinity.toInt();
|
||||
int maxValue = widget.maxValue ?? double.maxFinite.toInt();
|
||||
bool newIsValid = value >= minValue && value <= maxValue;
|
||||
|
||||
if (newIsValid != isValid) {
|
||||
isValid = newIsValid;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onValidityChanged?.call(isValid);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[800],
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(
|
||||
color: isValid ? Colors.grey : Colors.red,
|
||||
width: 2.0,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.remove),
|
||||
onPressed: _decrementValue,
|
||||
constraints: const BoxConstraints(),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
SizedBox(
|
||||
width: 60,
|
||||
child: TextFormField(
|
||||
controller: _controller,
|
||||
textAlign: TextAlign.center,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
),
|
||||
onChanged: (value) {
|
||||
_updateValueFromText(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: _incrementValue,
|
||||
constraints: const BoxConstraints(),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
62
lib/word_loader.dart
Normal file
62
lib/word_loader.dart
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:diacritic/diacritic.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class WordLoader {
|
||||
static final Map<String, List<String>> _cachedWordLists = {};
|
||||
|
||||
static Future<List<String>> loadWordList(String language) async {
|
||||
if (_cachedWordLists.containsKey(language)) {
|
||||
return _cachedWordLists[language]!;
|
||||
}
|
||||
|
||||
try {
|
||||
String assetPath = "assets/${language}_words.json";
|
||||
String jsonString = await rootBundle.loadString(assetPath);
|
||||
List<dynamic> wordsJson = json.decode(jsonString);
|
||||
List<String> words = wordsJson.cast<String>();
|
||||
_cachedWordLists[language] = words;
|
||||
return words;
|
||||
} catch (e) {
|
||||
print("Error loading word list for language '$language': $e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> isValidWord(String word) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var language = prefs.getString('selectedLanguage') ?? "english";
|
||||
List<String> words = await WordLoader.loadWordList(language);
|
||||
|
||||
List<String> normalizedWords =
|
||||
words.map((w) => removeDiacritics(w).toUpperCase()).toList();
|
||||
return normalizedWords.contains(word.toUpperCase());
|
||||
}
|
||||
|
||||
static List<String> _filterWordsByLength(List<String> words, int length) {
|
||||
return words
|
||||
.where((word) => word.length == length && !word.contains("-"))
|
||||
.toList();
|
||||
}
|
||||
|
||||
static String? _selectRandomWord(List<String> words) {
|
||||
if (words.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final random = Random();
|
||||
return words[random.nextInt(words.length)];
|
||||
}
|
||||
|
||||
static Future<String?> getWordOfLength(int length) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
var language = prefs.getString('selectedLanguage') ?? "english";
|
||||
List<String> words = await loadWordList(language);
|
||||
List<String> filteredWords = _filterWordsByLength(words, length);
|
||||
String? word = _selectRandomWord(filteredWords);
|
||||
print(word);
|
||||
return word;
|
||||
}
|
||||
}
|
||||
514
pubspec.lock
Normal file
514
pubspec.lock
Normal file
|
|
@ -0,0 +1,514 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
country_flags:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: country_flags
|
||||
sha256: "0c2d9f0673ad0a5ec148f5b264c9e7338208b83000336bb4db8d189cd50df2bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
diacritic:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: diacritic
|
||||
sha256: "12981945ec38931748836cd76f2b38773118d0baef3c68404bdfde9566147876"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
jovial_misc:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jovial_misc
|
||||
sha256: f6e64f789ee311025bb367be9c9afe9759f76dd8209070b7f38e735b5f529eb1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.5"
|
||||
jovial_svg:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jovial_svg
|
||||
sha256: adbc985f89a9e9c601d29aebb9fc17dd0a5db05b67af7e6c21da91eeb13dacb7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.23"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.1"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.9"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.1"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.3"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
sqflite:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sqflite_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "4468b24876d673418a7b7147e5a08a715b4998a7ae69227acafaab762e0e5490"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4+5"
|
||||
sqflite_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_darwin
|
||||
sha256: "96a698e2bc82bd770a4d6aab00b42396a7c63d9e33513a56945cbccb594c2474"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sqflite_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_platform_interface
|
||||
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0+3"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
sdks:
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
96
pubspec.yaml
Normal file
96
pubspec.yaml
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
name: wordle
|
||||
description: "Wordle"
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
# The following defines the version and build number for your application.
|
||||
# A version number is three numbers separated by dots, like 1.2.43
|
||||
# followed by an optional build number separated by a +.
|
||||
# Both the version and the builder number may be overridden in flutter
|
||||
# build by specifying --build-name and --build-number, respectively.
|
||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.4
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||
# dependencies can be manually updated by changing the version numbers below to
|
||||
# the latest version available on pub.dev. To see which dependencies have newer
|
||||
# versions available, run `flutter pub outdated`.
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.8
|
||||
sqflite: ^2.4.1
|
||||
shared_preferences: ^2.3.3
|
||||
country_flags: ^3.0.0
|
||||
diacritic: ^0.1.6
|
||||
provider: ^6.1.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^4.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/english_words.json
|
||||
- assets/french_words.json
|
||||
- assets/spanish_words.json
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/to/asset-from-package
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/to/font-from-package
|
||||
Loading…
Add table
Add a link
Reference in a new issue