make wordle clone from tutorial. prepare project for changes

https://perpet.io/blog/making-wordle-game-mobile-app-clone-with-flutter-full-tutorial/
This commit is contained in:
Морозов Андрей 2022-04-04 02:41:14 +04:00
parent 34a4b73756
commit bffa95969e
28 changed files with 7099 additions and 189 deletions

View File

@ -27,6 +27,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@ -43,7 +44,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.guessing_words"
applicationId "com.hellcat.words"
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()

View File

@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.guessing_words">
<!-- Flutter needs it to communicate with the running application
package="com.hellcat.words">
<!-- 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"/>

View File

@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.guessing_words">
package="com.hellcat.words">
<application
android:label="guessing_words"
android:label="Guessing words"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity

View File

@ -1,4 +1,4 @@
package com.example.guessing_words
package com.hellcat.words
import io.flutter.embedding.android.FlutterActivity

View File

@ -3,7 +3,7 @@
<!-- 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
Flutter draws its first frame -->
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.

View File

@ -3,7 +3,7 @@
<!-- 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
Flutter draws its first frame -->
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.

View File

@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.guessing_words">
<!-- Flutter needs it to communicate with the running application
package="com.example.app">
<!-- 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"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

5795
app/assets/words.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
import 'package:flutter/cupertino.dart';
class ColorConstants{
static const Color primaryGreen = Color(0xFF21BC5F);
static const Color primaryOrange = Color(0xFFFE9F10);
static const Color primaryGrey = Color(0xFFDEDEDE);
static const Color primaryGreyLight = Color(0xFFF3F2F2);
static const Color primaryGreyMedium = Color(0xFFD1D5DB);
static const Color primaryGreenLight= Color(0xFFE1F4EB);
static const Color primaryRedLight = Color(0xFFF4E1E1);
}

View File

@ -0,0 +1,17 @@
class TextConstants {
static const String gameTitle = 'WORDLE';
static const String errorWrongWord = "Слова нет в словаре";
static const String errorWrongWordLength = "Длина слова не подходит";
static const String youWin = "Вы победили!";
static const String youLose = "Вы проиграли!";
static const String howToPlayTitle = "Как играть";
static const String howToPlayText = "Угадайте слово за 6 попыток\n\n"
"Каждая попытка должна быть существующим словом из 5-ти букв\n\n"
"После каждой попытки, цвет символов будет меняться, чтобы показать"
"насколько вы близки к загаданному слову";
static const String howToPlayRole1 = "Таких букв в слове нет";
static const String howToPlayRole2 =
"Буква есть в слове, но она не на своем месте";
static const String howToPlayRole3 = "Буква на своем месте";
static const String nextWordle = "Следующее слово через";
}

View File

@ -0,0 +1,138 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:guessing_words/core/const/text_constants.dart';
import 'package:guessing_words/core/data/enums/keyboard_keys.dart';
import 'package:guessing_words/core/data/enums/message_types.dart';
import 'package:guessing_words/core/presentation/home/cubit/home_cubit.dart';
class DataSingleton {
static final DataSingleton _dataSingleton = DataSingleton._internal();
Set<String> allWords = {};
String secretWord = "";
List<String> gridData = [""];
Map<String, Color> coloredLetters = {};
int currentWordIndex = 0;
factory DataSingleton() {
return _dataSingleton;
}
DataSingleton._internal() {
if (secretWord.isEmpty) {
createWord();
}
}
bool setLetter(KeyboardKeys key) {
if (KeyboardKeys.enter.name == key.name) {
return false;
}
if (gridData.length <= currentWordIndex) {
gridData.add("");
}
if (gridData[currentWordIndex].length < 5) {
gridData[currentWordIndex] = gridData[currentWordIndex] + key.name;
return true;
}
return false;
}
void removeLetter() {
if (gridData.length <= currentWordIndex) {
gridData.add("");
}
int wordLength = gridData[currentWordIndex].length;
if (wordLength > 0) {
gridData[currentWordIndex] =
gridData[currentWordIndex].substring(0, wordLength - 1);
}
}
HomeState submitWord() {
if (gridData.length <= currentWordIndex) {
gridData.add("");
}
if (currentWordIndex < 5) {
if (gridData[currentWordIndex].length == 5) {
if (gridData[currentWordIndex] == secretWord) {
nextWord();
return WinGameState();
}
if (allWords.contains(gridData[currentWordIndex])) {
nextWord();
return GridUpdateState();
} else {
return SnackBarMessage(
MessageTypes.error, TextConstants.errorWrongWord);
}
} else {
return SnackBarMessage(
MessageTypes.error, TextConstants.errorWrongWordLength);
}
} else {
return LoseGameState();
}
}
void nextWord() {
final word = gridData[currentWordIndex];
word.split("").asMap().map((key, value) {
if (secretWord[key] == value) {
//green
if (coloredLetters.containsKey(value)) {
coloredLetters.update(value, (value) => Colors.green);
} else {
coloredLetters.addAll({value: Colors.green});
}
} else if (secretWord.contains(value)) {
//orange
if (coloredLetters.containsKey(value)) {
if (coloredLetters[key] == Colors.black38) {
coloredLetters.update(value, (value) => Colors.orangeAccent);
}
} else {
coloredLetters.addAll({value: Colors.orangeAccent});
}
} else {
//grey
if (!coloredLetters.containsKey(value)) {
coloredLetters.addAll({value: Colors.black38});
}
}
return MapEntry(key, value);
});
if (currentWordIndex < 5) {
currentWordIndex++;
}
}
Future<String> createWord() async {
final words = (await rootBundle.loadString('assets/words.txt')).split("\n");
var now = DateTime.now();
var random =
Random(now.year * 10000 + now.month * 100 + now.day + now.hour + 1);
var index = random.nextInt(words.length);
secretWord = words[index];
allWords = words.toSet();
print(secretWord);
return secretWord;
}
String getLetters() {
return gridData.join();
}
Color getKeyColor(KeyboardKeys myKey) {
return coloredLetters[myKey.name] ?? Colors.black26;
}
void resetData() {
allWords = {};
secretWord = "";
gridData = [""];
coloredLetters = {};
currentWordIndex = 0;
}
}

View File

@ -0,0 +1,6 @@
enum KeyboardKeys{
q,w,e,r,t,y,u,i,o,p,
a,s,d,f,g,h,j,k,l,
z,x,c,v,b,n,m,
enter
}

View File

@ -0,0 +1,6 @@
enum Letter {
unknown,
notInWord,
wrongSpot,
correctSpot,
}

View File

@ -0,0 +1,3 @@
enum MessageTypes{
info,error,success
}

View File

@ -0,0 +1,51 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:guessing_words/core/data/data_singleton.dart';
import 'package:guessing_words/core/data/enums/keyboard_keys.dart';
import 'package:guessing_words/core/data/enums/letter.dart';
import 'package:guessing_words/core/data/enums/message_types.dart';
part 'home_state.dart';
class HomeCubit extends Cubit<HomeState> {
HomeCubit() : super(HomeInitial());
DataSingleton data = DataSingleton();
void setLetter(KeyboardKeys key) {
if (data.setLetter(key)) {
emit(GridUpdateState());
}
}
void removeLetter() {
data.removeLetter();
emit(GridUpdateState());
}
bool submitWord() {
final state = data.submitWord();
if (state is GridUpdateState ||
state is WinGameState ||
state is LoseGameState) {
emit(state);
if (state is WinGameState) {
emit(GridUpdateState());
}
return true;
}else if(state is SnackBarMessage){
emit(state);
return false;
}
return false;
}
void updateKey(KeyboardKeys key, Letter letterType) {
emit(KeyboardKeyUpdateState(key, letterType));
}
Future clearGameArea() async {
data.resetData();
await data.createWord();
emit(HomeInitial());
}
}

View File

@ -0,0 +1,49 @@
part of 'home_cubit.dart';
abstract class HomeState extends Equatable {
const HomeState();
}
class HomeInitial extends HomeState {
@override
List<Object> get props => [];
}
class GridUpdateState extends HomeState {
final id = DateTime.now().microsecondsSinceEpoch;
@override
List<Object> get props => [id];
}
class KeyboardKeyUpdateState extends HomeState {
final id = DateTime.now().microsecondsSinceEpoch;
final KeyboardKeys key;
final Letter letterType;
KeyboardKeyUpdateState(this.key, this.letterType);
@override
List<Object> get props => [id,key,letterType];
}
class SnackBarMessage extends HomeState {
final id = DateTime.now().microsecondsSinceEpoch;
final MessageTypes type;
final String message;
SnackBarMessage(this.type, this.message);
@override
List<Object> get props => [id, type, message];
}
class LoseGameState extends HomeState {
@override
List<Object> get props => [];
}
class WinGameState extends HomeState {
@override
List<Object> get props => [];
}

View File

@ -0,0 +1,368 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_countdown_timer/countdown_timer_controller.dart';
import 'package:flutter_countdown_timer/current_remaining_time.dart';
import 'package:flutter_countdown_timer/flutter_countdown_timer.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:top_snackbar_flutter/custom_snack_bar.dart';
import 'package:top_snackbar_flutter/top_snack_bar.dart';
import 'package:twemoji/twemoji.dart';
import 'package:guessing_words/core/const/color_constants.dart';
import 'package:guessing_words/core/const/text_constants.dart';
import 'package:guessing_words/core/data/data_singleton.dart';
import 'package:guessing_words/core/data/enums/message_types.dart';
import 'package:guessing_words/core/presentation/home/cubit/home_cubit.dart';
import 'package:guessing_words/core/presentation/home/widget/home_content.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) => _showTimerIfNeeded());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
Padding(
padding: const EdgeInsets.only(right: 50),
child: GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
child: Dialog(
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(24.0)),
),
child: _buildDialogBody(),
),
);
});
},
child: const Icon(Icons.help_outline)),
),
],
title: Text(
TextConstants.gameTitle,
style: GoogleFonts.mulish(fontSize: 32, fontWeight: FontWeight.w800),
),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
centerTitle: true,
shadowColor: Colors.transparent,
),
body: _buildBody(context),
);
}
BlocProvider<HomeCubit> _buildBody(BuildContext context) {
return BlocProvider<HomeCubit>(
create: (BuildContext context) => HomeCubit(),
child: BlocConsumer<HomeCubit, HomeState>(
listenWhen: (_, currState) => true,
listener: (context, state) {
final homeCubit = BlocProvider.of<HomeCubit>(context);
if (state is SnackBarMessage) {
SnackBarMessage message = state;
_showMessage(message, context);
} else if (state is WinGameState) {
_makeWin(homeCubit);
} else if (state is LoseGameState) {
_makeLose(homeCubit);
}
},
buildWhen: (_, currState) => currState is HomeInitial,
builder: (context, state) {
return HomeContent(
key: UniqueKey(),
);
},
),
);
}
void _showMessage(SnackBarMessage myMessage, BuildContext context) {
switch (myMessage.type) {
case MessageTypes.info:
showTopSnackBar(
context,
CustomSnackBar.info(
message: myMessage.message,
),
);
break;
case MessageTypes.success:
showTopSnackBar(
context,
CustomSnackBar.success(
message: myMessage.message,
),
);
break;
case MessageTypes.error:
showTopSnackBar(
context,
CustomSnackBar.error(
message: myMessage.message,
),
);
break;
}
}
_buildDialogBody() {
return Container(
padding: const EdgeInsets.symmetric(vertical: 36, horizontal: 20),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(50)),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
alignment: Alignment.center,
width: double.infinity,
child: Text(TextConstants.howToPlayTitle,
style: GoogleFonts.mulish(
fontSize: 32, fontWeight: FontWeight.w700)),
),
const SizedBox(
height: 20,
),
Text(TextConstants.howToPlayText,
style: GoogleFonts.mulish(
fontSize: 15, fontWeight: FontWeight.w600)),
const SizedBox(
height: 20,
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: ColorConstants.primaryGreyLight,
borderRadius: BorderRadius.circular(12)),
child: Center(
child: Text(
"A",
style: GoogleFonts.mulish(
fontSize: 26, fontWeight: FontWeight.w600),
),
),
),
const SizedBox(
width: 16,
),
Text(TextConstants.howToPlayRole1,
style: GoogleFonts.mulish(
fontSize: 15, fontWeight: FontWeight.w600)),
],
),
const SizedBox(
height: 20,
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: ColorConstants.primaryOrange,
borderRadius: BorderRadius.circular(12)),
child: Center(
child: Text(
"A",
style: GoogleFonts.mulish(
fontSize: 26, fontWeight: FontWeight.w600),
),
),
),
const SizedBox(
width: 16,
),
Flexible(
child: Text(TextConstants.howToPlayRole2,
style: GoogleFonts.mulish(
fontSize: 15, fontWeight: FontWeight.w600))),
],
),
const SizedBox(
height: 20,
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: ColorConstants.primaryGreen,
borderRadius: BorderRadius.circular(12)),
child: Center(
child: Text(
"A",
style: GoogleFonts.mulish(
fontSize: 26, fontWeight: FontWeight.w600),
),
),
),
const SizedBox(
width: 20,
),
Flexible(
child: Text(TextConstants.howToPlayRole3,
style: GoogleFonts.mulish(
fontSize: 15, fontWeight: FontWeight.w600))),
],
),
],
),
);
}
void _showTimerIfNeeded() async {
final s = await DataSingleton().createWord();
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.containsKey("last_game_word")) {
if (prefs.getString("last_game_word") == s) {
_showTimerDialog(prefs.getBool("last_game_result") ?? false);
}
}
}
void _makeWin(homeCubit) async {
await _writeResults(true);
await homeCubit.clearGameArea();
_showTimerIfNeeded();
}
void _makeLose(homeCubit) async {
await _writeResults(false);
await homeCubit.clearGameArea();
_showTimerIfNeeded();
}
Future _writeResults(bool isWin) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final lastWord = DataSingleton().secretWord;
if (lastWord.isNotEmpty) {
await prefs.setString('last_game_word', lastWord);
await prefs.setBool('last_game_result', isWin);
}
}
void _showTimerDialog(bool isWin) {
final now = DateTime.now();
final nextMidnight =
DateTime(now.year, now.month, now.day).add(const Duration(days: 1));
final int endTime = DateTime.now().millisecondsSinceEpoch +
nextMidnight.difference(now).inMilliseconds;
final CountdownTimerController _countDownController =
CountdownTimerController(endTime: endTime, onEnd: _dismissTimerDialog);
showDialog(
barrierDismissible: false,
context: context,
barrierColor: Colors.transparent,
builder: (BuildContext ctx) {
return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: Dialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(24.0)),
),
backgroundColor: isWin
? ColorConstants.primaryGreenLight
: ColorConstants.primaryRedLight,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: double.infinity,
child: Center(
child: Text(
isWin ? TextConstants.youWin : TextConstants.youLose,
style: GoogleFonts.mulish(
fontSize: 32, fontWeight: FontWeight.w700),
)),
),
const SizedBox(
height: 20,
),
Twemoji(
emoji: isWin ? '🎉' : '😩',
height: 80,
width: 80,
),
const SizedBox(
height: 30,
),
SizedBox(
width: double.infinity,
child: Center(
child: Text(
TextConstants.nextWordle,
style: GoogleFonts.mulish(
fontSize: 25, fontWeight: FontWeight.w700),
)),
),
const SizedBox(
height: 16,
),
CountdownTimer(
controller: _countDownController,
widgetBuilder: (_, CurrentRemainingTime? time) {
if (time == null) {
return Container();
}
final duration = Duration(
//hours: time.hours ?? 0,
minutes: time.min ?? 0,
seconds: time.sec ?? 0);
return Text(
_durationToString(duration),
style: GoogleFonts.mulish(
fontSize: 20, fontWeight: FontWeight.w600),
);
},
),
],
),
),
),
);
});
}
void _dismissTimerDialog() async {
await DataSingleton().createWord();
Navigator.of(context, rootNavigator: true).pop();
}
String _durationToString(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, "0");
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return "$twoDigitMinutes:$twoDigitSeconds";
}
}

View File

@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:guessing_words/core/const/color_constants.dart';
import 'package:guessing_words/core/data/data_singleton.dart';
import 'package:guessing_words/core/presentation/home/cubit/home_cubit.dart';
class Grid extends StatelessWidget {
const Grid({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocBuilder<HomeCubit, HomeState>(
buildWhen: (_, currentState) => currentState is GridUpdateState,
builder: (context, state) {
final data = DataSingleton();
final letters = data.getLetters();
return Container(
margin: const EdgeInsets.symmetric(horizontal: 30),
child: GridView.count(
mainAxisSpacing: 10,
crossAxisSpacing: 10,
scrollDirection: Axis.vertical,
shrinkWrap: true,
crossAxisCount: 5,
children: List.generate(30, (index) {
final letter = letters.length > index ? letters[index] : "";
Color color = ColorConstants.primaryGreyLight;
if (letter.isNotEmpty &&
data.currentWordIndex > 0 &&
index < 5 * data.currentWordIndex) {
final indexInRow = index % 5;
if(data.gridData.join().contains(letter)){
color=ColorConstants.primaryGreyMedium;
}
if (data.secretWord.contains(letter)) {
color = ColorConstants.primaryOrange;
}
if (data.secretWord[indexInRow] == letter) {
color = ColorConstants.primaryGreen;
}
}
return Container(
width: 50,
height: 50,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(12)),
color: color,
),
child: Text(
letter.toUpperCase(),
style: GoogleFonts.mulish(
fontWeight: FontWeight.w600, fontSize: 30),
),
);
}),
),
);
},
);
}
}

View File

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:guessing_words/core/presentation/home/widget/keyboard.dart';
import 'grid.dart';
class HomeContent extends StatelessWidget {
const HomeContent({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(top: 10),
width: double.infinity,
height: double.infinity,
color: Colors.white,
child: Column(
children: const [
Grid(),
Spacer(
flex: 2,
),
Keyboard(),
Spacer(),
],
),
);
}
}

View File

@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:guessing_words/core/data/enums/keyboard_keys.dart';
import 'keyboard_key.dart';
class Keyboard extends StatelessWidget {
const Keyboard({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Row(
children: const [
Flexible(flex:1,child: KeyboardKey(keyboardKey: KeyboardKeys.q,)),
Flexible(flex:1,child: KeyboardKey(keyboardKey: KeyboardKeys.w)),
Flexible(flex:1,child: KeyboardKey(keyboardKey: KeyboardKeys.e)),
Flexible(flex:1,child: KeyboardKey(keyboardKey: KeyboardKeys.r)),
Flexible(flex:1,child: KeyboardKey(keyboardKey: KeyboardKeys.t)),
Flexible(flex:1,child: KeyboardKey(keyboardKey: KeyboardKeys.y)),
Flexible(flex:1,child: KeyboardKey(keyboardKey: KeyboardKeys.u)),
Flexible(flex:1,child: KeyboardKey(keyboardKey: KeyboardKeys.i)),
Flexible(flex:1,child: KeyboardKey(keyboardKey: KeyboardKeys.o)),
Flexible(flex:1,child: KeyboardKey(keyboardKey: KeyboardKeys.p)),
],
),
Row(
children: const [
Spacer(flex: 5,),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.a,)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.s)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.d)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.f)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.g)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.h)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.j)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.k)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.l)),
Spacer(flex: 5,),
],
),
Row(
children: const [
Flexible(flex:14,child: EnterKeyboardKey()),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.z)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.x)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.c)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.v)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.b)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.n)),
Flexible(flex:10,child: KeyboardKey(keyboardKey: KeyboardKeys.m)),
Flexible(flex:14,child: BackspaceKeyboardKey()),
],
),
],
),
);
}
}

View File

@ -0,0 +1,136 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:guessing_words/core/data/data_singleton.dart';
import 'package:guessing_words/core/data/enums/keyboard_keys.dart';
import 'package:guessing_words/core/data/enums/letter.dart';
import 'package:guessing_words/core/presentation/home/cubit/home_cubit.dart';
class KeyboardKey extends StatelessWidget {
const KeyboardKey(
{Key? key, this.color = Colors.black26, required this.keyboardKey})
: super(key: key);
final Color color;
final KeyboardKeys keyboardKey;
@override
Widget build(BuildContext context) {
final homeCubit = BlocProvider.of<HomeCubit>(context);
return BlocBuilder<HomeCubit, HomeState>(
buildWhen: (_, currentState) {
if (currentState is KeyboardKeyUpdateState) {
KeyboardKeyUpdateState state = currentState;
if (state.key.name == keyboardKey.name) {
return true;
}
}
return false;
},
builder: (context, currentState) {
final data = DataSingleton();
return Padding(
padding: const EdgeInsets.all(4.0),
child: InkWell(
onTap: () {
homeCubit.setLetter(keyboardKey);
},
child: AspectRatio(
aspectRatio: 2 / 3,
child: Container(
decoration: BoxDecoration(
color: data.getKeyColor(keyboardKey),
borderRadius: BorderRadius.circular(4.6)),
child: Center(
child: Text(
keyboardKey.name.toUpperCase(),
style: const TextStyle(fontWeight: FontWeight.w500),
),
),
),
),
),
);
},
);
}
}
class EnterKeyboardKey extends StatelessWidget {
const EnterKeyboardKey({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final homeCubit = BlocProvider.of<HomeCubit>(context);
final data = DataSingleton();
return Padding(
padding: const EdgeInsets.all(4.0),
child: AspectRatio(
aspectRatio: 1 / 1,
child: InkWell(
onTap: () {
if (homeCubit.submitWord()) {
data.gridData[data.currentWordIndex - 1]
.split("")
.asMap()
.map((index, e) {
final key =
KeyboardKeys.values.firstWhere((KeyboardKeys element) {
return element.name == e;
});
if (data.secretWord[index] == e) {
homeCubit.updateKey(key, Letter.correctSpot);
return MapEntry(index, e);
}
if (data.secretWord.contains(e)) {
homeCubit.updateKey(key, Letter.wrongSpot);
return MapEntry(index, e);
}
homeCubit.updateKey(key, Letter.notInWord);
return MapEntry(index, e);
});
}
},
child: Container(
decoration: BoxDecoration(
color: Colors.black26,
borderRadius: BorderRadius.circular(4.6)),
child: Center(
child: Text(
KeyboardKeys.enter.name.toUpperCase(),
style: const TextStyle(fontWeight: FontWeight.w500),
),
),
),
),
),
);
}
}
class BackspaceKeyboardKey extends StatelessWidget {
const BackspaceKeyboardKey({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final homeCubit = BlocProvider.of<HomeCubit>(context);
return Padding(
padding: const EdgeInsets.all(4.0),
child: AspectRatio(
aspectRatio: 1 / 1,
child: InkWell(
onTap: () {
homeCubit.removeLetter();
},
child: Container(
decoration: BoxDecoration(
color: Colors.black26,
borderRadius: BorderRadius.circular(4.6)),
child: const Center(
child: Icon(Icons.backspace_outlined),
),
),
),
),
);
}
}

View File

@ -1,115 +1,19 @@
import 'package:flutter/material.dart';
import 'package:fullscreen/fullscreen.dart';
import 'core/presentation/home/page/home_page.dart';
void main() {
runApp(const MyApp());
runApp(const GuessingWords());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
class GuessingWords extends StatelessWidget {
const GuessingWords({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
FullScreen.enterFullScreen(FullScreenMode.EMERSIVE_STICKY);
return const MaterialApp(
home: HomePage(),
);
}
}

View File

@ -8,6 +8,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
bloc:
dependency: transitive
description:
name: bloc
url: "https://pub.dartlang.org"
source: hosted
version: "8.0.3"
boolean_selector:
dependency: transitive
description:
@ -43,6 +50,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
cupertino_icons:
dependency: "direct main"
description:
@ -50,6 +64,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
desktop_window:
dependency: "direct main"
description:
name: desktop_window
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
equatable:
dependency: "direct main"
description:
name: equatable
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
fake_async:
dependency: transitive
description:
@ -57,11 +85,39 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.2"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_bloc:
dependency: "direct main"
description:
name: flutter_bloc
url: "https://pub.dartlang.org"
source: hosted
version: "8.0.1"
flutter_countdown_timer:
dependency: "direct main"
description:
name: flutter_countdown_timer
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.0"
flutter_lints:
dependency: "direct dev"
description:
@ -69,11 +125,65 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
flutter_svg:
dependency: transitive
description:
name: flutter_svg
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
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"
fullscreen:
dependency: "direct main"
description:
name: fullscreen
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
google_fonts:
dependency: "direct main"
description:
name: google_fonts
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.1"
http:
dependency: transitive
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.4"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
io:
dependency: transitive
description:
name: io
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.4"
lints:
dependency: transitive
description:
@ -94,7 +204,7 @@ packages:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
version: "0.1.4"
meta:
dependency: transitive
description:
@ -102,13 +212,174 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
nested:
dependency: transitive
description:
name: nested
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
version: "1.8.1"
path_drawing:
dependency: transitive
description:
name: path_drawing
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
path_parsing:
dependency: transitive
description:
name: path_parsing
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
path_provider:
dependency: transitive
description:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.9"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.12"
path_provider_ios:
dependency: transitive
description:
name: path_provider_ios
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.5"
path_provider_macos:
dependency: transitive
description:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "4.4.0"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.4"
provider:
dependency: transitive
description:
name: provider
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.2"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.13"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.11"
shared_preferences_ios:
dependency: transitive
description:
name: shared_preferences_ios
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
shared_preferences_web:
dependency: "direct main"
description:
name: shared_preferences_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
sky_engine:
dependency: transitive
description: flutter
@ -120,7 +391,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.8.2"
stack_trace:
dependency: transitive
description:
@ -155,7 +426,21 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.8"
version: "0.4.9"
top_snackbar_flutter:
dependency: "direct main"
description:
name: top_snackbar_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
twemoji:
dependency: "direct main"
description:
name: twemoji
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.2"
typed_data:
dependency: transitive
description:
@ -169,6 +454,35 @@ packages:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
version: "2.1.2"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0+1"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "5.3.1"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
sdks:
dart: ">=2.16.0-134.5.beta <3.0.0"
dart: ">=2.16.0-100.0.dev <3.0.0"
flutter: ">=2.10.0-0"

View File

@ -1,89 +1,34 @@
name: guessing_words
description: A new Flutter project.
# 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 used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
environment:
sdk: ">=2.16.0-134.5.beta <3.0.0"
sdk: ">=2.15.1 <3.0.0"
# 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.
flutter_bloc: ^8.0.1
top_snackbar_flutter: ^1.0.2
flutter_countdown_timer: ^4.1.0
shared_preferences: ^2.0.12
equatable: ^2.0.3
twemoji: ^0.4.2
cupertino_icons: ^1.0.2
google_fonts: ^2.2.0
shared_preferences_web: ^2.0.3
desktop_window: ^0.4.0
fullscreen: ^1.0.3
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: ^1.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.
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:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# 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/custom-fonts/#from-packages
assets:
- assets/words.txt

View File

@ -13,7 +13,7 @@ import 'package:guessing_words/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
await tester.pumpWidget(const GuessingWords());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);

View File

@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h"
#include <desktop_window/desktop_window_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
DesktopWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopWindowPlugin"));
}

View File

@ -3,6 +3,10 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
desktop_window
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
@ -13,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)