From d27cfa49c9ff756336e208e5a157d22fa435fb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr> Date: Tue, 30 Apr 2024 15:19:11 +0200 Subject: [PATCH] Improve game architecture --- android/app/build.gradle | 2 +- android/gradle.properties | 4 +- assets/icons/colors_5.png | Bin 1516 -> 0 bytes assets/icons/colors_6.png | Bin 2010 -> 0 bytes assets/icons/colors_7.png | Bin 1543 -> 0 bytes assets/icons/colors_8.png | Bin 2193 -> 0 bytes assets/icons/level_easy.png | Bin 3055 -> 0 bytes assets/icons/level_hard.png | Bin 7309 -> 0 bytes assets/icons/level_medium.png | Bin 5156 -> 0 bytes assets/icons/level_nightmare.png | Bin 9098 -> 0 bytes assets/icons/size_extra.png | Bin 962 -> 0 bytes assets/icons/size_large.png | Bin 774 -> 0 bytes assets/icons/size_medium.png | Bin 661 -> 0 bytes assets/icons/size_small.png | Bin 480 -> 0 bytes assets/icons/skin_default.png | Bin 949 -> 0 bytes assets/skins/default_0.png | Bin 157 -> 0 bytes assets/skins/default_1.png | Bin 157 -> 0 bytes assets/skins/default_2.png | Bin 157 -> 0 bytes assets/skins/default_3.png | Bin 157 -> 0 bytes assets/skins/default_4.png | Bin 157 -> 0 bytes assets/skins/default_5.png | Bin 157 -> 0 bytes assets/skins/default_6.png | Bin 157 -> 0 bytes assets/skins/default_7.png | Bin 157 -> 0 bytes assets/skins/default_8.png | Bin 157 -> 0 bytes assets/translations/en.json | 2 - assets/translations/fr.json | 2 - .../metadata/android/en-US/changelogs/39.txt | 1 + .../metadata/android/fr-FR/changelogs/39.txt | 1 + icons/build_game_icons.sh | 64 --- icons/colors_5.svg | 2 - icons/colors_6.svg | 2 - icons/colors_7.svg | 2 - icons/colors_8.svg | 2 - icons/level_easy.svg | 2 - icons/level_hard.svg | 2 - icons/level_medium.svg | 2 - icons/level_nightmare.svg | 2 - icons/size_extra.svg | 3 - icons/size_large.svg | 3 - icons/size_medium.svg | 3 - icons/size_small.svg | 3 - icons/skin_default.svg | 2 - icons/skins/default/0.svg | 2 - icons/skins/default/1.svg | 2 - icons/skins/default/2.svg | 2 - icons/skins/default/3.svg | 2 - icons/skins/default/4.svg | 2 - icons/skins/default/5.svg | 2 - icons/skins/default/6.svg | 2 - icons/skins/default/7.svg | 2 - icons/skins/default/8.svg | 2 - lib/config/default_game_settings.dart | 81 ++++ lib/config/default_global_settings.dart | 28 ++ lib/config/menu.dart | 63 +-- lib/cubit/game_cubit.dart | 170 ++++++++ lib/cubit/game_state.dart | 19 + .../{bottom_nav_cubit.dart => nav_cubit.dart} | 22 +- lib/cubit/settings_game_cubit.dart | 84 ++++ lib/cubit/settings_game_state.dart | 19 + lib/cubit/settings_global_cubit.dart | 61 +++ lib/cubit/settings_global_state.dart | 19 + lib/entities/cell.dart | 37 -- lib/main.dart | 50 ++- lib/models/board.dart | 144 +++++++ lib/models/cell.dart | 18 + lib/models/game.dart | 114 ++++++ lib/models/settings_game.dart | 81 ++++ lib/models/settings_global.dart | 41 ++ lib/models/types.dart | 3 + lib/provider/data.dart | 340 ---------------- lib/ui/layout/game.dart | 42 -- lib/ui/layout/parameters.dart | 119 ------ lib/ui/layout/tileset.dart | 50 --- lib/ui/painters/board_painter.dart | 32 +- lib/ui/painters/parameter_painter.dart | 372 ++++++++++++++++++ lib/ui/screens/about_page.dart | 41 -- lib/ui/screens/game_page.dart | 37 -- lib/ui/screens/page_about.dart | 38 ++ lib/ui/screens/page_game.dart | 18 + lib/ui/screens/page_settings.dart | 23 ++ lib/ui/screens/settings_page.dart | 26 -- lib/ui/skeleton.dart | 47 +-- lib/ui/widgets/app_bar.dart | 37 -- lib/ui/widgets/bottom_nav_bar.dart | 40 -- lib/ui/widgets/button_game_start_new.dart | 34 ++ .../{home => game}/button_game_restart.dart | 12 +- lib/ui/widgets/game/cell_interactive.dart | 55 +++ lib/ui/widgets/game/game.dart | 37 ++ lib/ui/widgets/game/game_board.dart | 43 ++ lib/ui/widgets/game/game_top_indicator.dart | 70 ++++ lib/ui/widgets/game/indicator_top.dart | 64 --- lib/ui/widgets/game/message_game_end.dart | 64 +-- lib/ui/widgets/game/select_color_bar.dart | 56 +-- lib/ui/widgets/global_app_bar.dart | 82 ++++ lib/ui/widgets/helpers/app_titles.dart | 17 + lib/ui/widgets/{ => helpers}/header_app.dart | 5 +- .../widgets/helpers/outlined_text_widget.dart | 49 +++ .../widgets/home/button_game_start_new.dart | 38 -- lib/ui/widgets/parameters.dart | 121 ++++++ lib/ui/widgets/settings/theme_card.dart | 2 +- lib/utils/board_utils.dart | 168 -------- lib/utils/color_extensions.dart | 35 ++ lib/utils/color_theme.dart | 4 +- lib/utils/game_utils.dart | 20 - lib/utils/tools.dart | 8 + pubspec.lock | 90 ++--- pubspec.yaml | 10 +- 107 files changed, 2108 insertions(+), 1416 deletions(-) delete mode 100644 assets/icons/colors_5.png delete mode 100644 assets/icons/colors_6.png delete mode 100644 assets/icons/colors_7.png delete mode 100644 assets/icons/colors_8.png delete mode 100644 assets/icons/level_easy.png delete mode 100644 assets/icons/level_hard.png delete mode 100644 assets/icons/level_medium.png delete mode 100644 assets/icons/level_nightmare.png delete mode 100644 assets/icons/size_extra.png delete mode 100644 assets/icons/size_large.png delete mode 100644 assets/icons/size_medium.png delete mode 100644 assets/icons/size_small.png delete mode 100644 assets/icons/skin_default.png delete mode 100644 assets/skins/default_0.png delete mode 100644 assets/skins/default_1.png delete mode 100644 assets/skins/default_2.png delete mode 100644 assets/skins/default_3.png delete mode 100644 assets/skins/default_4.png delete mode 100644 assets/skins/default_5.png delete mode 100644 assets/skins/default_6.png delete mode 100644 assets/skins/default_7.png delete mode 100644 assets/skins/default_8.png create mode 100644 fastlane/metadata/android/en-US/changelogs/39.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/39.txt delete mode 100644 icons/colors_5.svg delete mode 100644 icons/colors_6.svg delete mode 100644 icons/colors_7.svg delete mode 100644 icons/colors_8.svg delete mode 100644 icons/level_easy.svg delete mode 100644 icons/level_hard.svg delete mode 100644 icons/level_medium.svg delete mode 100644 icons/level_nightmare.svg delete mode 100644 icons/size_extra.svg delete mode 100644 icons/size_large.svg delete mode 100644 icons/size_medium.svg delete mode 100644 icons/size_small.svg delete mode 100644 icons/skin_default.svg delete mode 100644 icons/skins/default/0.svg delete mode 100644 icons/skins/default/1.svg delete mode 100644 icons/skins/default/2.svg delete mode 100644 icons/skins/default/3.svg delete mode 100644 icons/skins/default/4.svg delete mode 100644 icons/skins/default/5.svg delete mode 100644 icons/skins/default/6.svg delete mode 100644 icons/skins/default/7.svg delete mode 100644 icons/skins/default/8.svg create mode 100644 lib/config/default_game_settings.dart create mode 100644 lib/config/default_global_settings.dart create mode 100644 lib/cubit/game_cubit.dart create mode 100644 lib/cubit/game_state.dart rename lib/cubit/{bottom_nav_cubit.dart => nav_cubit.dart} (51%) create mode 100644 lib/cubit/settings_game_cubit.dart create mode 100644 lib/cubit/settings_game_state.dart create mode 100644 lib/cubit/settings_global_cubit.dart create mode 100644 lib/cubit/settings_global_state.dart delete mode 100644 lib/entities/cell.dart create mode 100644 lib/models/board.dart create mode 100644 lib/models/cell.dart create mode 100644 lib/models/game.dart create mode 100644 lib/models/settings_game.dart create mode 100644 lib/models/settings_global.dart create mode 100644 lib/models/types.dart delete mode 100644 lib/provider/data.dart delete mode 100644 lib/ui/layout/game.dart delete mode 100644 lib/ui/layout/parameters.dart delete mode 100644 lib/ui/layout/tileset.dart create mode 100644 lib/ui/painters/parameter_painter.dart delete mode 100644 lib/ui/screens/about_page.dart delete mode 100644 lib/ui/screens/game_page.dart create mode 100644 lib/ui/screens/page_about.dart create mode 100644 lib/ui/screens/page_game.dart create mode 100644 lib/ui/screens/page_settings.dart delete mode 100644 lib/ui/screens/settings_page.dart delete mode 100644 lib/ui/widgets/app_bar.dart delete mode 100644 lib/ui/widgets/bottom_nav_bar.dart create mode 100644 lib/ui/widgets/button_game_start_new.dart rename lib/ui/widgets/{home => game}/button_game_restart.dart (62%) create mode 100644 lib/ui/widgets/game/cell_interactive.dart create mode 100644 lib/ui/widgets/game/game.dart create mode 100644 lib/ui/widgets/game/game_board.dart create mode 100644 lib/ui/widgets/game/game_top_indicator.dart delete mode 100644 lib/ui/widgets/game/indicator_top.dart create mode 100644 lib/ui/widgets/global_app_bar.dart create mode 100644 lib/ui/widgets/helpers/app_titles.dart rename lib/ui/widgets/{ => helpers}/header_app.dart (77%) create mode 100644 lib/ui/widgets/helpers/outlined_text_widget.dart delete mode 100644 lib/ui/widgets/home/button_game_start_new.dart create mode 100644 lib/ui/widgets/parameters.dart delete mode 100644 lib/utils/board_utils.dart create mode 100644 lib/utils/color_extensions.dart delete mode 100644 lib/utils/game_utils.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 9a2e79e..4cb92c9 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -37,7 +37,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 33 + compileSdkVersion 34 namespace "org.benoitharrault.colors" defaultConfig { diff --git a/android/gradle.properties b/android/gradle.properties index 604fc08..c8bbff9 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=0.0.38 -app.versionCode=38 +app.versionName=0.0.39 +app.versionCode=39 diff --git a/assets/icons/colors_5.png b/assets/icons/colors_5.png deleted file mode 100644 index dd0a276e105a79aa25ca36f6cc4d2f69c77df81f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1516 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@v$I14-?iy0WCPk}I_+{y{! z3=9mCC9V-A!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2NHH)l*#`K8xH2#>FfuYQ zGBGkSF|x2Sv$8X@aj<Z3v2t*;a`Ccp^Re*=aPSIp@CkD8332iZbMlLD3W{<GiE|4} z@Q6t9h)VH_%koLc@kuKQ$S4cSstU=e2`OrcC~1o->4+-ph^pv{spyHRgv+aBsi^9U zt41rT#VD#9NvMY?Xc$RonM!HZYH6EEY1eA&SV-$x%jnz48raGj*~=L@$eTFHn>Z<$ z&M`G}RWx@~vhYx{^i;NvQn&F@vGG;4^;5Nt*0A$avk!=~4^(#y3U>@@bPU#T3T|== z(QppcbP3aR4fk-3&~l5?_K3Fgh|%#*(DP2w_fIkKPc;ZkGYrZw3d%GJ&NL3rG7iZ$ z4vo_f%{~yCV-l9A6_%tQmTMC3s}<>~85N=xRd6b%)FL)WGd4*rw#+iF!ZNYiI=Rj! zxz0AV(Jn1TF|Eliz1cpa#Xht7ZDy-OW}8D+ihNePW469Zc86n5r&CUsQ*O6YUbk~z zk8^&nbAF#oLC@d939dyGU5h5V6;F06aaJvz;$Aw{y<AhJe7a}FOwY<$Ue$BFYqZsB zb<}F-`_wJ)saxP%ztFdSkzd1Nzs4p0O-^df%L1C02ehsXY+Du7wmPW2Sf+hVP=~8V z$C}_SH;t}R*{<~=-5Wx>H-`3X3hmt-*1t7;!uE&>J0d3Th?ul9a?-BI$-5&b?~a<X zCu-{6=&Adnr|pZLwm)Y2fta}(+UtDucIX-H)HgmnPw7mg+1W_5v&)p9S=qlkp!q-F z`TsecI%U2k3=GT$o-U3d6}R5r`5r9nC~@qgv6zr+;E^t&qkU4YZ&rj@CosBhWN7LV z4Y3gk?BYslR^W)(=q%(BQT_M!8I8j`J{p}k{ou{&^WTr}esiYwnbFB-dHS>30_Gb> z&rEyA6v29V4Jgg8Z|E}%;!}*-GEq-aX3IobH-_);bb@9+^*uS;L)CohahJ)rUYRji zY>wN%G-T!VW2rI<Qv}0Tx1V2XqI)ZoqvHF_OTPLG9tbUD(oc96<n@`6fh6!?Kidu? z?fd|%)whL(t_ynkY<RsnJ%FpfP*S%>*V=d0hMikG%r>pqx^sfrCZDNGkFPb|?rHi> zc)_Vp?z->f3f}#Y4645Uo4w%a`FRo>OZhLnuU+`%--2M#1CR5k*g@QKfMJ8-N&ohl z3~^s=AZ}}@m)MZ__0$TD+lNv&dRm{m|D0oj)GmePt)>TNXP$Gt$B;i+dj_*f@TA%1 z4wsc=&oJIn;xBI4)@z**!25mvl!>dD-Q177KWQBnG->{HDSPb$4;HWR3w4;9x+J&z zpUbUh`{b-|gl~uqZI~aGW7nX0hT+g=7dDm>Bj%3j2RAL~ly+Ehx*@BqA=+rc6xj!@ zJQ`{26<W;eHZk8wZI~;o;BNJ=(NC3;ccr@3-#4$ecr_Ge)lC%5yybGY;=12YS&`K` z4wn!0?W((9d_slUPm|}umc-d_KYcqUY8SllK-p5pd&kz<7d?O8SrIWyEbY_Wo@E?Y z8ST~nHYz5&@fiLoKAWz?@b9*M1@p4%3g)J0mh1y7tPV;U{9E^cM`FV=->2QXuR7Yw z{BPkps=t1B_w?vL^8c39^nWt9*wdDs`S}dn^i58|S-km%n%iEi&wh8Opy>Lo6&))V zDjwE#R9v_%^riT`-<2z~PU}_A&Ek%)mi@i-_^+qzQ(kS3UBw_*yY!3Ubxuem5KsPz zb@<Efl|O}XO8WCjx1NWwD9Ge4v1-w3*vHDiz))f<osl{#(s{-CTK4Okc122MY<USX N+tbz0Wt~$(699v-Cj|fi diff --git a/assets/icons/colors_6.png b/assets/icons/colors_6.png deleted file mode 100644 index 2a63916ed00f0570bedd1647e34706d041fbec44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2010 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@v$I14-?iy0WCPk}I_+{y{! z3=9mCC9V-A!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2NHH)loel5_ab;j&U}R)q zW@2PvVq|4zWMg4uXJO)CW#VLG;$~;&;b7+FWES9J5#(kO;$acyVG-eF73F0W<6{-) zXO$FSlM-Z;7G#qVVv`YKmlt7I6y;D7<4_joP{~qJmEcs9;8d67)Rf}X3X#>S)zYrj z){)`TiICHcl+%;t)|cltkmoiIkTy}`F;(I*onvaI%xj^-YpKd>t;T1g&S$I6XQ#pE zpvmv3#qX>w;HoR&rYqpCC*Ywc=;2@IsW0dm(BNet=wm44Yb4}vEEHfY6yzBmY$_aL zCK75c5}JJ=+(IPW(?7yeB+^PW(oZ_7;8e7=Xq>HBoS$^OtyqF)WTIwNqM<{gomi5+ zc(Q|dvbRl&sdcKOc)GJhhO<Ow^V@7YgKSsHTsO&FAA@`kse+!rg`QGHt{SD@(q+MN z<y!LPwzB08vK78Em3}f+dhOM2veoXg)d8~A!P3=XQZ><%wQ5SWu@VjTiVePUjRt*< z!E#L+%1wcCO(Am4A#yF6$}OSttzq(QVe;+a@*Uywo%YI|5ei+A3O!K@J<$q%F^YW! zlKoDq{V|FYVihMkt4@qlnpi9~DNbo}ywcPJrD=)E(=C=wPgI_fq&zc8d3K7*oD`M0 z8rpMHRpzCs%uiQckgmGWX5+#P)g@VKOS9CLWveaEQD2dxzA{&RRi65qe2umF8tZ)Y z))i=MEYjRkqP4YDYg?(-_A;#<dPY0SwRh?p@2b$=U8%jNQhRTe&i-nh12sAaYjh9K zQ#w+wd$eBfScBg22E7xFdMBIoPc`YEZq`2&X?CVX|7@h$*%tk?%aqTx8k}!6xX@;B zson5em*KT8qwC#9H@b~(^%~#qHNMkle1C$;g9#=NCz?E(Wb$~D$&<;ZPbZr`v$B6S z#q{}9(-%|CUQRQ6HQns>46`>g%-_y5e>cng-2u(_v&=utw)i;P;?o?9|M|}U&*@BY z53gWgVCnO8aSW-r_4dy1`60y;>>oC7yS8bgnd@{{*Au0l;ft2MP*U0H5-76jZPSsU z7fNA<dRsjrgWk>&ExUVLG;xi}vQV$YGiPp^TJzU8Po4UCj^$?S^~>+p+CG2!`R7NQ z`=6_9-&?LXer|OCy#!m6Owp!KHnY~SImlI-R5CCyG{^=>i>W%Locy+>Lur$3_FaaJ z?8~~17TZ`nRSR6ka{73m;L~+8xD~AaPAylRna<YMyTfaO;9;h;{>iGAi9T0EKm605 zWD~?cqoiG7@+-#WbCpkOGLQy7>}QKGerBt3@^0GI6^_$6j50!|Rz6!f>5D}6PTv|` zZNa1ajbpTKZ#LO;S^xItHKxnu+_&xh`ipxX>**T(sdc}a7mNM-#2vUVU)7-^_}_u# zn^E0|cgOCyVU}>Y_Ir1czpplf($VRuyVi<Z-Zwi|^Qzp_Zm0RJM-6}b%RM$dXl0!D zH+WKhluW?p2OUea%l0t**b%Ml-Dkt_VXNsLo)x>deRPe##rRR0H=kKe`W~Ce?k#g_ z4_?StyI=6Lp-+zCjmM8)Z(_f^J@~5sb^f~rG8fKY{I@h(mN`j2B#Uj$)3@?FH+6oz z#jr7EpVziYHf#}Rf1TG+UeD;4-oW%pO5wn-_i>Lc56tD+&}YN`BJaKZ^Zm>lp6`A& zL7ZXt<u?tt|J@U*Rp0Obt?}$j+x2>PmdYG>uVf~WJGVmQc1cA2{w#+5y{rGN=s%qs zxc|H;Guwvl14rL|UnF_{C2NV-+jyO{EY^t3m5e>AUW^Atew*K_Ss}c^GPL2M=_;Xw zWjqFAt6A0*Z<}dc@oMqDrj_DPRT%o#U*(86T$N+KebKsw0@|<NisW43h&UN#b3Lw0 zzl34kb&iUO_NPBT^5J{2NbrMJ_C|eux1Wd8Zsl_^N=sQYeUY$di56kHmnQqIC}ewK z!%Bez(Pv6S9_L2vj8IfKVwLk==kxtjT_Q{xx9=rZ9SMAUiXq#S=|}d_vwg1&*SxyU zv19Jmh!+;@mm<F!_coYEbT6(fwBFCxV#4Gk`DSL3zV^C=hIvx!kDQyFz`1(v*(~8w zrnofUvOhP&v>smH_`QT_(VOlo5_0wtH?PhMwK`tPq%pt#+~T$M=N|}HKJ*OY;`n%a z?ZoIUvsbKpG>vE9&fo~`hAyFba{ce-Sfr-fu4KLNK}7DQ`=q1;_a@JJ^MB2TGzQk$ zRR=4M*#+?~coo<C&Az~1ll{W_aJ78<;y~Gz>;gY0&*gC5X6=?WccW-@ZCuiKNo8#z zHm!MIwm<GXc>L!5=W!dKsZS4FwLDhn?aDhZv~?<e{hXxxds0t;(Z{QwgZo#j_3ZYW zP$<0W%+J%RkygjE!W<I*th&w;X=;~X_qkmq{(K)3X}O7kLFvF6cGj7p{1LyVs^ps) zD_Fb@3tYx}`uQ2Lrw=1GGo9e-P7-uYsW$tg?d0umoA(i9{(lwi^NUX!2|StbS^UR? V4Izr}6dr-ZJzf1=);T3K0RV)w5hnlu diff --git a/assets/icons/colors_7.png b/assets/icons/colors_7.png deleted file mode 100644 index fdc1669a1d89ed225cc4624319ff01558ab987b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1543 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@v$I14-?iy0WCPk}I_+{y{! z3=9mCC9V-A!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2NHH)l*#`K8xH2#>FfuSO zF)}hUGO{o+vNAETF)^_-GjT98bFwgVu`qM9vhcF9@UgK9u(Jwtu!(ZAi*d3`a&bs= zbIS8_De!VB@^LHkajRshsPgluty`xqz@sj}tKl!F86>Y+SECgouT`t1EySl?tF7JK zq$9+q)7qkY=8W#yvwFh(dTz4%!u<Lo{02E$Mxp|yVuGe~OwGgu&BcW*{{Od<5Vn;R zagr8wmJxOJs(15lbe9$PkQ4W^j`Wt7@R67BHIw(V2=sGz2vC#?FqIE<wh2*|4po*8 z%{~xr;t;9q6s0N~Rd6a=#V$roE><}zR$VSmB|6T)Bwo!pUR^#}LnlR3A;m~J#au5{ zOEFDLF;iP9v-xe7x<a;^LXMeyuC8*fo?@Pka-N=YzP@6<y=;M=N`by|LC@bpeU&0b z#Ug#xVlU}Zf2ndqwF)EkDn;cgz4mHjjcPNc8e@$*6U};4%?2fv22;&OWz|N5zD85c zR&(t(E0s=5oi0n=?rf<Z8?`=by*^vDzFg^kYrP3JdXsGQC)w&xw$-0zXE5Dj*>rou z8TN*=9E@f=7|qqtp66t|&}QQzXOkr^rpsJSSGbw2bTeD!Zob;xe2u&LS`UkLK6>jt zEjD^uZt}9+>TR{n+iIJS^$tCwo%+VRd~J67+U)kT+3#<=-{0<FfZd@0yTgI@hvz9B z3A8^NWPdEk;dqe4@nDA&!44;b9Z!Wgo(^$56Y6v(((G)g)7eO~v&)p9S=qlkp!q-F z`TsecrbiWZ3=GWPo-U3d6}R5re(paxP~`Z>rrTBlce-|@-|bp)LO>+YC2&r|q8*|K zJDCGR<V2SG9l7RMsJ}or*Y@20vud9XIiEVgIO%!I{qKK$	Yd&bl4k&AQMpeXHi# zTRa;kUdjO_`2P&?i#5zAhL>IPell(DmsX}9-=BA#+PK^3diSZ4o0ElI-hOCjs3>~B ze_BX}#iJl|F0bBViNMLKy`CRhSoZwXe3>LLq1Gj!Ou!&>;gXdMq=5^I8Sl+G#677` z^4yUdqD@J{bC1pGoU~}t-i2cEtMfXw)@QGnFn3MH3hn80*BG6CRd{%_RN&UTOe^f} zK3p}s|BhW@(3N}3-Z99${Za46XDV;F?(d{8Ul$zLX_#HF@)zoqhIQ;URadK4TRfPf z$(Z}(#o1^5Uox0=o{9%}onlC>`L~lXmEk>yS^W*=AhsLF^%dq#VZ0Z4^UvR`t&ER; zvc6GEZ8-ig=U?L2Qw)tS|1jxIHgYhWU*8lj`M{fn{f4Jxg7o})OHJlIZ%#jZ|Jg}8 zL2`e?*(E#$?dR=(G#fSKo3PJON^N*t^Dof%6vKP=LI+k&<~0vL>no);sQ>W}_z|4i zaG<7+G22t}0oVNc2hphw#~#WQG)!UK_W7ss-BS!(|5`rK(qumJ;b%Jg6vlUvQPrLo z89($tZMeFGCm<p7tEwyOgU14VH(XCKy#Db=@ariC)4di8W=vre`~36x@2QMl(SPQZ zOfO-)GqL8ML=fAJ<jl{iN53@eVwBm?IfXH7+viD1We1uK_%sw^8UBmd{Xe0W+OS#I z&WZOD>kpeL=l3^EVKj@lRqOeXAz1gXAnUo$Z%=Ld#y2DTZBW2<y?t5nw%?Yy_)j)D zU$87aM{vr-cP8gzriV-t&-!_LvPpL563I5@wVxI)IAzja_;~x0_dn&JkwPr>;$lOg zv8pROqw91QQC3FP`zp5loFCr%2u)2YPt6sb`gGItr`=X>__zLel`ZmA?b9j0s?Ix` tCfgaEd{w9pD$4(*Zq+=S<+$SVUsiUm)Y%_p|0ja%_H^}gS?83{1OU_kG$a53 diff --git a/assets/icons/colors_8.png b/assets/icons/colors_8.png deleted file mode 100644 index b660ef3e29aefc5c940a327aaf7a22d76a73bb23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2193 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 diff --git a/assets/icons/level_easy.png b/assets/icons/level_easy.png deleted file mode 100644 index aa9e2485272c12fd99d9e1faae5d71d027ee5431..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3055 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 XSzn-jOXOAt1_lOCS3j3^P6<r_R}qmZ diff --git a/assets/icons/level_hard.png b/assets/icons/level_hard.png deleted file mode 100644 index 433c686b984fd22c4966def3dedcaf7384951810..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7309 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 diff --git a/assets/icons/level_medium.png b/assets/icons/level_medium.png deleted file mode 100644 index 213f2ca844afb0cea47cc4bdd1b865433b068b59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5156 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 CrMu_= diff --git a/assets/icons/level_nightmare.png b/assets/icons/level_nightmare.png deleted file mode 100644 index df4182c1e228bbb33c5546d2df38d16ca1edd4f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9098 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 diff --git a/assets/icons/size_extra.png b/assets/icons/size_extra.png deleted file mode 100644 index d3401d9dd4df7a871eb7ac88592707f63480d0dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 962 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@v$I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zA<O}c#ab*AjMs`+S z4G_@O)X>({(ACn^)zZ|{($dn_)YsND(AG56*4Eb1GStyB*3r??)iTl5)z#P6H!v_T z)Uh=*G&C|YGB!3gF)=YSGcz|gx3I9Vw6wIcva+_ewzajjx3_n2aBy;Ra&d8Sb#--f zb8~lh_wexW@$vEV^Yi!j4+sbd3=9kp508wDjEahij*gCriHVDgi+5ZfA0MBTl$4yD zoRX4~mX?;4m6e~LUr<m`US3{NQBhM<Q&(5l($do2-rn2W+t=4OVZwxo6DLlYGG*Gd zX|rd~o-=38ym|B1tXZ>R!-kzZckbD<=g5&G$BrGlaN)w`%a`xmx%1$`gC|d(ynXxj z<HwKx|NpltwK>kfz+~#_;uunK>+PN6K_ZC~tQQ)Y`}n+s1Y8B$71EA2%qTw4ar1k< zp!nuLDW)O3y5IHkudZFUdFHzP*}J0G9!t;IaZoaAYt-TG(sLd%FmWg}U?u)}A9(Yz z{yB$5)GtGWDE7q(Yre={xcAs-b1m~S9+O~3mI4Ka$=Ryv2a8^wZ{BHT!5HRMFE(do z^T}G4oIjd7najNY%i64Hp7$pF<^5*!(hY(R42&!SaH8M@qwI{^dX~I;)BjmTtZ2?G zSp6k?K{R{ug_qI>MmL=QdN44uTu@Jt*|l3W{Gi`U`-a)28#wHy{?n`oVULwKUyJH& zi#-jT5q~3#cx!b2if;&Ie|}*5rTr}DUnPjD{bG9(An(Ay%f2P?U*buj8#;e&FRX2r zEZF}g{J>GG8SQ27n2{YO;K0z!x+QT_v=OJ6*L~|Ozv~ALH^lt9XTLn{+YYDy?x$>~ zaws&kF)vH_TYggQN6fGFd!L7wZs4h#@=v!Skp1;eek5NCI54!!81Q*pt7<<ed^x|F zyR?97kJewY9f9oLZ;oqgSYBZla5&5;8&MO!iErMNec!c9R`ON(FN~jFCw*sav+K9V zrFZU}K#L<bu8cqK&Fs?|m?SI~e4PK@{st#%U?4^5f`ttWAF`#dxccJ{E9;?cSL~-K Qz5(SZPgg&ebxsLQ0E{1ItN;K2 diff --git a/assets/icons/size_large.png b/assets/icons/size_large.png deleted file mode 100644 index 18b3420564654a9adf32e5a91d0d41f82fd4e2ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 774 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@v$I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zA*c;#z;>y6lz{tSB z&&H~up`od%simc*t*x!2qhnxTU~Ft`YHDh(qh)SxZmp|rZEbC1V`HbMZLg=};Nalo z<mBw^?CR?3;o;$BsOx2@=Vz?vXRPn%=ND+KA84W<5)u+>Vi0PoA7y3`7Z(?AZkUje zkd%~^oSdARnwplDR#H+@QBhG<RaISG-QZx>+uJ*7(xfR<rc9kWb;gVtvu4ejJ$v?? zIdisc+qP%Vo`VMu9y@mIY^c-Ir%ykA{P^k9r~m)|>&2?gU|?W8<>}%WQgQ3;9nWCl zK#_)vJS?n20y9&2x)ivYj86RcUoZCM?7IAf>l5AQ?*6mu-fF9ruS)jDm8=f^v&EX# z|F`6Yow^lP40yqNiGok1f8Ow|x_Ls}F0|J0yZ*+6_pNXJq?mbj3(r(!U~pL6u>1F~ zId1>w{`y;T)&BWEU9p&@b>@Nx4ky>1Tm0YmuOWl?PWfX^^(*a>Eiz%b(f9Yf{_D;1 zwf0|6uUvKh$vz?Ox+QkMRJTbppYeU>W5dqCa6ur!_M83g73aUdXkQtBWdDVncdIu( zNPm@n@wzQLvT65Z69j(OdkCu^&(n{8v2VkI->;e)ooyK$7Mp+k<W>9Y;_sFHaZdiv z{~7A(EZKMGgYO$lWbGgJGh}c4e=xAtP5t1~EB$WkTZ`uRa?f5V?-F0Uf}Mdu!ajQA z|F6EaeT)Bl{aqIR_2-@Pq=WOXtY4g8$%kxC54*_!OriMgj0_rL0e{kO-Mfb@i^&#Y h&nV}bSGM{`B~yZ2+wU_c&Xs}`dAj<!taD0e0stH>9UcGx diff --git a/assets/icons/size_medium.png b/assets/icons/size_medium.png deleted file mode 100644 index 4b4f7754fb46de7cf2ddfc265da900bf77cc0d3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 661 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@v$I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zASQ_9H;>y6lz{toT z#KWSYp`oRvrK_uJU|?WrX=!C;Wo>P3Yinz7Z|~sX5bdfH9UUFxrV|qr6B`?w<f)sS zoSc%9l9iQ}<FA|Jua_I3m!F?sP*6}5tXCYY-<6=>m0-}FsNbDv(3@n?muxU0#c)cR z;hZ^hR<B;YY15{iJ9qBcv*&Q5$&n*RjvYI8{P^(;7cN}BeEIh6+YcT*c=F`Q+qZ8& ze*E}pj>Z4~|7W~e{gi=$vCz}SF{I+w+uJAom>fk|0}gNsrn?@EbYUrUdh*}ixAxtx z6h|j1@AI+urk^?;S-NWSs*p|oTc;JSn%{7%`q)ZF2BN^9$^#F+JumsTuV@zExugp- zi*f?29#mzn+sVkl5W%s)TIZSGueD3$*ByOe_i{HktBqe*W#>PYm+I1n72dxTk*shy z+3@+sZE4ZZ0)Oi+^<PW=Fz3tO7y91pIp@Cn?crr$2rzn}@$KEVZp)5+GrzpODj}ox z*YC^meW197oARISLf77}X@*~4giqSHQ>OXY6lUG&RSXObH;V+Te?Lx|x9@+v>farL z&DWQ_cdvhH4cExPz+fP)@LOFY&XAEIh5N)m%XjZNkY#bR9o!nSbRUOk-+a#eAu!;3 S`i3cTAjO`pelF{r5}E+3Nbo}d diff --git a/assets/icons/size_small.png b/assets/icons/size_small.png deleted file mode 100644 index 2c72840d2dfe2dfb36791973a159869a325de758..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 480 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@v$I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zA@Cfh;ab;j&U}R+A z=402;(9qP>)Y8(@*4Eb1(J?hOH8(eRa&mHYbxkOBPOfxGsd7n8O--wHEuZ3BHN&rO zOT_d8F|%gPnmv2=|Ns9*mhx&bFfdAbx;TbZ+<JR!H(!&3h-)C5=uvSEyMO=vLv_?m zj<xLERaTi1!KygfC$v@nYNNNmLoH_vF9T`dgL=b5o51>-YyP&fU5s<Y<V%?t7&N#p z+}$*7eeLA;u@f6!+peyBd%I>o6SBcI>;)3{)3=_hnqT|5*7RL_62tHFd|#^>8sZuE z*gq<pZu-u5-}ctpBmY2w3)lFW=}^3BK{P9af{wzUt*3vY7ztN}o58{8QTiZAbRT<` WRNXSKx%+!SN<CfuT-G@yGywo@K!HpE diff --git a/assets/icons/skin_default.png b/assets/icons/skin_default.png deleted file mode 100644 index ee53cc12eaad99722e3dd8fadd52e76bdf5ed977..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 949 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@v$I14-?iy0WCPk}I_+{y{! z3=9mCC9V-A!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2NHH)lS_k-qxPnaJ<W$L0 zQ4Lj9Q&UrmR8wndQCqi8JxX1pvr{urQ?t8Ut5!?9tW3LBTldTv-Lq#+=a^dj|8E~) z?~s`2<elpj(B>5A<P;z89N6LFo$2Y9;2RJfXdD)39vbKn5*QvB7#kbp5fYkxAWAba z$}cFY;8e6$a<sE^bYM_)n0u^)V{AxJyk|hXmrJ6jV`8LTnrlF&b3kUSac1+|Ty5Rl zc)fz2zePnw#l^)XB_*Y$rDYEKWo2a*>RMHL?bX%QH8nN0n%ec6I*kT>O)ilw4%)45 zQ5}U!9UUE=&bpnQon2jB-7b3F#Yz(=PMkDp(sYYub2YT*&6_uW{`>_C7A&;cxNzaZ zMT-_KS+ZoEkKPVFqn-N3J0mI%$J8C3r*tOL>};gj*=5R)vL`>YvVV6#^HcHM|M|}U z&*}8PH$Tn5!1&wK#WAGf*4ta=;nI#0t%;l|O`d8UDJ*Kwdp1foO*oiv_yE)H(^(#B zhyVX))$@6nv}$!0|NGnZ>F0ybgqcrPT(u)dZ)xg%2FvM}iWnFee#lQ~37S~Eu}soh zJac*{f55ys(`IR~*3Mu0t0^da_m%0J*adv&$7<zD&X$)GmO2+5e0N32Rk=#W@P04r zW6#3(ajt!`c4c;G78?W6;GU_%yc@q3ez|(%-Z{C2=i;*M>H=21>#99`{q6gpZ1sn` z%67`lTa!8c_PwWhAEI9wT-kp3;lgic53qZ<O<y8rUV7l!r&U)%9#tRNe4XFRzL(En z|GX*xbSnNkZ8kse^?NEqU;NZ}j6}Pp;S__W{NJT@Wix+0%zm}}cwxEk-k`}F|0+Lr zQvQ1_M@HlA*`LSm2OeK;wv;WId--#x<*eC#aZ}zsQzqI9h6l0CS<zFndUQp~Tf11Z zzvWHU+tctVp*^HrQ$lKj6qi@==OE+M`yWg>mnuC^dTUgscS%q9eUW)BBLl;O_?t0a dr_(yL-sx|cT*6g7f7cq2#h$KyF6*2UngA4;UC{sl diff --git a/assets/skins/default_0.png b/assets/skins/default_0.png deleted file mode 100644 index 4d1712540f2a45d43a2aa512132e7274f1daf686..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6jLZxS3>iZITo@P_I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zAU=HvJasB`Q|MDZ! zCm0wQq&;06LnI{M9yDZRU|=|;@Oyb2FMk390|P^Wj?e)H#&-6L??60HS3j3^P6<r_ Dst+uv diff --git a/assets/skins/default_1.png b/assets/skins/default_1.png deleted file mode 100644 index 3ca3e7164af5cde6e6750172c2149cb63ebb7165..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6jLZxS3>iZITo@P_I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zAU=HvJaeZcGKl4^n z4+8^(w5N+>h=k<ZgNBR@3=D@9elL&X<xgN>U|=ZF5jw!Y*v@|O9f;@Y>gTe~DWM4f DQmrc; diff --git a/assets/skins/default_2.png b/assets/skins/default_2.png deleted file mode 100644 index 56ad59eff020c5dd84e9d257ff91c5f8f88e6f33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6jLZxS3>iZITo@P_I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zAU=HvJaV_ZiyL=;0 z4g&*&w5N+>h=k<ZgNBR@3=D@9elL&X<xgN>U|=ZF5jw!Y*v@|O9f;@Y>gTe~DWM4f DRnaRZ diff --git a/assets/skins/default_3.png b/assets/skins/default_3.png deleted file mode 100644 index e92c6694a192606905b8119f2a213b8cadf14ba5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6jLZxS3>iZITo@P_I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zAU=HvJaW$P|$~RYf z3IhX!w5N+>h=k<ZgNBR@3=D@9elL&X<xgN>U|=ZF5jw!Y*v@|O9f;@Y>gTe~DWM4f D24^X~ diff --git a/assets/skins/default_4.png b/assets/skins/default_4.png deleted file mode 100644 index 74feaa46de20c31c30e28c8c1dd96d5bff212f02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6jLZxS3>iZITo@P_I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zAU=HvJas7Wzr_Oo4 z2m=Fyw5N+>h=k<ZgNBR@3=D@9elL&X<xgN>U|=ZF5jw!Y*v@|O9f;@Y>gTe~DWM4f DL%k}> diff --git a/assets/skins/default_5.png b/assets/skins/default_5.png deleted file mode 100644 index abcf897497ebc0ccab435b576985dd854be011fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6jLZxS3>iZITo@P_I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zAU=HvJas8j~EE1Gg z%D})N?djqeA|d(qpdljz1H&PO-^=58`4boz7#Iq4gbpw;wzFS+2jY3U`njxgN@xNA DDUm8y diff --git a/assets/skins/default_6.png b/assets/skins/default_6.png deleted file mode 100644 index aee88ace319e14392688b29f92fa0da137bb4339..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6jLZxS3>iZITo@P_I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zAU=HvJab0M$aYN{b z3<d@UX-^l&5DCe*2Mrk+7#I#I{9Yc%%b&o&z`#(TBXod)v7P<mI}p#))z4*}Q$iB} DTV^Xr diff --git a/assets/skins/default_7.png b/assets/skins/default_7.png deleted file mode 100644 index 3e0ac15c6eea57e437afc36ebf81a618b342256e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6jLZxS3>iZITo@P_I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zAU=HvJakcpW-&kcf zF9QREw5N+>h=k<ZgNBR@3=D@9elL&X<xgN>U|=ZF5jw!Y*v@|O9f;@Y>gTe~DWM4f DHqk1Q diff --git a/assets/skins/default_8.png b/assets/skins/default_8.png deleted file mode 100644 index 70b58df7443e74de8523829600b4c6cc19eab5a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6jLZxS3>iZITo@P_I14-?iy0VXB0!k&?2PAU z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zAU=HvJas5<0clNaS z6$}gv(w;7kArg{r4;nHuFfbfa_`N)imp_4lfq|hwN9X_pV>|oBcOagptDnm{r-UW| DZOto} diff --git a/assets/translations/en.json b/assets/translations/en.json index c40bf79..9229916 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,8 +1,6 @@ { "app_name": "Colors", - "long_press_to_quit": "Long press to quit game...", - "bottom_nav_home": "Game", "bottom_nav_settings": "Settings", "bottom_nav_about": "About", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 895e185..dc7d503 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,8 +1,6 @@ { "app_name": "Colors", - "long_press_to_quit": "Appuyer longtemps pour quitter le jeu...", - "bottom_nav_home": "Jeu", "bottom_nav_settings": "Réglages", "bottom_nav_about": "Infos", diff --git a/fastlane/metadata/android/en-US/changelogs/39.txt b/fastlane/metadata/android/en-US/changelogs/39.txt new file mode 100644 index 0000000..9bb4c4d --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/39.txt @@ -0,0 +1 @@ +Improve application architecture. diff --git a/fastlane/metadata/android/fr-FR/changelogs/39.txt b/fastlane/metadata/android/fr-FR/changelogs/39.txt new file mode 100644 index 0000000..5face79 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/39.txt @@ -0,0 +1 @@ +Amélioration de l'architecture de l'application. diff --git a/icons/build_game_icons.sh b/icons/build_game_icons.sh index 1817ed0..7ea6ed5 100755 --- a/icons/build_game_icons.sh +++ b/icons/build_game_icons.sh @@ -22,31 +22,6 @@ AVAILABLE_GAME_IMAGES=" game_win " -# Settings images -AVAILABLES_GAME_SETTINGS=" - colors:5,6,7,8 - level:easy,medium,hard,nightmare - size:small,medium,large,extra -" - -# Skins -AVAILABLE_SKINS=" - default -" - -# Images per skin -SKIN_IMAGES=" - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 -" - ####################################################### # optimize svg @@ -89,41 +64,13 @@ function build_icon() { optipng ${OPTIPNG_OPTIONS} ${TARGET} } -function build_settings_icons() { - INPUT_STRING="$1" - - SETTING_NAME="$(echo "${INPUT_STRING}" | cut -d":" -f1)" - SETTING_VALUES="$(echo "${INPUT_STRING}" | cut -d":" -f2 | tr "," " ")" - - for SETTING_VALUE in ${SETTING_VALUES} - do - SETTING_CODE="${SETTING_NAME}_${SETTING_VALUE}" - build_icon ${CURRENT_DIR}/${SETTING_CODE}.svg ${ASSETS_DIR}/icons/${SETTING_CODE}.png - done -} - -function build_icon_for_skin() { - SKIN_CODE="$1" - - # skin main image - build_icon ${CURRENT_DIR}/skin_${SKIN_CODE}.svg ${ASSETS_DIR}/icons/skin_${SKIN_CODE}.png - - # skin images - for SKIN_IMAGE in ${SKIN_IMAGES} - do - build_icon ${CURRENT_DIR}/skins/${SKIN_CODE}/${SKIN_IMAGE}.svg ${ASSETS_DIR}/skins/${SKIN_CODE}_${SKIN_IMAGE}.png - done -} - ####################################################### # Create output folders mkdir -p ${ASSETS_DIR}/icons -mkdir -p ${ASSETS_DIR}/skins # Delete existing generated images find ${ASSETS_DIR}/icons -type f -name "*.png" -delete -find ${ASSETS_DIR}/skins -type f -name "*.png" -delete # build game images for GAME_IMAGE in ${AVAILABLE_GAME_IMAGES} @@ -131,14 +78,3 @@ do build_icon ${CURRENT_DIR}/${GAME_IMAGE}.svg ${ASSETS_DIR}/icons/${GAME_IMAGE}.png done -# build settings images -for GAME_SETTING in ${AVAILABLES_GAME_SETTINGS} -do - build_settings_icons "${GAME_SETTING}" -done - -# build skins images -for SKIN in ${AVAILABLE_SKINS} -do - build_icon_for_skin "${SKIN}" -done diff --git a/icons/colors_5.svg b/icons/colors_5.svg deleted file mode 100644 index 2f218e9..0000000 --- a/icons/colors_5.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#97c05c" stroke="#000" stroke-width="2"/><g transform="translate(-1.0061 .33115)" aria-label="5"><path d="m63.315 55.063q0 2.2656-0.85938 4.2188-0.83984 1.9336-2.4609 3.3203-1.7969 1.4844-4.1406 2.207-2.3242 0.70312-5.3125 0.70312-3.4961-0.01953-5.918-0.56641-2.4023-0.52734-3.9258-1.1914v-6.4258h0.82031q1.7773 1.0547 3.8281 1.7578t4.082 0.70312q1.2305 0 2.6562-0.27344 1.4453-0.29297 2.2852-1.0352 0.66406-0.60547 0.99609-1.2305 0.35156-0.625 0.35156-1.9336 0-1.0156-0.46875-1.7383-0.44922-0.74219-1.1719-1.1914-1.0547-0.64453-2.5391-0.83984-1.4844-0.21484-2.6953-0.21484-1.7578 0-3.3789 0.3125-1.6016 0.29297-2.8125 0.58594h-0.85938v-16.406h20.684v5.5664h-13.633v4.7461q0.60547-0.03906 1.5234-0.05859 0.9375-0.03906 1.6406-0.03906 2.4023 0 4.2773 0.46875 1.8945 0.44922 3.2617 1.2695 1.7773 1.0742 2.7734 2.8516 0.99609 1.7578 0.99609 4.4336z"/></g><rect x="67.01" y="67.01" width="29.323" height="29.323" fill="#ff6f43" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="36.339" width="29.323" height="29.323" fill="#ffce2c" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="5.6672" width="29.323" height="29.323" fill="#359c35" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="36.339" y="5.6672" width="29.323" height="29.323" fill="#708cfd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="5.6671" y="5.6672" width="29.323" height="29.323" fill="#e63a3f" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/></svg> diff --git a/icons/colors_6.svg b/icons/colors_6.svg deleted file mode 100644 index 6df1641..0000000 --- a/icons/colors_6.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#f29c38" stroke="#000" stroke-width="2"/><g transform="translate(3.8694 .21001)" aria-label="6"><path d="m59.025 55.399q0 2.2461-0.82031 4.2188t-2.3047 3.3203q-1.582 1.4453-3.6719 2.207-2.0703 0.76172-4.8633 0.76172-2.6172 0-4.7852-0.70312-2.1484-0.72266-3.6914-2.1875-1.7773-1.6797-2.7148-4.3359t-0.9375-6.3477q0-3.8281 0.87891-6.7969t2.8711-5.2539q1.9141-2.1875 4.9609-3.3984 3.0664-1.2109 7.2852-1.2109 1.4258 0 3.125 0.19531t2.207 0.29297v5.7227h-0.74219q-0.52734-0.25391-1.7969-0.56641-1.25-0.33203-2.8516-0.33203-3.75 0-5.8398 1.8359t-2.5195 5.1172q1.5039-0.89844 3.1641-1.4453 1.6797-0.56641 3.6133-0.56641 1.6992 0 3.1445 0.39062 1.4648 0.39062 2.7148 1.25 1.6211 1.1328 2.5977 3.0469 0.97656 1.9141 0.97656 4.7852zm-8.8867 4.3945q0.60547-0.66406 0.95703-1.5625 0.37109-0.91797 0.37109-2.4805 0-1.4258-0.41016-2.3242-0.41016-0.91797-1.1328-1.4648-0.70312-0.54688-1.6602-0.74219-0.95703-0.21484-1.9727-0.21484-0.85938 0-1.7969 0.19531-0.9375 0.19531-1.7188 0.48828 0 0.19531-0.01953 0.64453t-0.01953 1.1328q0 2.4023 0.46875 3.9648 0.48828 1.543 1.2891 2.3633 0.54688 0.60547 1.2891 0.89844 0.74219 0.27344 1.6016 0.27344 0.64453 0 1.4258-0.29297t1.3281-0.87891z"/></g><rect x="36.339" y="67.01" width="29.323" height="29.323" fill="#a13cb1" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="67.01" width="29.323" height="29.323" fill="#ff6f43" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="36.339" width="29.323" height="29.323" fill="#ffce2c" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="5.6672" width="29.323" height="29.323" fill="#359c35" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="36.339" y="5.6672" width="29.323" height="29.323" fill="#708cfd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="5.6671" y="5.6672" width="29.323" height="29.323" fill="#e63a3f" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/></svg> diff --git a/icons/colors_7.svg b/icons/colors_7.svg deleted file mode 100644 index 7870bc7..0000000 --- a/icons/colors_7.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#cd5542" stroke="#000" stroke-width="2"/><g transform="translate(3.9378 .2393)" aria-label="7"><path d="m58.459 42.001-13.164 23.301h-8.3594l13.672-23.516h-14.941v-5.5664h22.793z"/></g><rect x="5.6671" y="67.01" width="29.323" height="29.323" fill="#38ffff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="36.339" y="67.01" width="29.323" height="29.323" fill="#a13cb1" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="67.01" width="29.323" height="29.323" fill="#ff6f43" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="36.339" width="29.323" height="29.323" fill="#ffce2c" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="5.6672" width="29.323" height="29.323" fill="#359c35" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="36.339" y="5.6672" width="29.323" height="29.323" fill="#708cfd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="5.6671" y="5.6672" width="29.323" height="29.323" fill="#e63a3f" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/></svg> diff --git a/icons/colors_8.svg b/icons/colors_8.svg deleted file mode 100644 index 37f3949..0000000 --- a/icons/colors_8.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#6041b0" stroke="#000" stroke-width="2"/><g transform="translate(4.1624 .2393)" aria-label="8"><path d="m59.123 57.177q0 3.8672-3.3008 6.3281-3.2812 2.4609-9.0234 2.4609-3.2227 0-5.5273-0.66406-2.3047-0.66406-3.8086-1.8359-1.4844-1.1523-2.207-2.6953-0.70312-1.543-0.70312-3.3203 0-2.1875 1.2695-3.8672 1.2891-1.6992 4.4336-2.9688v-0.11719q-2.5391-1.1719-3.7305-2.9492t-1.1914-4.1211q0-3.457 3.2031-5.6641 3.2031-2.207 8.3398-2.207 5.3906 0 8.4375 2.0117 3.0664 1.9922 3.0664 5.332 0 2.0703-1.2891 3.6914t-3.9453 2.7539v0.11719q3.0469 1.1523 4.5117 3.1055t1.4648 4.6094zm-8.0664-13.438q0-1.4844-1.1523-2.3633-1.1328-0.87891-3.0273-0.87891-0.70312 0-1.4453 0.17578-0.72266 0.17578-1.3281 0.50781-0.56641 0.33203-0.9375 0.87891-0.37109 0.52734-0.37109 1.2109 0 1.1523 0.64453 1.7969 0.66406 0.64453 2.1484 1.2891 0.54688 0.23438 1.4844 0.58594 0.95703 0.33203 2.3047 0.76172 0.89844-1.0547 1.2891-1.8945 0.39062-0.83984 0.39062-2.0703zm0.60547 13.77q0-1.4062-0.70312-2.1289t-2.8906-1.6602q-0.64453-0.29297-1.875-0.72266-1.2305-0.42969-2.0703-0.74219-0.83984 0.76172-1.5234 1.8555-0.66406 1.0742-0.66406 2.4219 0 2.0312 1.4453 3.2422 1.4648 1.1914 3.8086 1.1914 0.625 0 1.4648-0.17578 0.83984-0.19531 1.4453-0.58594 0.70312-0.44922 1.1328-1.0547 0.42969-0.60547 0.42969-1.6406z"/></g><rect x="5.6671" y="36.339" width="29.323" height="29.323" fill="#f2739d" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="5.6671" y="67.01" width="29.323" height="29.323" fill="#38ffff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="36.339" y="67.01" width="29.323" height="29.323" fill="#a13cb1" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="67.01" width="29.323" height="29.323" fill="#ff6f43" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="36.339" width="29.323" height="29.323" fill="#ffce2c" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="5.6672" width="29.323" height="29.323" fill="#359c35" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="36.339" y="5.6672" width="29.323" height="29.323" fill="#708cfd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="5.6671" y="5.6672" width="29.323" height="29.323" fill="#e63a3f" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/></svg> diff --git a/icons/level_easy.svg b/icons/level_easy.svg deleted file mode 100644 index 30048ce..0000000 --- a/icons/level_easy.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#97c05c" stroke="#000" stroke-width="2"/><path d="m50.952 32.393c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434c-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044s4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#030303" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/></svg> diff --git a/icons/level_hard.svg b/icons/level_hard.svg deleted file mode 100644 index 976249e..0000000 --- a/icons/level_hard.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#cd5542" stroke="#000" stroke-width="2"/><path d="m28.065 11.952c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434c-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044s4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#010101" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m73.839 11.952c1.3622-0.0046 4.9652 11.398 6.07 12.195s13.062 0.61914 13.487 1.9133c0.42533 1.2941-9.3059 8.2444-9.7225 9.5414-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434c-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#010101" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m50.952 52.835c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133 0.42533 1.2941-9.3059 8.2444-9.7225 9.5414-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 5e-3 -10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434-10.203-8.1785-9.7868-9.4754c0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80442 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#010101" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/></svg> diff --git a/icons/level_medium.svg b/icons/level_medium.svg deleted file mode 100644 index e70fd60..0000000 --- a/icons/level_medium.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#f29c38" stroke="#000" stroke-width="2"/><path d="m27.72 32.393c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434c-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044s4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m74.183 32.393c1.3622-0.0046 4.9652 11.398 6.07 12.195s13.062 0.61914 13.487 1.9133-9.3059 8.2444-9.7225 9.5414c-0.41656 1.297 3.4475 12.614 2.3482 13.418-1.0994 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797-1.1048-0.79696 2.6827-12.14 2.2574-13.434s-10.203-8.1785-9.7868-9.4754c0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/></svg> diff --git a/icons/level_nightmare.svg b/icons/level_nightmare.svg deleted file mode 100644 index 87f28a3..0000000 --- a/icons/level_nightmare.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#6041b0" stroke="#000" stroke-width="2"/><path d="m28.929 11.793c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80442-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797-1.1048-0.79696 2.6827-12.14 2.2574-13.434-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m73.125 11.861c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133 0.42533 1.2941-9.3059 8.2444-9.7225 9.5414-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434-10.203-8.1785-9.7868-9.4754c0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80442 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m28.778 52.923c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133 0.42533 1.2941-9.3059 8.2444-9.7225 9.5414s3.4475 12.614 2.3481 13.418c-1.0993 0.80442-10.717-6.3028-12.079-6.2982-1.3622 5e-3 -10.931 7.1767-12.036 6.3797-1.1048-0.79697 2.6827-12.14 2.2574-13.434-0.42533-1.2941-10.203-8.1785-9.7868-9.4754s12.375-1.2 13.474-2.0044c1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m73.104 52.992c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80442-10.717-6.3028-12.079-6.2982-1.3622 5e-3 -10.931 7.1767-12.036 6.3797-1.1048-0.79697 2.6827-12.14 2.2574-13.434-0.42533-1.2941-10.203-8.1785-9.7868-9.4754s12.375-1.2 13.474-2.0044c1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/></svg> diff --git a/icons/size_extra.svg b/icons/size_extra.svg deleted file mode 100644 index fe07dfb..0000000 --- a/icons/size_extra.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="0" fill="#5f41af" stroke="#000" stroke-width="2"/> - <g transform="translate(4.4197 1.4084)" fill="#fff" stroke="#282828" stroke-linecap="round" stroke-width="3"><rect x="13.796" y="16.807" width="12.714" height="12.714"/><rect x="26.51" y="16.807" width="12.714" height="12.714"/><rect x="39.223" y="16.807" width="12.714" height="12.714"/><rect x="51.937" y="16.807" width="12.714" height="12.714"/><rect x="64.651" y="16.807" width="12.714" height="12.714"/><rect x="13.796" y="29.521" width="12.714" height="12.714"/><rect x="26.51" y="29.521" width="12.714" height="12.714"/><rect x="39.223" y="29.521" width="12.714" height="12.714"/><rect x="51.937" y="29.521" width="12.714" height="12.714"/><rect x="64.651" y="29.521" width="12.714" height="12.714"/><rect x="13.796" y="42.235" width="12.714" height="12.714"/><rect x="26.51" y="42.235" width="12.714" height="12.714"/><rect x="39.223" y="42.235" width="12.714" height="12.714"/><rect x="51.937" y="42.235" width="12.714" height="12.714"/><rect x="64.651" y="42.235" width="12.714" height="12.714"/><rect x="13.796" y="54.948" width="12.714" height="12.714"/><rect x="26.51" y="54.948" width="12.714" height="12.714"/><rect x="39.223" y="54.948" width="12.714" height="12.714"/><rect x="51.937" y="54.948" width="12.714" height="12.714"/><rect x="64.651" y="54.948" width="12.714" height="12.714"/><rect x="13.796" y="67.662" width="12.714" height="12.714"/><rect x="26.51" y="67.662" width="12.714" height="12.714"/><rect x="39.223" y="67.662" width="12.714" height="12.714"/><rect x="51.937" y="67.662" width="12.714" height="12.714"/><rect x="64.651" y="67.662" width="12.714" height="12.714"/></g></svg> diff --git a/icons/size_large.svg b/icons/size_large.svg deleted file mode 100644 index 174a530..0000000 --- a/icons/size_large.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="0" fill="#cd5542" stroke="#000" stroke-width="2"/> - <g transform="translate(6.2763 8.2075)" fill="#fff" stroke="#282828" stroke-linecap="round" stroke-width="3"><rect x="18.296" y="16.365" width="12.714" height="12.714"/><rect x="31.01" y="16.365" width="12.714" height="12.714"/><rect x="43.724" y="16.365" width="12.714" height="12.714"/><rect x="56.437" y="16.365" width="12.714" height="12.714"/><rect x="18.296" y="29.079" width="12.714" height="12.714"/><rect x="31.01" y="29.079" width="12.714" height="12.714"/><rect x="43.724" y="29.079" width="12.714" height="12.714"/><rect x="56.437" y="29.079" width="12.714" height="12.714"/><rect x="18.296" y="41.792" width="12.714" height="12.714"/><rect x="31.01" y="41.792" width="12.714" height="12.714"/><rect x="43.724" y="41.792" width="12.714" height="12.714"/><rect x="56.437" y="41.792" width="12.714" height="12.714"/><rect x="18.296" y="54.506" width="12.714" height="12.714"/><rect x="31.01" y="54.506" width="12.714" height="12.714"/><rect x="43.724" y="54.506" width="12.714" height="12.714"/><rect x="56.437" y="54.506" width="12.714" height="12.714"/></g></svg> diff --git a/icons/size_medium.svg b/icons/size_medium.svg deleted file mode 100644 index bcc2fba..0000000 --- a/icons/size_medium.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="0" fill="#f29c38" stroke="#000" stroke-width="2"/> - <g transform="translate(11.967 8.1117)" fill="#fff" stroke="#282828" stroke-linecap="round" stroke-width="3"><rect x="18.962" y="22.818" width="12.714" height="12.714"/><rect x="31.676" y="22.818" width="12.714" height="12.714"/><rect x="44.389" y="22.818" width="12.714" height="12.714"/><rect x="18.962" y="35.531" width="12.714" height="12.714"/><rect x="31.676" y="35.531" width="12.714" height="12.714"/><rect x="44.389" y="35.531" width="12.714" height="12.714"/><rect x="18.962" y="48.245" width="12.714" height="12.714"/><rect x="31.676" y="48.245" width="12.714" height="12.714"/><rect x="44.389" y="48.245" width="12.714" height="12.714"/></g></svg> diff --git a/icons/size_small.svg b/icons/size_small.svg deleted file mode 100644 index 1ccb6e9..0000000 --- a/icons/size_small.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="0" fill="#97c05c" stroke="#000" stroke-width="2"/> - <g transform="translate(8.8715 12.603)" fill="#fff" stroke="#282828" stroke-linecap="round" stroke-width="3"><rect x="28.415" y="24.683" width="12.714" height="12.714"/><rect x="41.128" y="24.683" width="12.714" height="12.714"/><rect x="28.415" y="37.397" width="12.714" height="12.714"/><rect x="41.128" y="37.397" width="12.714" height="12.714"/></g></svg> diff --git a/icons/skin_default.svg b/icons/skin_default.svg deleted file mode 100644 index 18ae839..0000000 --- a/icons/skin_default.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#a4a4a4" stroke="#000" stroke-width="2"/><rect x="-15.387" y="-20.515" width="100" height="100" ry="2" fill="none"/><rect x="5.6673" y="36.339" width="29.323" height="29.323" fill="#f2739d" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="5.6673" y="67.01" width="29.323" height="29.323" fill="#38ffff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="36.339" y="67.01" width="29.323" height="29.323" fill="#a13cb1" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="67.01" width="29.323" height="29.323" fill="#ff6f43" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="36.339" width="29.323" height="29.323" fill="#ffce2c" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="67.01" y="5.6674" width="29.323" height="29.323" fill="#359c35" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="36.339" y="5.6674" width="29.323" height="29.323" fill="#708cfd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/><rect x="5.6673" y="5.6674" width="29.323" height="29.323" fill="#e63a3f" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width=".76239"/></svg> diff --git a/icons/skins/default/0.svg b/icons/skins/default/0.svg deleted file mode 100644 index 87a62b1..0000000 --- a/icons/skins/default/0.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="none"/><rect width="100" height="100" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width="0"/></svg> diff --git a/icons/skins/default/1.svg b/icons/skins/default/1.svg deleted file mode 100644 index 11201e5..0000000 --- a/icons/skins/default/1.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="none"/><rect width="100" height="100" fill="#E63A3F" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width="0"/></svg> diff --git a/icons/skins/default/2.svg b/icons/skins/default/2.svg deleted file mode 100644 index 76e07b7..0000000 --- a/icons/skins/default/2.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="none"/><rect width="100" height="100" fill="#708CFD" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width="0"/></svg> diff --git a/icons/skins/default/3.svg b/icons/skins/default/3.svg deleted file mode 100644 index 092dab6..0000000 --- a/icons/skins/default/3.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="none"/><rect width="100" height="100" fill="#359C35" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width="0"/></svg> diff --git a/icons/skins/default/4.svg b/icons/skins/default/4.svg deleted file mode 100644 index 57094f5..0000000 --- a/icons/skins/default/4.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="none"/><rect width="100" height="100" fill="#FFCE2C" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width="0"/></svg> diff --git a/icons/skins/default/5.svg b/icons/skins/default/5.svg deleted file mode 100644 index e0f4d96..0000000 --- a/icons/skins/default/5.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="none"/><rect width="100" height="100" fill="#FF6F43" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width="0"/></svg> diff --git a/icons/skins/default/6.svg b/icons/skins/default/6.svg deleted file mode 100644 index feb210e..0000000 --- a/icons/skins/default/6.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="none"/><rect width="100" height="100" fill="#A13CB1" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width="0"/></svg> diff --git a/icons/skins/default/7.svg b/icons/skins/default/7.svg deleted file mode 100644 index 2b9d085..0000000 --- a/icons/skins/default/7.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="none"/><rect width="100" height="100" fill="#38FFFF" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width="0"/></svg> diff --git a/icons/skins/default/8.svg b/icons/skins/default/8.svg deleted file mode 100644 index 40b8092..0000000 --- a/icons/skins/default/8.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="2" fill="none"/><rect width="100" height="100" fill="#F2739D" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".5" stroke-width="2.5e-7"/></svg> diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart new file mode 100644 index 0000000..db88412 --- /dev/null +++ b/lib/config/default_game_settings.dart @@ -0,0 +1,81 @@ +import 'package:colors/utils/tools.dart'; + +class DefaultGameSettings { + // available game parameters codes + static const String parameterCodeDifficultyLevel = 'difficultyLevel'; + static const String parameterCodeBoardSize = 'boardSize'; + static const String parameterCodeColorsCount = 'colorsCount'; + static const List<String> availableParameters = [ + parameterCodeDifficultyLevel, + parameterCodeBoardSize, + parameterCodeColorsCount, + ]; + + // difficulty level: available values + static const String difficultyLevelValueEasy = 'easy'; + static const String difficultyLevelValueMedium = 'medium'; + static const String difficultyLevelValueHard = 'hard'; + static const String difficultyLevelValueNightmare = 'nightmare'; + static const List<String> allowedDifficultyLevelValues = [ + difficultyLevelValueEasy, + difficultyLevelValueMedium, + difficultyLevelValueHard, + difficultyLevelValueNightmare, + ]; + // difficulty level: default value + static const String defaultDifficultyLevelValue = difficultyLevelValueMedium; + + // board size: available values + static const String boardSizeValueSmall = 'small'; + static const String boardSizeValueMedium = 'medium'; + static const String boardSizeValueLarge = 'large'; + static const String boardSizeValueExtra = 'extra'; + static const List<String> allowedBoardSizeValues = [ + boardSizeValueSmall, + boardSizeValueMedium, + boardSizeValueLarge, + boardSizeValueExtra, + ]; + // board size: default value + static const String defaultBoardSizeValue = boardSizeValueMedium; + + // colors count: available values + static const String colorsCountValueLow = '5'; + static const String colorsCountValueMedium = '6'; + static const String colorsCountValueHigh = '7'; + static const String colorsCountValueVeryHigh = '8'; + static const List<String> allowedColorsCountValues = [ + colorsCountValueLow, + colorsCountValueMedium, + colorsCountValueHigh, + colorsCountValueVeryHigh, + ]; + // colors count: default value + static const String defaultColorsCountValue = colorsCountValueMedium; + + // available values from parameter code + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeDifficultyLevel: + return DefaultGameSettings.allowedDifficultyLevelValues; + case parameterCodeBoardSize: + return DefaultGameSettings.allowedBoardSizeValues; + case parameterCodeColorsCount: + return DefaultGameSettings.allowedColorsCountValues; + } + + printlog('Did not find any available value for game parameter "$parameterCode".'); + return []; + } + + static int getMovesCountLimitDeltaFromLevelCode(String parameterLevel) { + const Map<String, int> values = { + difficultyLevelValueEasy: 5, + difficultyLevelValueMedium: 3, + difficultyLevelValueHard: 1, + difficultyLevelValueNightmare: -1, + }; + return values[parameterLevel] ?? + getMovesCountLimitDeltaFromLevelCode(DefaultGameSettings.defaultDifficultyLevelValue); + } +} diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart new file mode 100644 index 0000000..3ec1474 --- /dev/null +++ b/lib/config/default_global_settings.dart @@ -0,0 +1,28 @@ +import 'package:colors/utils/tools.dart'; + +class DefaultGlobalSettings { + // available global parameters codes + static const String parameterCodeSkin = 'skin'; + static const List<String> availableParameters = [ + parameterCodeSkin, + ]; + + // skin: available values + static const String skinValueColors = 'colors'; + static const List<String> allowedSkinValues = [ + skinValueColors, + ]; + // skin: default value + static const String defaultSkinValue = skinValueColors; + + // available values from parameter code + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeSkin: + return DefaultGlobalSettings.allowedSkinValues; + } + + printlog('Did not find any available value for global parameter "$parameterCode".'); + return []; + } +} diff --git a/lib/config/menu.dart b/lib/config/menu.dart index b969ec5..2f9395d 100644 --- a/lib/config/menu.dart +++ b/lib/config/menu.dart @@ -1,10 +1,9 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:unicons/unicons.dart'; -import 'package:colors/ui/screens/about_page.dart'; -import 'package:colors/ui/screens/game_page.dart'; -import 'package:colors/ui/screens/settings_page.dart'; +import 'package:colors/ui/screens/page_about.dart'; +import 'package:colors/ui/screens/page_game.dart'; +import 'package:colors/ui/screens/page_settings.dart'; class MenuItem { final String code; @@ -19,35 +18,39 @@ class MenuItem { } class Menu { - static List<MenuItem> items = [ - const MenuItem( - code: 'bottom_nav_home', - icon: Icon(UniconsLine.home), - page: GamePage(), - ), - const MenuItem( - code: 'bottom_nav_settings', - icon: Icon(UniconsLine.setting), - page: SettingsPage(), - ), - const MenuItem( - code: 'bottom_nav_about', - icon: Icon(UniconsLine.info_circle), - page: AboutPage(), - ), - ]; + static const indexGame = 0; + static const menuItemGame = MenuItem( + code: 'bottom_nav_game', + icon: Icon(UniconsLine.home), + page: PageGame(), + ); - static Widget getPageWidget(int pageIndex) { - return Menu.items.elementAt(pageIndex).page; + static const indexSettings = 1; + static const menuItemSettings = MenuItem( + code: 'bottom_nav_settings', + icon: Icon(UniconsLine.setting), + page: PageSettings(), + ); + + static const indexAbout = 2; + static const menuItemAbout = MenuItem( + code: 'bottom_nav_about', + icon: Icon(UniconsLine.info_circle), + page: PageAbout(), + ); + + static Map<int, MenuItem> items = { + indexGame: menuItemGame, + indexSettings: menuItemSettings, + indexAbout: menuItemAbout, + }; + + static bool isIndexAllowed(int pageIndex) { + return items.keys.contains(pageIndex); } - static List<BottomNavigationBarItem> getMenuItems() { - return Menu.items - .map((MenuItem item) => BottomNavigationBarItem( - icon: item.icon, - label: tr(item.code), - )) - .toList(); + static Widget getPageWidget(int pageIndex) { + return items[pageIndex]?.page ?? menuItemGame.page; } static int itemsCount = Menu.items.length; diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart new file mode 100644 index 0000000..16775c3 --- /dev/null +++ b/lib/cubit/game_cubit.dart @@ -0,0 +1,170 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:colors/models/game.dart'; +import 'package:colors/models/settings_game.dart'; +import 'package:colors/models/settings_global.dart'; +import 'package:colors/utils/tools.dart'; + +part 'game_state.dart'; + +class GameCubit extends HydratedCubit<GameState> { + GameCubit() + : super(GameState( + currentGame: Game.createNull(), + )); + + void updateState(Game game) { + emit(GameState( + currentGame: game, + )); + } + + void refresh() { + final Game game = Game( + board: state.currentGame.board, + gameSettings: state.currentGame.gameSettings, + globalSettings: state.currentGame.globalSettings, + isRunning: state.currentGame.isRunning, + isFinished: state.currentGame.isFinished, + gameWon: state.currentGame.gameWon, + movesCount: state.currentGame.movesCount, + maxMovesCount: state.currentGame.maxMovesCount, + animationInProgress: state.currentGame.animationInProgress, + progress: state.currentGame.progress, + progressTotal: state.currentGame.progressTotal, + progressDelta: state.currentGame.progressDelta, + ); + + updateState(game); + } + + void startNewGame({ + required GameSettings gameSettings, + required GlobalSettings globalSettings, + }) { + printlog('Starting new game:'); + printlog('- level: ${gameSettings.difficultyLevel}'); + printlog('- size: ${gameSettings.parameterSize}'); + printlog('- colors: ${gameSettings.parameterColorsCount}'); + + Game newGame = Game.createNew( + gameSettings: gameSettings, + globalSettings: globalSettings, + ); + + newGame.dump(); + + updateState(newGame); + updateGameIsRunning(true); + } + + void quitGame() { + state.currentGame.isRunning = false; + refresh(); + } + + void updateCellValue(int col, int row, int value) { + state.currentGame.board.cells[row][col].value = value; + refresh(); + } + + void increaseMovesCount() { + state.currentGame.movesCount++; + refresh(); + } + + void updateProgressDelta(int progressDelta) { + state.currentGame.progressDelta = progressDelta; + refresh(); + } + + void updateProgress(int progress) { + state.currentGame.progress = progress; + refresh(); + } + + void updateAnimationInProgress(bool inProgress) { + state.currentGame.animationInProgress = inProgress; + refresh(); + } + + void updateGameIsRunning(bool gameIsRunning) { + state.currentGame.isRunning = gameIsRunning; + refresh(); + } + + void updateGameIsFinished(bool gameIsFinished) { + state.currentGame.isFinished = gameIsFinished; + refresh(); + } + + void updateGameWon(bool gameWon) { + state.currentGame.gameWon = gameWon; + refresh(); + } + + fillBoardFromFirstCell(int value) { + List<List<int>> cellsToFill = state.currentGame.board.getSiblingFillableCells(0, 0, []); + final int progressBeforeMove = cellsToFill.length; + + if (value == state.currentGame.board.getFirstCellValue()) { + return; + } + + increaseMovesCount(); + + // Sort cells from the closest to the furthest, relatively to the top left corner + cellsToFill + .sort((a, b) => (pow(a[0], 2) + pow(a[1], 2)).compareTo(pow(b[0], 2) + pow(b[1], 2))); + + const interval = Duration(milliseconds: 10); + int cellIndex = 0; + updateAnimationInProgress(true); + Timer.periodic( + interval, + (Timer timer) { + if (cellIndex < cellsToFill.length) { + updateCellValue(cellsToFill[cellIndex][1], cellsToFill[cellIndex][0], value); + cellIndex++; + } else { + timer.cancel(); + + int progressAfterMove = + state.currentGame.board.getSiblingFillableCells(0, 0, []).length; + int progressDelta = progressAfterMove - progressBeforeMove; + updateProgressDelta(progressDelta); + updateProgress(progressAfterMove); + + updateAnimationInProgress(false); + + if (state.currentGame.board.isBoardSolved()) { + updateGameWon(true); + updateGameIsFinished(true); + } + } + }, + ); + } + + @override + GameState? fromJson(Map<String, dynamic> json) { + Game currentGame = json['currentGame'] as Game; + + return GameState( + currentGame: currentGame, + ); + } + + @override + Map<String, dynamic>? toJson(GameState state) { + return <String, dynamic>{ + 'currentGame': state.currentGame.toJson(), + }; + } +} diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart new file mode 100644 index 0000000..3fd161a --- /dev/null +++ b/lib/cubit/game_state.dart @@ -0,0 +1,19 @@ +part of 'game_cubit.dart'; + +@immutable +class GameState extends Equatable { + const GameState({ + required this.currentGame, + }); + + final Game currentGame; + + @override + List<dynamic> get props => <dynamic>[ + currentGame, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'currentGame': currentGame, + }; +} diff --git a/lib/cubit/bottom_nav_cubit.dart b/lib/cubit/nav_cubit.dart similarity index 51% rename from lib/cubit/bottom_nav_cubit.dart rename to lib/cubit/nav_cubit.dart index f33dca3..a92b230 100644 --- a/lib/cubit/bottom_nav_cubit.dart +++ b/lib/cubit/nav_cubit.dart @@ -2,26 +2,32 @@ import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:colors/config/menu.dart'; -class BottomNavCubit extends HydratedCubit<int> { - BottomNavCubit() : super(0); +class NavCubit extends HydratedCubit<int> { + NavCubit() : super(0); void updateIndex(int index) { - if (isIndexAllowed(index)) { + if (Menu.isIndexAllowed(index)) { emit(index); } else { - goToHomePage(); + goToGamePage(); } } - bool isIndexAllowed(int index) { - return (index >= 0) && (index < Menu.itemsCount); + void goToGamePage() { + emit(Menu.indexGame); } - void goToHomePage() => emit(0); + void goToSettingsPage() { + emit(Menu.indexSettings); + } + + void goToAboutPage() { + emit(Menu.indexAbout); + } @override int fromJson(Map<String, dynamic> json) { - return 0; + return Menu.indexGame; } @override diff --git a/lib/cubit/settings_game_cubit.dart b/lib/cubit/settings_game_cubit.dart new file mode 100644 index 0000000..f70f2be --- /dev/null +++ b/lib/cubit/settings_game_cubit.dart @@ -0,0 +1,84 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:colors/config/default_game_settings.dart'; +import 'package:colors/models/settings_game.dart'; + +part 'settings_game_state.dart'; + +class GameSettingsCubit extends HydratedCubit<GameSettingsState> { + GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault())); + + void setValues({ + String? difficultyLevel, + String? boardSize, + String? colorsCount, + }) { + emit( + GameSettingsState( + settings: GameSettings( + difficultyLevel: difficultyLevel ?? state.settings.difficultyLevel, + parameterSize: boardSize ?? state.settings.parameterSize, + parameterColorsCount: colorsCount ?? state.settings.parameterColorsCount, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGameSettings.parameterCodeDifficultyLevel: + return GameSettings.getLevelValueFromUnsafe(state.settings.difficultyLevel); + case DefaultGameSettings.parameterCodeBoardSize: + return GameSettings.getSizeValueFromUnsafe(state.settings.parameterSize); + case DefaultGameSettings.parameterCodeColorsCount: + return GameSettings.getColorsValueFromUnsafe(state.settings.parameterColorsCount); + } + return ''; + } + + void setParameterValue(String code, String value) { + final String difficultyLevel = (code == DefaultGameSettings.parameterCodeDifficultyLevel) + ? value + : getParameterValue(DefaultGameSettings.parameterCodeDifficultyLevel); + final String boardSize = (code == DefaultGameSettings.parameterCodeBoardSize) + ? value + : getParameterValue(DefaultGameSettings.parameterCodeBoardSize); + final String colorsCount = (code == DefaultGameSettings.parameterCodeColorsCount) + ? value + : getParameterValue(DefaultGameSettings.parameterCodeColorsCount); + + setValues( + difficultyLevel: difficultyLevel, + boardSize: boardSize, + colorsCount: colorsCount, + ); + } + + @override + GameSettingsState? fromJson(Map<String, dynamic> json) { + final String difficultyLevel = + json[DefaultGameSettings.parameterCodeDifficultyLevel] as String; + final String boardSize = json[DefaultGameSettings.parameterCodeBoardSize] as String; + final String colorsCount = json[DefaultGameSettings.parameterCodeColorsCount] as String; + + return GameSettingsState( + settings: GameSettings( + difficultyLevel: difficultyLevel, + parameterSize: boardSize, + parameterColorsCount: colorsCount, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GameSettingsState state) { + return <String, dynamic>{ + DefaultGameSettings.parameterCodeDifficultyLevel: state.settings.difficultyLevel, + DefaultGameSettings.parameterCodeBoardSize: state.settings.parameterSize, + DefaultGameSettings.parameterCodeColorsCount: state.settings.parameterColorsCount, + }; + } +} diff --git a/lib/cubit/settings_game_state.dart b/lib/cubit/settings_game_state.dart new file mode 100644 index 0000000..b773dc6 --- /dev/null +++ b/lib/cubit/settings_game_state.dart @@ -0,0 +1,19 @@ +part of 'settings_game_cubit.dart'; + +@immutable +class GameSettingsState extends Equatable { + const GameSettingsState({ + required this.settings, + }); + + final GameSettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'settings': settings, + }; +} diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart new file mode 100644 index 0000000..41d3f19 --- /dev/null +++ b/lib/cubit/settings_global_cubit.dart @@ -0,0 +1,61 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:colors/config/default_global_settings.dart'; +import 'package:colors/models/settings_global.dart'; + +part 'settings_global_state.dart'; + +class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { + GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault())); + + void setValues({ + String? skin, + }) { + emit( + GlobalSettingsState( + settings: GlobalSettings( + skin: skin ?? state.settings.skin, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGlobalSettings.parameterCodeSkin: + return GlobalSettings.getSkinValueFromUnsafe(state.settings.skin); + } + return ''; + } + + void setParameterValue(String code, String value) { + final String skin = (code == DefaultGlobalSettings.parameterCodeSkin) + ? value + : getParameterValue(DefaultGlobalSettings.parameterCodeSkin); + + setValues( + skin: skin, + ); + } + + @override + GlobalSettingsState? fromJson(Map<String, dynamic> json) { + final String skin = json[DefaultGlobalSettings.parameterCodeSkin] as String; + + return GlobalSettingsState( + settings: GlobalSettings( + skin: skin, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GlobalSettingsState state) { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: state.settings.skin, + }; + } +} diff --git a/lib/cubit/settings_global_state.dart b/lib/cubit/settings_global_state.dart new file mode 100644 index 0000000..4e4fbdf --- /dev/null +++ b/lib/cubit/settings_global_state.dart @@ -0,0 +1,19 @@ +part of 'settings_global_cubit.dart'; + +@immutable +class GlobalSettingsState extends Equatable { + const GlobalSettingsState({ + required this.settings, + }); + + final GlobalSettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'settings': settings, + }; +} diff --git a/lib/entities/cell.dart b/lib/entities/cell.dart deleted file mode 100644 index 9617e4c..0000000 --- a/lib/entities/cell.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:colors/provider/data.dart'; -import 'package:colors/utils/board_utils.dart'; - -class Cell { - int value; - - Cell( - this.value, - ); - - Container interactiveWidget(Data myProvider, ColorScheme colorScheme) { - final String imageAsset = 'assets/skins/${myProvider.parameterSkin}_$value.png'; - - return Container( - margin: const EdgeInsets.all(2), - decoration: BoxDecoration( - border: Border.all( - color: colorScheme.onSurface, - width: 4, - ), - ), - child: GestureDetector( - child: Image( - image: AssetImage(imageAsset), - fit: BoxFit.fill, - ), - onTap: () { - if (!myProvider.animationInProgress && myProvider.getFirstCellValue() != value) { - BoardUtils.fillBoardFromFirstCell(myProvider, value); - } - }, - ), - ); - } -} diff --git a/lib/main.dart b/lib/main.dart index a74262f..4ffcc22 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,22 +1,22 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; -import 'package:overlay_support/overlay_support.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:provider/provider.dart'; import 'package:colors/config/theme.dart'; -import 'package:colors/cubit/bottom_nav_cubit.dart'; +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/cubit/nav_cubit.dart'; +import 'package:colors/cubit/settings_game_cubit.dart'; +import 'package:colors/cubit/settings_global_cubit.dart'; import 'package:colors/cubit/theme_cubit.dart'; -import 'package:colors/provider/data.dart'; import 'package:colors/ui/skeleton.dart'; void main() async { - /// Initialize packages + // Initialize packages WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); final Directory tmpDir = await getTemporaryDirectory(); @@ -46,34 +46,28 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), + BlocProvider<NavCubit>(create: (context) => NavCubit()), BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()), + BlocProvider<GameCubit>(create: (context) => GameCubit()), + BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()), + BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()), ], child: BlocBuilder<ThemeCubit, ThemeModeState>( builder: (BuildContext context, ThemeModeState state) { - return ChangeNotifierProvider( - create: (BuildContext context) => Data(), - child: Consumer<Data>( - builder: (context, data, child) { - return OverlaySupport( - child: MaterialApp( - title: 'Minehunter', - home: const SkeletonScreen(), + return MaterialApp( + title: 'Colors', + home: const SkeletonScreen(), - // Theme stuff - theme: lightTheme, - darkTheme: darkTheme, - themeMode: state.themeMode, + // Theme stuff + theme: lightTheme, + darkTheme: darkTheme, + themeMode: state.themeMode, - // Localization stuff - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - debugShowCheckedModeBanner: false, - ), - ); - }, - ), + // Localization stuff + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + debugShowCheckedModeBanner: false, ); }, ), diff --git a/lib/models/board.dart b/lib/models/board.dart new file mode 100644 index 0000000..5d6ccd9 --- /dev/null +++ b/lib/models/board.dart @@ -0,0 +1,144 @@ +import 'dart:math'; + +import 'package:colors/models/cell.dart'; +import 'package:colors/models/settings_game.dart'; +import 'package:colors/models/types.dart'; +import 'package:colors/utils/tools.dart'; + +class Board { + final BoardCells cells; + + Board({ + required this.cells, + }); + + factory Board.createNull() { + return Board(cells: []); + } + + factory Board.createRandom(GameSettings gameSettings) { + final int boardSizeHorizontal = gameSettings.boardSize; + final int boardSizeVertical = gameSettings.boardSize; + + final rand = Random(); + + BoardCells cells = []; + for (int rowIndex = 0; rowIndex < boardSizeVertical; rowIndex++) { + List<Cell> row = []; + for (int colIndex = 0; colIndex < boardSizeHorizontal; colIndex++) { + final int value = 1 + rand.nextInt(gameSettings.colorsCount); + row.add(Cell(value)); + } + cells.add(row); + } + + return Board( + cells: cells, + ); + } + + int getCellValue(int col, int row) { + return cells[row][col].value; + } + + int getFirstCellValue() { + return cells[0][0].value; + } + + List<List<int>> getSiblingFillableCells( + int row, + int col, + List<List<int>> siblingCells, + ) { + final int boardSize = cells.length; + + if (siblingCells.isEmpty) { + siblingCells = [ + [row, col] + ]; + } + + final int referenceValue = cells[row][col].value; + + for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { + for (int deltaCol = -1; deltaCol <= 1; deltaCol++) { + if (deltaCol == 0 || deltaRow == 0) { + final int candidateRow = row + deltaRow; + final int candidateCol = col + deltaCol; + + if ((candidateRow >= 0 && candidateRow < boardSize) && + (candidateCol >= 0 && candidateCol < boardSize)) { + if (cells[candidateRow][candidateCol].value == referenceValue) { + bool alreadyFound = false; + for (int index = 0; index < siblingCells.length; index++) { + if ((siblingCells[index][0] == candidateRow) && + (siblingCells[index][1] == candidateCol)) { + alreadyFound = true; + } + } + if (!alreadyFound) { + siblingCells.add([candidateRow, candidateCol]); + siblingCells = getSiblingFillableCells( + candidateRow, + candidateCol, + siblingCells, + ); + } + } + } + } + } + } + + return siblingCells; + } + + // check grid contains only one color + bool isBoardSolved() { + final int firstCellValue = cells[0][0].value; + for (int row = 0; row < cells.length; row++) { + for (int col = 0; col < cells[row].length; col++) { + if (cells[row][col].value != firstCellValue) { + return false; + } + } + } + + printlog('-> ok grid fully painted!'); + + return true; + } + + void dump() { + String horizontalRule = '----'; + for (int i = 0; i < cells[0].length; i++) { + horizontalRule += '-'; + } + + printlog('Board:'); + printlog(horizontalRule); + + for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { + String row = '| '; + for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { + row += cells[rowIndex][colIndex].value.toString(); + } + row += ' |'; + + printlog(row); + } + + printlog(horizontalRule); + } + + @override + String toString() { + return '$Board(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'cells': cells.toString(), + }; + } +} diff --git a/lib/models/cell.dart b/lib/models/cell.dart new file mode 100644 index 0000000..54d6e3a --- /dev/null +++ b/lib/models/cell.dart @@ -0,0 +1,18 @@ +class Cell { + Cell( + this.value, + ); + + int value; + + @override + String toString() { + return '$Cell(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'value': value, + }; + } +} diff --git a/lib/models/game.dart b/lib/models/game.dart new file mode 100644 index 0000000..28b61ea --- /dev/null +++ b/lib/models/game.dart @@ -0,0 +1,114 @@ +import 'package:colors/config/default_game_settings.dart'; +import 'package:colors/models/board.dart'; +import 'package:colors/models/settings_game.dart'; +import 'package:colors/models/settings_global.dart'; +import 'package:colors/utils/tools.dart'; + +class Game { + final Board board; + final GameSettings gameSettings; + final GlobalSettings globalSettings; + bool isRunning = false; + bool isFinished = false; + bool gameWon = false; + + int maxMovesCount = 0; + int movesCount = 0; + + int progress = 0; + int progressTotal = 0; + int progressDelta = 0; + + bool animationInProgress = false; + + Game({ + required this.board, + required this.gameSettings, + required this.globalSettings, + this.isRunning = false, + this.isFinished = false, + this.gameWon = false, + this.maxMovesCount = 0, + this.movesCount = 0, + this.progress = 0, + this.progressTotal = 0, + this.progressDelta = 0, + this.animationInProgress = false, + }); + + factory Game.createNull() { + return Game( + board: Board.createNull(), + gameSettings: GameSettings.createDefault(), + globalSettings: GlobalSettings.createDefault(), + ); + } + + factory Game.createNew({ + GameSettings? gameSettings, + GlobalSettings? globalSettings, + }) { + GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault(); + GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); + + final int baseMaxMovesCount = + (30 * (newGameSettings.boardSize * newGameSettings.colorsCount) / (17 * 6)).round(); + final int deltaMovesCountFromLevel = + DefaultGameSettings.getMovesCountLimitDeltaFromLevelCode( + newGameSettings.difficultyLevel); + + return Game( + board: Board.createRandom(newGameSettings), + gameSettings: newGameSettings, + globalSettings: newGlobalSettings, + isRunning: true, + isFinished: false, + gameWon: false, + progress: 1, + progressTotal: newGameSettings.boardSize * newGameSettings.boardSize, + maxMovesCount: baseMaxMovesCount + deltaMovesCountFromLevel, + ); + } + + void dump() { + printlog(''); + printlog('## Current game dump:'); + printlog(''); + gameSettings.dump(); + globalSettings.dump(); + printlog(''); + board.dump(); + printlog(''); + printlog('Game: '); + printlog(' isRunning: $isRunning'); + printlog(' isFinished: $isFinished'); + printlog(' gameWon: $gameWon'); + printlog(' movesCount: $movesCount'); + printlog(' maxMovesCount: $maxMovesCount'); + printlog(' progress: $progress'); + printlog(' progressTotal: $progressTotal'); + printlog(' progressDelta: $progressDelta'); + printlog(''); + } + + @override + String toString() { + return '$Game(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'board': board.toJson(), + 'gameSettings': gameSettings.toJson(), + 'globalSettings': globalSettings.toJson(), + 'isRunning': isRunning, + 'isFinished': isFinished, + 'gameWon': gameWon, + 'movesCount': movesCount, + 'maxMovesCount': maxMovesCount, + 'progress': progress, + 'progressTotal': progressTotal, + 'progressDelta': progressDelta, + }; + } +} diff --git a/lib/models/settings_game.dart b/lib/models/settings_game.dart new file mode 100644 index 0000000..b5227e8 --- /dev/null +++ b/lib/models/settings_game.dart @@ -0,0 +1,81 @@ +import 'package:colors/config/default_game_settings.dart'; +import 'package:colors/utils/tools.dart'; + +class GameSettings { + String difficultyLevel; + String parameterSize; + String parameterColorsCount; + + GameSettings({ + required this.difficultyLevel, + required this.parameterSize, + required this.parameterColorsCount, + }); + + static String getLevelValueFromUnsafe(String level) { + if (DefaultGameSettings.allowedDifficultyLevelValues.contains(level)) { + return level; + } + + return DefaultGameSettings.defaultDifficultyLevelValue; + } + + static String getSizeValueFromUnsafe(String size) { + if (DefaultGameSettings.allowedBoardSizeValues.contains(size)) { + return size; + } + + return DefaultGameSettings.defaultBoardSizeValue; + } + + static String getColorsValueFromUnsafe(String colors) { + if (DefaultGameSettings.allowedColorsCountValues.contains(colors)) { + return colors; + } + + return DefaultGameSettings.defaultColorsCountValue; + } + + factory GameSettings.createDefault() { + return GameSettings( + difficultyLevel: DefaultGameSettings.defaultDifficultyLevelValue, + parameterSize: DefaultGameSettings.defaultBoardSizeValue, + parameterColorsCount: DefaultGameSettings.defaultColorsCountValue, + ); + } + + int getBoardSizeFromParameter(String parameterSize) { + const Map<String, int> values = { + DefaultGameSettings.boardSizeValueSmall: 6, + DefaultGameSettings.boardSizeValueMedium: 10, + DefaultGameSettings.boardSizeValueLarge: 14, + DefaultGameSettings.boardSizeValueExtra: 20, + }; + return values[parameterSize] ?? + getBoardSizeFromParameter(DefaultGameSettings.defaultBoardSizeValue); + } + + int get boardSize => getBoardSizeFromParameter(parameterSize); + int get colorsCount => int.parse(parameterColorsCount); + + void dump() { + printlog('$GameSettings:'); + printlog(' ${DefaultGameSettings.parameterCodeDifficultyLevel}: $difficultyLevel'); + printlog(' ${DefaultGameSettings.parameterCodeBoardSize}: $parameterSize'); + printlog(' ${DefaultGameSettings.parameterCodeColorsCount}: $parameterColorsCount'); + printlog(''); + } + + @override + String toString() { + return '$GameSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGameSettings.parameterCodeDifficultyLevel: difficultyLevel, + DefaultGameSettings.parameterCodeBoardSize: parameterSize, + DefaultGameSettings.parameterCodeColorsCount: parameterColorsCount, + }; + } +} diff --git a/lib/models/settings_global.dart b/lib/models/settings_global.dart new file mode 100644 index 0000000..09994f4 --- /dev/null +++ b/lib/models/settings_global.dart @@ -0,0 +1,41 @@ +import 'package:colors/config/default_global_settings.dart'; +import 'package:colors/utils/tools.dart'; + +class GlobalSettings { + String skin; + + GlobalSettings({ + required this.skin, + }); + + static String getSkinValueFromUnsafe(String skin) { + if (DefaultGlobalSettings.allowedSkinValues.contains(skin)) { + return skin; + } + + return DefaultGlobalSettings.defaultSkinValue; + } + + factory GlobalSettings.createDefault() { + return GlobalSettings( + skin: DefaultGlobalSettings.defaultSkinValue, + ); + } + + void dump() { + printlog('$GlobalSettings: '); + printlog(' ${DefaultGlobalSettings.parameterCodeSkin}: $skin'); + printlog(''); + } + + @override + String toString() { + return '$GlobalSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: skin, + }; + } +} diff --git a/lib/models/types.dart b/lib/models/types.dart new file mode 100644 index 0000000..339bfe6 --- /dev/null +++ b/lib/models/types.dart @@ -0,0 +1,3 @@ +import 'package:colors/models/cell.dart'; + +typedef BoardCells = List<List<Cell>>; diff --git a/lib/provider/data.dart b/lib/provider/data.dart deleted file mode 100644 index dfe3136..0000000 --- a/lib/provider/data.dart +++ /dev/null @@ -1,340 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import 'package:colors/entities/cell.dart'; - -typedef Board = List<List<Cell>>; - -class Data extends ChangeNotifier { - // Configuration available parameters - final List<String> _availableParameters = ['level', 'size', 'colors', 'skin']; - List<String> get availableParameters => _availableParameters; - - // Configuration available values - final List<String> _availableLevelValues = ['easy', 'medium', 'hard', 'nightmare']; - final List<String> _availableSizeValues = ['small', 'medium', 'large', 'extra']; - final List<String> _availableColorsValues = ['5', '6', '7', '8']; - final List<String> _availableSkinValues = ['default']; - - List<String> get availableLevelValues => _availableLevelValues; - List<String> get availableSizeValues => _availableSizeValues; - List<String> get availableColorsValues => _availableColorsValues; - List<String> get availableSkinValues => _availableSkinValues; - - // Application default configuration - String _parameterLevel = ''; - final String _parameterLevelDefault = 'medium'; - String _parameterSize = ''; - final String _parameterSizeDefault = 'medium'; - String _parameterColors = ''; - final String _parameterColorsDefault = '6'; - String _parameterSkin = ''; - final String _parameterSkinDefault = 'default'; - - // Application current configuration - String get parameterLevel => _parameterLevel; - String get parameterSize => _parameterSize; - String get parameterColors => _parameterColors; - String get parameterSkin => _parameterSkin; - - // Game data - bool _gameIsRunning = false; - bool _animationInProgress = false; - bool _gameWon = false; - int _boardSize = 0; - int _colorsCount = 0; - int _movesCount = 0; - int _maxMovesCount = 0; - Board _board = []; - - int _progress = 0; - int _progressTotal = 0; - int _progressDelta = 0; - - bool isParameterValueAllowed(String parameterValue, List<String> parametersAllowedValues) { - bool found = false; - for (String item in parametersAllowedValues) { - if (item.contains(parameterValue)) { - found = true; - } - } - return found; - } - - void updateParameterLevel(String parameterLevel) { - if (isParameterValueAllowed(parameterLevel, _availableLevelValues)) { - _parameterLevel = parameterLevel; - } else { - _parameterLevel = _parameterLevelDefault; - } - notifyListeners(); - } - - void updateParameterSize(String parameterSize) { - if (isParameterValueAllowed(parameterSize, _availableSizeValues)) { - _parameterSize = parameterSize; - } else { - _parameterSize = _parameterSizeDefault; - } - updateBoardSize(getBoardSizeFromParameter(parameterSize)); - notifyListeners(); - } - - void updateParameterColors(String parameterColors) { - if (isParameterValueAllowed(parameterColors, _availableColorsValues)) { - _parameterColors = parameterColors; - } else { - _parameterColors = _parameterColorsDefault; - } - updateColorsCount(getColorsCountFromParameter(parameterColors)); - notifyListeners(); - } - - void updateParameterSkin(String parameterSkin) { - if (isParameterValueAllowed(parameterSkin, _availableSkinValues)) { - _parameterSkin = parameterSkin; - } else { - _parameterSkin = _parameterSkinDefault; - } - notifyListeners(); - } - - String getParameterValue(String parameterCode) { - switch (parameterCode) { - case 'level': - { - return _parameterLevel; - } - case 'size': - { - return _parameterSize; - } - case 'colors': - { - return _parameterColors; - } - case 'skin': - { - return _parameterSkin; - } - } - return ''; - } - - List<String> getParameterAvailableValues(String parameterCode) { - switch (parameterCode) { - case 'level': - { - return _availableLevelValues; - } - case 'size': - { - return _availableSizeValues; - } - case 'colors': - { - return _availableColorsValues; - } - case 'skin': - { - return _availableSkinValues; - } - } - return []; - } - - void setParameterValue(String parameterCode, String parameterValue) async { - switch (parameterCode) { - case 'level': - { - updateParameterLevel(parameterValue); - } - break; - case 'size': - { - updateParameterSize(parameterValue); - } - break; - case 'colors': - { - updateParameterColors(parameterValue); - } - break; - case 'skin': - { - updateParameterSkin(parameterValue); - } - break; - } - final prefs = await SharedPreferences.getInstance(); - prefs.setString(parameterCode, parameterValue); - } - - void initParametersValues() async { - final prefs = await SharedPreferences.getInstance(); - setParameterValue('level', prefs.getString('level') ?? _parameterLevelDefault); - setParameterValue('size', prefs.getString('size') ?? _parameterSizeDefault); - setParameterValue('colors', prefs.getString('colors') ?? _parameterColorsDefault); - setParameterValue('skin', prefs.getString('skin') ?? _parameterSkinDefault); - } - - int getBoardSizeFromParameter(String parameterSize) { - switch (parameterSize) { - case 'small': - { - return 6; - } - case 'medium': - { - return 10; - } - case 'large': - { - return 14; - } - case 'extra': - { - return 20; - } - } - return getBoardSizeFromParameter(_parameterSizeDefault); - } - - int getColorsCountFromParameter(String parameterColors) { - switch (parameterColors) { - case '5': - { - return 5; - } - case '6': - { - return 6; - } - case '7': - { - return 7; - } - case '8': - { - return 8; - } - } - return getColorsCountFromParameter(_parameterColorsDefault); - } - - int getMovesCountLimitDeltaFromLevelParameter(String parameterLevel) { - switch (parameterLevel) { - case 'easy': - { - return 5; - } - case 'medium': - { - return 3; - } - case 'hard': - { - return 1; - } - case 'nightmare': - { - return -1; - } - } - return getMovesCountLimitDeltaFromLevelParameter(_parameterLevelDefault); - } - - int get boardSize => _boardSize; - void updateBoardSize(int boardSize) { - _boardSize = boardSize; - _progressTotal = boardSize * boardSize; - } - - int get colorsCount => _colorsCount; - void updateColorsCount(int colorsCount) { - _colorsCount = colorsCount; - } - - Board get board => _board; - void updateBoard(Board board) { - _board = board; - notifyListeners(); - } - - updateCellValue(int col, int row, int value) { - _board[row][col].value = value; - notifyListeners(); - } - - int getFirstCellValue() { - return _board[0][0].value; - } - - int getCellValue(int col, int row) { - return _board[row][col].value; - } - - int get movesCount => _movesCount; - void updateMovesCount(int movesCount) { - _movesCount = movesCount; - notifyListeners(); - } - - void incrementMovesCount() { - updateMovesCount(movesCount + 1); - } - - int get maxMovesCount => _maxMovesCount; - void updateMaxMovesCount(int maxMovesCount) { - _maxMovesCount = maxMovesCount; - } - - int get progress => _progress; - int get progressTotal => _progressTotal; - int get progressDelta => _progressDelta; - void updateProgress(int progress) { - _progress = progress; - notifyListeners(); - } - - void updateProgressTotal(int progressTotal) { - _progressTotal = progressTotal; - notifyListeners(); - } - - void updateProgressDelta(int progressDelta) { - _progressDelta = progressDelta; - notifyListeners(); - } - - bool get gameIsRunning => _gameIsRunning; - void updateGameIsRunning(bool gameIsRunning) { - _gameIsRunning = gameIsRunning; - notifyListeners(); - } - - bool isGameFinished() { - return _gameWon; - } - - bool get gameWon => _gameWon; - void updateGameWon(bool gameWon) { - _gameWon = gameWon; - notifyListeners(); - } - - bool get animationInProgress => _animationInProgress; - void updateAnimationInProgress(bool animationInProgress) { - _animationInProgress = animationInProgress; - notifyListeners(); - } - - void resetGame() { - _gameIsRunning = false; - _gameWon = false; - _movesCount = 0; - _maxMovesCount = 0; - _progress = 0; - notifyListeners(); - } -} diff --git a/lib/ui/layout/game.dart b/lib/ui/layout/game.dart deleted file mode 100644 index a91ceaf..0000000 --- a/lib/ui/layout/game.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:colors/ui/layout/tileset.dart'; -import 'package:colors/provider/data.dart'; -import 'package:colors/ui/widgets/game/indicator_top.dart'; -import 'package:colors/ui/widgets/game/message_game_end.dart'; -import 'package:colors/ui/widgets/game/select_color_bar.dart'; - -class Game extends StatelessWidget { - const Game({ - super.key, - required this.myProvider, - required this.boardWidth, - }); - - final Data myProvider; - final double boardWidth; - - @override - Widget build(BuildContext context) { - final bool gameIsFinished = myProvider.isGameFinished(); - - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 8), - TopIndicator(myProvider: myProvider), - const SizedBox(height: 2), - Expanded( - child: Tileset(myProvider: myProvider, boardWidth: boardWidth), - ), - const SizedBox(height: 2), - Container( - child: gameIsFinished - ? EndGameMessage(myProvider: myProvider) - : SelectColorBar(myProvider: myProvider), - ), - ], - ); - } -} diff --git a/lib/ui/layout/parameters.dart b/lib/ui/layout/parameters.dart deleted file mode 100644 index 34a9a42..0000000 --- a/lib/ui/layout/parameters.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:colors/ui/widgets/home/button_game_start_new.dart'; -import 'package:colors/provider/data.dart'; - -class Parameters extends StatelessWidget { - const Parameters({super.key, required this.myProvider}); - - final Data myProvider; - - static const double separatorHeight = 2.0; - static const double blockMargin = 3.0; - static const double blockPadding = 2.0; - static const Color buttonBackgroundColor = Colors.white; - static const Color buttonBorderColorActive = Colors.blue; - static const Color buttonBorderColorInactive = Colors.white; - static const double buttonBorderWidth = 10.0; - static const double buttonBorderRadius = 8.0; - static const double buttonPadding = 0.0; - static const double buttonMargin = 0.0; - - @override - Widget build(BuildContext context) { - List<Widget> lines = []; - - final List<String> parameters = myProvider.availableParameters; - for (int index = 0; index < parameters.length; index++) { - lines.add(buildParameterSelector(parameters[index])); - lines.add(const SizedBox(height: separatorHeight)); - } - - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: separatorHeight), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: lines, - ), - ), - const SizedBox(height: separatorHeight), - StartNewGameButton(myProvider: myProvider), - ], - ); - } - - static Image buildImageWidget(String imageAssetCode) { - return Image( - image: AssetImage('assets/icons/$imageAssetCode.png'), - fit: BoxFit.fill, - ); - } - - static Container buildImageContainerWidget(String imageAssetCode) { - return Container( - child: buildImageWidget(imageAssetCode), - ); - } - - static Column buildDecorationImageWidget() { - return Column( - children: [ - TextButton( - child: buildImageContainerWidget('game_win'), - onPressed: () {}, - ), - ], - ); - } - - Widget buildParameterSelector(String parameterCode) { - final List<String> availableValues = myProvider.getParameterAvailableValues(parameterCode); - - if (availableValues.length == 1) { - return const SizedBox(height: 0.0); - } - - return Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - for (int index = 0; index < availableValues.length; index++) - Column( - children: [buildParameterButton(parameterCode, availableValues[index])], - ), - ], - ), - ], - ); - } - - Widget buildParameterButton(String parameterCode, String parameterValue) { - final String currentValue = myProvider.getParameterValue(parameterCode).toString(); - - final bool isActive = (parameterValue == currentValue); - final String imageAsset = '${parameterCode}_$parameterValue'; - - return TextButton( - child: Container( - margin: const EdgeInsets.all(buttonMargin), - padding: const EdgeInsets.all(buttonPadding), - decoration: BoxDecoration( - color: buttonBackgroundColor, - borderRadius: BorderRadius.circular(buttonBorderRadius), - border: Border.all( - color: isActive ? buttonBorderColorActive : buttonBorderColorInactive, - width: buttonBorderWidth, - ), - ), - child: buildImageWidget(imageAsset), - ), - onPressed: () => myProvider.setParameterValue(parameterCode, parameterValue), - ); - } -} diff --git a/lib/ui/layout/tileset.dart b/lib/ui/layout/tileset.dart deleted file mode 100644 index f18470d..0000000 --- a/lib/ui/layout/tileset.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:colors/ui/painters/board_painter.dart'; -import 'package:colors/provider/data.dart'; -import 'package:colors/utils/board_utils.dart'; - -class Tileset extends StatelessWidget { - const Tileset({ - super.key, - required this.myProvider, - required this.boardWidth, - }); - - final Data myProvider; - final double boardWidth; - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(4), - padding: const EdgeInsets.all(4), - child: Column( - children: [ - Center( - child: GestureDetector( - onTapUp: (details) { - final double xTap = details.localPosition.dx; - final double yTap = details.localPosition.dy; - final int boardSize = myProvider.boardSize; - final int col = xTap ~/ (boardWidth / boardSize); - final int row = yTap ~/ (boardWidth / boardSize); - final int cellValue = myProvider.getCellValue(col, row); - BoardUtils.fillBoardFromFirstCell(myProvider, cellValue); - }, - child: CustomPaint( - size: Size(boardWidth, boardWidth), - willChange: false, - painter: BoardPainter( - myProvider: myProvider, - colorScheme: Theme.of(context).colorScheme, - ), - isComplex: true, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/ui/painters/board_painter.dart b/lib/ui/painters/board_painter.dart index d1ec5b2..42bb2a2 100644 --- a/lib/ui/painters/board_painter.dart +++ b/lib/ui/painters/board_painter.dart @@ -1,19 +1,23 @@ import 'package:flutter/material.dart'; -import 'package:colors/entities/cell.dart'; +import 'package:colors/models/cell.dart'; +import 'package:colors/models/game.dart'; +import 'package:colors/models/types.dart'; import 'package:colors/utils/color_theme.dart'; -import 'package:colors/provider/data.dart'; class BoardPainter extends CustomPainter { - const BoardPainter({required this.myProvider, required this.colorScheme}); + const BoardPainter({ + required this.game, + required this.colorScheme, + }); - final Data myProvider; + final Game game; final ColorScheme colorScheme; @override void paint(Canvas canvas, Size size) { - final int boardSize = myProvider.boardSize; - final Board board = myProvider.board; + final int boardSize = game.gameSettings.boardSize; + final BoardCells cells = game.board.cells; final double cellSize = size.width / boardSize; // background @@ -22,9 +26,9 @@ class BoardPainter extends CustomPainter { for (var col = 0; col < boardSize; col++) { final double x = cellSize * col; - final Cell cell = board[row][col]; + final Cell cell = cells[row][col]; final int cellValue = cell.value; - final int colorCode = ColorTheme.getColorCode(myProvider.parameterSkin, cellValue); + final int colorCode = ColorTheme.getColorCode(game.globalSettings.skin, cellValue); final cellPaintBackground = Paint(); cellPaintBackground.color = Color(colorCode); @@ -40,7 +44,7 @@ class BoardPainter extends CustomPainter { // borders const double borderSize = 2; final cellPaintBorder = Paint(); - cellPaintBorder.color = colorScheme.onSurface; + cellPaintBorder.color = colorScheme.onBackground; cellPaintBorder.strokeWidth = borderSize; cellPaintBorder.strokeCap = StrokeCap.round; @@ -49,27 +53,27 @@ class BoardPainter extends CustomPainter { for (var col = 0; col < boardSize; col++) { final double x = cellSize * col; - final Cell cell = board[row][col]; + final Cell cell = cells[row][col]; final int cellValue = cell.value; - if ((row == 0) || (row > 1 && cellValue != myProvider.getCellValue(col, row - 1))) { + if ((row == 0) || (row > 1 && cellValue != game.board.getCellValue(col, row - 1))) { final Offset borderStart = Offset(x, y); final Offset borderStop = Offset(x + cellSize, y); canvas.drawLine(borderStart, borderStop, cellPaintBorder); } if ((row == boardSize - 1) || - ((row + 1) < boardSize && cellValue != myProvider.getCellValue(col, row + 1))) { + ((row + 1) < boardSize && cellValue != game.board.getCellValue(col, row + 1))) { final Offset borderStart = Offset(x, y + cellSize); final Offset borderStop = Offset(x + cellSize, y + cellSize); canvas.drawLine(borderStart, borderStop, cellPaintBorder); } - if ((col == 0) || (col > 1 && cellValue != myProvider.getCellValue(col - 1, row))) { + if ((col == 0) || (col > 1 && cellValue != game.board.getCellValue(col - 1, row))) { final Offset borderStart = Offset(x, y); final Offset borderStop = Offset(x, y + cellSize); canvas.drawLine(borderStart, borderStop, cellPaintBorder); } if ((col == boardSize - 1) || - ((col + 1) < boardSize && cellValue != myProvider.getCellValue(col + 1, row))) { + ((col + 1) < boardSize && cellValue != game.board.getCellValue(col + 1, row))) { final Offset borderStart = Offset(x + cellSize, y); final Offset borderStop = Offset(x + cellSize, y + cellSize); canvas.drawLine(borderStart, borderStop, cellPaintBorder); diff --git a/lib/ui/painters/parameter_painter.dart b/lib/ui/painters/parameter_painter.dart new file mode 100644 index 0000000..fb216ed --- /dev/null +++ b/lib/ui/painters/parameter_painter.dart @@ -0,0 +1,372 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:colors/config/default_game_settings.dart'; +import 'package:colors/config/default_global_settings.dart'; +import 'package:colors/models/settings_game.dart'; +import 'package:colors/models/settings_global.dart'; +import 'package:colors/utils/color_extensions.dart'; +import 'package:colors/utils/color_theme.dart'; +import 'package:colors/utils/tools.dart'; + +class ParameterPainter extends CustomPainter { + const ParameterPainter({ + required this.code, + required this.value, + required this.isSelected, + required this.gameSettings, + required this.globalSettings, + }); + + final String code; + final String value; + final bool isSelected; + final GameSettings gameSettings; + final GlobalSettings globalSettings; + + @override + void paint(Canvas canvas, Size size) { + // force square + final double canvasSize = min(size.width, size.height); + + const Color borderColorEnabled = Colors.blue; + const Color borderColorDisabled = Colors.white; + + // "enabled/disabled" border + final paint = Paint(); + paint.style = PaintingStyle.stroke; + paint.color = isSelected ? borderColorEnabled : borderColorDisabled; + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 20 / 100 * canvasSize; + canvas.drawRect( + Rect.fromPoints(const Offset(0, 0), Offset(canvasSize, canvasSize)), paint); + + // content + switch (code) { + case DefaultGameSettings.parameterCodeDifficultyLevel: + paintDifficultyLevelParameterItem(value, canvas, canvasSize); + break; + case DefaultGameSettings.parameterCodeBoardSize: + paintBoardSizeParameterItem(value, canvas, canvasSize); + break; + case DefaultGameSettings.parameterCodeColorsCount: + paintColorsCountParameterItem(value, canvas, canvasSize); + break; + case DefaultGlobalSettings.parameterCodeSkin: + paintUnknownParameterItem(value, canvas, canvasSize); + break; + default: + printlog('Unknown parameter: $code/$value'); + paintUnknownParameterItem(value, canvas, canvasSize); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return false; + } + + // "unknown" parameter -> simple block with text + void paintUnknownParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + paint.color = Colors.grey; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + final textSpan = TextSpan( + text: '?\n$value', + style: const TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + (size - textPainter.width) * 0.5, + (size - textPainter.height) * 0.5, + ), + ); + } + + void paintDifficultyLevelParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + Color backgroundColor = Colors.grey; + + final List<dynamic> stars = []; + + switch (value) { + case DefaultGameSettings.difficultyLevelValueEasy: + backgroundColor = Colors.green; + stars.add([0.5, 0.5]); + break; + case DefaultGameSettings.difficultyLevelValueMedium: + backgroundColor = Colors.orange; + stars.add([0.3, 0.5]); + stars.add([0.7, 0.5]); + break; + case DefaultGameSettings.difficultyLevelValueHard: + backgroundColor = Colors.red; + stars.add([0.3, 0.3]); + stars.add([0.7, 0.3]); + stars.add([0.5, 0.7]); + break; + case DefaultGameSettings.difficultyLevelValueNightmare: + backgroundColor = Colors.purple; + stars.add([0.3, 0.3]); + stars.add([0.7, 0.3]); + stars.add([0.3, 0.7]); + stars.add([0.7, 0.7]); + break; + default: + printlog('Wrong value for level parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + // Stars + final textSpan = TextSpan( + text: '⭐', + style: TextStyle( + color: Colors.black, + fontSize: size / 3, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + ); + textPainter.layout(); + + for (var center in stars) { + textPainter.paint( + canvas, + Offset( + size * center[0] - textPainter.width * 0.5, + size * center[1] - textPainter.height * 0.5, + ), + ); + } + } + + void paintBoardSizeParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + Color backgroundColor = Colors.grey; + int gridWidth = 1; + + switch (value) { + case DefaultGameSettings.boardSizeValueSmall: + backgroundColor = Colors.green; + gridWidth = 2; + break; + case DefaultGameSettings.boardSizeValueMedium: + backgroundColor = Colors.orange; + gridWidth = 3; + break; + case DefaultGameSettings.boardSizeValueLarge: + backgroundColor = Colors.red; + gridWidth = 4; + break; + case DefaultGameSettings.boardSizeValueExtra: + backgroundColor = Colors.purple; + gridWidth = 5; + break; + default: + printlog('Wrong value for boardSize parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + // Mini grid + final squareBackgroundColor = Colors.grey.shade200; + final squareBorderColor = Colors.grey.shade800; + + final double cellSize = size / 7; + final double origin = (size - gridWidth * cellSize) / 2; + + for (int row = 0; row < gridWidth; row++) { + for (int col = 0; col < gridWidth; col++) { + final Offset topLeft = Offset(origin + col * cellSize, origin + row * cellSize); + final Offset bottomRight = topLeft + Offset(cellSize, cellSize); + + paint.color = squareBackgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + + paint.color = squareBorderColor; + paint.style = PaintingStyle.stroke; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + } + } + } + + void paintColorsCountParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + Color backgroundColor = Colors.grey; + + switch (value) { + case DefaultGameSettings.colorsCountValueLow: + backgroundColor = Colors.green; + break; + case DefaultGameSettings.colorsCountValueMedium: + backgroundColor = Colors.orange; + break; + case DefaultGameSettings.colorsCountValueHigh: + backgroundColor = Colors.red; + break; + case DefaultGameSettings.colorsCountValueVeryHigh: + backgroundColor = Colors.purple; + break; + default: + printlog('Wrong value for colorsCount parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + // Colors preview + const List<Offset> positions = [ + Offset(0, 0), + Offset(1, 0), + Offset(2, 0), + Offset(2, 1), + Offset(2, 2), + Offset(1, 2), + Offset(0, 2), + Offset(0, 1), + ]; + + const skin = DefaultGlobalSettings.defaultSkinValue; + + final double padding = 4 / 100 * size; + final double margin = 3 / 100 * size; + final double width = ((size - 2 * padding) / 3) - 2 * margin; + + final int maxValue = int.parse(value); + for (int colorIndex = 0; colorIndex < maxValue; colorIndex++) { + final Offset position = positions[colorIndex]; + + final Offset topLeft = Offset(padding + margin + position.dx * (width + 2 * margin), + padding + margin + position.dy * (width + 2 * margin)); + + final Offset bottomRight = topLeft + Offset(width, width); + + final squareColor = Color(ColorTheme.getColorCode(skin, colorIndex + 1)); + paint.color = squareColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + + final borderColor = squareColor.darken(20); + paint.color = borderColor; + paint.style = PaintingStyle.stroke; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + } + + // centered text value + final textSpan = TextSpan( + text: value.toString(), + style: TextStyle( + color: Colors.black, + fontSize: size / 4, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + (size - textPainter.width) * 0.5, + (size - textPainter.height) * 0.5, + ), + ); + } + + void paintColorsThemeParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + Color backgroundColor = Colors.grey; + const int gridWidth = 4; + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + // Mini grid + final borderColor = Colors.grey.shade800; + + final double cellSize = size / gridWidth; + final double origin = (size - gridWidth * cellSize) / 2; + + for (int row = 0; row < gridWidth; row++) { + for (int col = 0; col < gridWidth; col++) { + final Offset topLeft = Offset(origin + col * cellSize, origin + row * cellSize); + final Offset bottomRight = topLeft + Offset(cellSize, cellSize); + + const squareColor = Colors.pink; + + paint.color = squareColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + + paint.color = borderColor; + paint.style = PaintingStyle.stroke; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + } + } + } +} diff --git a/lib/ui/screens/about_page.dart b/lib/ui/screens/about_page.dart deleted file mode 100644 index 459d008..0000000 --- a/lib/ui/screens/about_page.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:package_info_plus/package_info_plus.dart'; - -import 'package:colors/ui/widgets/header_app.dart'; - -class AboutPage extends StatelessWidget { - const AboutPage({super.key}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: <Widget>[ - const SizedBox(height: 8), - const AppHeader(text: 'about_title'), - const Text('about_content').tr(), - FutureBuilder<PackageInfo>( - future: PackageInfo.fromPlatform(), - builder: (context, snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.done: - return const Text('about_version').tr( - namedArgs: { - 'version': snapshot.data!.version, - }, - ); - default: - return const SizedBox(); - } - }, - ), - ], - ), - ); - } -} diff --git a/lib/ui/screens/game_page.dart b/lib/ui/screens/game_page.dart deleted file mode 100644 index d974148..0000000 --- a/lib/ui/screens/game_page.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:colors/ui/layout/game.dart'; -import 'package:colors/ui/layout/parameters.dart'; -import 'package:colors/provider/data.dart'; - -class GamePage extends StatefulWidget { - const GamePage({super.key}); - - @override - GamePageState createState() => GamePageState(); -} - -class GamePageState extends State<GamePage> { - @override - void initState() { - super.initState(); - - final Data myProvider = Provider.of<Data>(context, listen: false); - myProvider.initParametersValues(); - } - - @override - Widget build(BuildContext context) { - final Data myProvider = Provider.of<Data>(context); - final double boardWidth = MediaQuery.of(context).size.width; - - return SafeArea( - child: Center( - child: myProvider.gameIsRunning - ? Game(myProvider: myProvider, boardWidth: boardWidth) - : Parameters(myProvider: myProvider), - ), - ); - } -} diff --git a/lib/ui/screens/page_about.dart b/lib/ui/screens/page_about.dart new file mode 100644 index 0000000..a8602ef --- /dev/null +++ b/lib/ui/screens/page_about.dart @@ -0,0 +1,38 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +import 'package:colors/ui/widgets/helpers/header_app.dart'; + +class PageAbout extends StatelessWidget { + const PageAbout({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + const SizedBox(height: 8), + const AppHeader(text: 'about_title'), + const Text('about_content').tr(), + FutureBuilder<PackageInfo>( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.done: + return const Text('about_version').tr( + namedArgs: { + 'version': snapshot.data!.version, + }, + ); + default: + return const SizedBox(); + } + }, + ), + ], + ); + } +} diff --git a/lib/ui/screens/page_game.dart b/lib/ui/screens/page_game.dart new file mode 100644 index 0000000..d3a64ac --- /dev/null +++ b/lib/ui/screens/page_game.dart @@ -0,0 +1,18 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; + +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/ui/widgets/game/game.dart'; +import 'package:colors/ui/widgets/parameters.dart'; + +class PageGame extends StatelessWidget { + const PageGame({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + return gameState.currentGame.isRunning ? const GameWidget() : const Parameters(); + }); + } +} diff --git a/lib/ui/screens/page_settings.dart b/lib/ui/screens/page_settings.dart new file mode 100644 index 0000000..d559f95 --- /dev/null +++ b/lib/ui/screens/page_settings.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +import 'package:colors/ui/widgets/helpers/header_app.dart'; +import 'package:colors/ui/widgets/settings/settings_form.dart'; + +class PageSettings extends StatelessWidget { + const PageSettings({super.key}); + + @override + Widget build(BuildContext context) { + return const Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: <Widget>[ + SizedBox(height: 8), + AppHeader(text: 'settings_title'), + SizedBox(height: 8), + SettingsForm(), + ], + ); + } +} diff --git a/lib/ui/screens/settings_page.dart b/lib/ui/screens/settings_page.dart deleted file mode 100644 index dc59bbe..0000000 --- a/lib/ui/screens/settings_page.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:colors/ui/widgets/header_app.dart'; -import 'package:colors/ui/widgets/settings/settings_form.dart'; - -class SettingsPage extends StatelessWidget { - const SettingsPage({super.key}); - - @override - Widget build(BuildContext context) { - return const Padding( - padding: EdgeInsets.symmetric(horizontal: 8), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: <Widget>[ - SizedBox(height: 8), - AppHeader(text: 'settings_title'), - SizedBox(height: 8), - SettingsForm(), - ], - ), - ); - } -} diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index a428bc5..eaec6f6 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -1,47 +1,28 @@ -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_swipe/flutter_swipe.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; import 'package:colors/config/menu.dart'; -import 'package:colors/cubit/bottom_nav_cubit.dart'; -import 'package:colors/provider/data.dart'; -import 'package:colors/ui/widgets/app_bar.dart'; -import 'package:colors/ui/widgets/bottom_nav_bar.dart'; +import 'package:colors/cubit/nav_cubit.dart'; +import 'package:colors/ui/widgets/global_app_bar.dart'; -class SkeletonScreen extends StatefulWidget { +class SkeletonScreen extends StatelessWidget { const SkeletonScreen({super.key}); - @override - State<SkeletonScreen> createState() => _SkeletonScreenState(); -} - -class _SkeletonScreenState extends State<SkeletonScreen> { @override Widget build(BuildContext context) { - final Data myProvider = Provider.of<Data>(context); - return Scaffold( + appBar: const GlobalAppBar(), extendBodyBehindAppBar: false, - appBar: StandardAppBar(myProvider: myProvider), - body: Swiper( - itemCount: Menu.itemsCount, - itemBuilder: (BuildContext context, int index) { - return Menu.getPageWidget(index); - }, - pagination: SwiperPagination( - margin: const EdgeInsets.all(0), - builder: SwiperCustomPagination( - builder: (BuildContext context, SwiperPluginConfig config) { - return BottomNavBar(swipeController: config.controller); - }, - ), + body: Material( + color: Theme.of(context).colorScheme.background, + child: BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Menu.getPageWidget(pageIndex), + ); + }, ), - onIndexChanged: (newPageIndex) { - BlocProvider.of<BottomNavCubit>(context).updateIndex(newPageIndex); - }, - outer: true, - loop: false, ), backgroundColor: Theme.of(context).colorScheme.background, ); diff --git a/lib/ui/widgets/app_bar.dart b/lib/ui/widgets/app_bar.dart deleted file mode 100644 index c998df6..0000000 --- a/lib/ui/widgets/app_bar.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:overlay_support/overlay_support.dart'; - -import 'package:colors/provider/data.dart'; -import 'package:colors/ui/widgets/header_app.dart'; -import 'package:colors/utils/game_utils.dart'; - -class StandardAppBar extends StatelessWidget implements PreferredSizeWidget { - const StandardAppBar({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - final List<Widget> menuActions = []; - - if (myProvider.gameIsRunning) { - menuActions.add(TextButton( - child: const Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - onPressed: () => toast(tr('long_press_to_quit')), - onLongPress: () => GameUtils.quitGame(myProvider), - )); - } - - return AppBar( - title: const AppHeader(text: 'app_name'), - actions: menuActions, - ); - } - - @override - Size get preferredSize => const Size.fromHeight(50); -} diff --git a/lib/ui/widgets/bottom_nav_bar.dart b/lib/ui/widgets/bottom_nav_bar.dart deleted file mode 100644 index 87e30d5..0000000 --- a/lib/ui/widgets/bottom_nav_bar.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_swipe/flutter_swipe.dart'; - -import 'package:colors/config/menu.dart'; -import 'package:colors/cubit/bottom_nav_cubit.dart'; - -class BottomNavBar extends StatelessWidget { - const BottomNavBar({super.key, required this.swipeController}); - - final SwiperController swipeController; - - @override - Widget build(BuildContext context) { - return Card( - margin: const EdgeInsets.all(0), - elevation: 4, - shadowColor: Theme.of(context).colorScheme.shadow, - color: Theme.of(context).colorScheme.surfaceVariant, - shape: const ContinuousRectangleBorder(), - child: BlocBuilder<BottomNavCubit, int>( - builder: (BuildContext context, int state) { - return BottomNavigationBar( - currentIndex: state, - onTap: (int index) { - context.read<BottomNavCubit>().updateIndex(index); - swipeController.move(index); - }, - type: BottomNavigationBarType.fixed, - elevation: 0, - backgroundColor: Colors.transparent, - selectedItemColor: Theme.of(context).colorScheme.primary, - unselectedItemColor: Theme.of(context).textTheme.bodySmall!.color, - items: Menu.getMenuItems(), - ); - }, - ), - ); - } -} diff --git a/lib/ui/widgets/button_game_start_new.dart b/lib/ui/widgets/button_game_start_new.dart new file mode 100644 index 0000000..613d2fb --- /dev/null +++ b/lib/ui/widgets/button_game_start_new.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/cubit/settings_game_cubit.dart'; +import 'package:colors/cubit/settings_global_cubit.dart'; + +class StartNewGameButton extends StatelessWidget { + const StartNewGameButton({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameSettingsCubit, GameSettingsState>( + builder: (BuildContext context, GameSettingsState gameSettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + return TextButton( + child: const Image( + image: AssetImage('assets/icons/button_start.png'), + fit: BoxFit.fill, + ), + onPressed: () => gameCubit.startNewGame( + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/ui/widgets/home/button_game_restart.dart b/lib/ui/widgets/game/button_game_restart.dart similarity index 62% rename from lib/ui/widgets/home/button_game_restart.dart rename to lib/ui/widgets/game/button_game_restart.dart index 364ca07..dce403b 100644 --- a/lib/ui/widgets/home/button_game_restart.dart +++ b/lib/ui/widgets/game/button_game_restart.dart @@ -1,15 +1,15 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; -import 'package:colors/provider/data.dart'; -import 'package:colors/utils/game_utils.dart'; +import 'package:colors/cubit/game_cubit.dart'; class RestartGameButton extends StatelessWidget { - const RestartGameButton({super.key, required this.myProvider}); - - final Data myProvider; + const RestartGameButton({super.key}); @override Widget build(BuildContext context) { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + return TextButton( style: ButtonStyle( padding: MaterialStateProperty.all<EdgeInsets>(const EdgeInsets.all(0)), @@ -18,7 +18,7 @@ class RestartGameButton extends StatelessWidget { image: AssetImage('assets/icons/button_back.png'), fit: BoxFit.fill, ), - onPressed: () => GameUtils.quitGame(myProvider), + onPressed: () => gameCubit.quitGame(), ); } } diff --git a/lib/ui/widgets/game/cell_interactive.dart b/lib/ui/widgets/game/cell_interactive.dart new file mode 100644 index 0000000..40b67d0 --- /dev/null +++ b/lib/ui/widgets/game/cell_interactive.dart @@ -0,0 +1,55 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; + +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/models/cell.dart'; +import 'package:colors/models/game.dart'; +import 'package:colors/utils/color_theme.dart'; + +class InteractiveCell extends StatelessWidget { + const InteractiveCell({ + super.key, + required this.cell, + required this.colorScheme, + required this.currentGame, + required this.itemWidth, + }); + + final Cell cell; + final ColorScheme colorScheme; + final Game currentGame; + final double itemWidth; + + @override + Widget build(BuildContext context) { + final String skin = currentGame.globalSettings.skin; + final squareColor = Color(ColorTheme.getColorCode(skin, cell.value)); + + return Container( + margin: const EdgeInsets.all(2), + decoration: BoxDecoration( + border: Border.all( + color: colorScheme.onSurface, + width: 3, + ), + ), + child: GestureDetector( + child: Container( + color: squareColor, + padding: const EdgeInsets.all(4), + child: SizedBox.square( + dimension: itemWidth - 19, + ), + ), + onTap: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + if (!currentGame.animationInProgress && + currentGame.board.getFirstCellValue() != cell.value) { + gameCubit.fillBoardFromFirstCell(cell.value); + } + }, + ), + ); + } +} diff --git a/lib/ui/widgets/game/game.dart b/lib/ui/widgets/game/game.dart new file mode 100644 index 0000000..870676c --- /dev/null +++ b/lib/ui/widgets/game/game.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; + +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/models/game.dart'; +import 'package:colors/ui/widgets/game/game_board.dart'; +import 'package:colors/ui/widgets/game/game_top_indicator.dart'; +import 'package:colors/ui/widgets/game/message_game_end.dart'; +import 'package:colors/ui/widgets/game/select_color_bar.dart'; + +class GameWidget extends StatelessWidget { + const GameWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 8), + const GameTopIndicatorWidget(), + const SizedBox(height: 2), + const GameBoard(), + const Expanded( + child: SizedBox(height: 2), + ), + currentGame.isFinished ? const EndGameMessage() : const SelectColorBar(), + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/game_board.dart b/lib/ui/widgets/game/game_board.dart new file mode 100644 index 0000000..385fc17 --- /dev/null +++ b/lib/ui/widgets/game/game_board.dart @@ -0,0 +1,43 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; + +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/models/game.dart'; +import 'package:colors/ui/painters/board_painter.dart'; + +class GameBoard extends StatelessWidget { + const GameBoard({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + final double boardWidth = MediaQuery.of(context).size.width; + + return GestureDetector( + onTapUp: (details) { + final double xTap = details.localPosition.dx; + final double yTap = details.localPosition.dy; + final int boardSize = currentGame.gameSettings.boardSize; + final int col = xTap ~/ (boardWidth / boardSize); + final int row = yTap ~/ (boardWidth / boardSize); + final int cellValue = currentGame.board.getCellValue(col, row); + + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + gameCubit.fillBoardFromFirstCell(cellValue); + }, + child: CustomPaint( + size: Size(boardWidth, boardWidth), + willChange: false, + painter: BoardPainter( + game: currentGame, + colorScheme: Theme.of(context).colorScheme, + ), + isComplex: true, + ), + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/game_top_indicator.dart b/lib/ui/widgets/game/game_top_indicator.dart new file mode 100644 index 0000000..93948cd --- /dev/null +++ b/lib/ui/widgets/game/game_top_indicator.dart @@ -0,0 +1,70 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; + +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/models/game.dart'; + +class GameTopIndicatorWidget extends StatelessWidget { + const GameTopIndicatorWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; + + String progressIndicator = '${currentGame.progress}/${currentGame.progressTotal}'; + + if (currentGame.movesCount > 0) { + progressIndicator += ' (+${currentGame.progressDelta})'; + } + + Color maxMovesCountColor = Colors.grey; + if (currentGame.movesCount > currentGame.maxMovesCount) { + maxMovesCountColor = Colors.red; + } + + return Table( + children: [ + TableRow( + children: [ + Column( + children: [ + Text( + currentGame.movesCount.toString(), + style: TextStyle( + fontSize: 35, + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.primary, + ), + ) + ], + ), + Column( + children: [ + Text( + '(max: ${currentGame.maxMovesCount})', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: maxMovesCountColor, + ), + ), + Text( + progressIndicator, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: Colors.green, + ), + ), + ], + ), + ], + ), + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/game/indicator_top.dart b/lib/ui/widgets/game/indicator_top.dart deleted file mode 100644 index 610037e..0000000 --- a/lib/ui/widgets/game/indicator_top.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:colors/provider/data.dart'; - -class TopIndicator extends StatelessWidget { - const TopIndicator({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - String progressIndicator = '${myProvider.progress}/${myProvider.progressTotal}'; - - if (myProvider.movesCount > 0) { - progressIndicator += ' (+${myProvider.progressDelta})'; - } - - Color maxMovesCountColor = Colors.grey; - if (myProvider.movesCount > myProvider.maxMovesCount) { - maxMovesCountColor = Colors.red; - } - - return Table( - children: [ - TableRow( - children: [ - Column( - children: [ - Text( - myProvider.movesCount.toString(), - style: const TextStyle( - fontSize: 35, - fontWeight: FontWeight.w600, - color: Colors.grey, - ), - ), - ], - ), - Column( - children: [ - Text( - '(max: ${myProvider.maxMovesCount})', - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: maxMovesCountColor, - ), - ), - Text( - progressIndicator, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: Colors.green, - ), - ), - ], - ), - ], - ), - ], - ); - } -} diff --git a/lib/ui/widgets/game/message_game_end.dart b/lib/ui/widgets/game/message_game_end.dart index 4a9cd90..2a5cae8 100644 --- a/lib/ui/widgets/game/message_game_end.dart +++ b/lib/ui/widgets/game/message_game_end.dart @@ -1,44 +1,50 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; -import 'package:colors/ui/widgets/home/button_game_restart.dart'; -import 'package:colors/provider/data.dart'; +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/models/game.dart'; +import 'package:colors/ui/widgets/game/button_game_restart.dart'; class EndGameMessage extends StatelessWidget { - const EndGameMessage({super.key, required this.myProvider}); - - final Data myProvider; + const EndGameMessage({super.key}); @override Widget build(BuildContext context) { - String decorationImageAssetName = ''; - if (myProvider.gameWon) { - decorationImageAssetName = 'assets/icons/game_win.png'; - } else { - decorationImageAssetName = 'assets/icons/game_fail.png'; - } + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game currentGame = gameState.currentGame; - final Image decorationImage = Image( - image: AssetImage(decorationImageAssetName), - fit: BoxFit.fill, - ); + String decorationImageAssetName = ''; + if (currentGame.gameWon) { + decorationImageAssetName = 'assets/icons/game_win.png'; + } else { + decorationImageAssetName = 'assets/icons/game_fail.png'; + } + + final Image decorationImage = Image( + image: AssetImage(decorationImageAssetName), + fit: BoxFit.fill, + ); - return Container( - margin: const EdgeInsets.all(2), - padding: const EdgeInsets.all(2), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( + return Container( + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.all(2), + child: Table( + defaultColumnWidth: const IntrinsicColumnWidth(), children: [ - Column(children: [decorationImage]), - Column(children: [decorationImage]), - Column(children: [RestartGameButton(myProvider: myProvider)]), - Column(children: [decorationImage]), - Column(children: [decorationImage]), + TableRow( + children: [ + Column(children: [decorationImage]), + Column(children: [decorationImage]), + const Column(children: [RestartGameButton()]), + Column(children: [decorationImage]), + Column(children: [decorationImage]), + ], + ), ], ), - ], - ), + ); + }, ); } } diff --git a/lib/ui/widgets/game/select_color_bar.dart b/lib/ui/widgets/game/select_color_bar.dart index 8211b46..3b8addb 100644 --- a/lib/ui/widgets/game/select_color_bar.dart +++ b/lib/ui/widgets/game/select_color_bar.dart @@ -1,36 +1,42 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; -import 'package:colors/entities/cell.dart'; -import 'package:colors/provider/data.dart'; +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/models/cell.dart'; +import 'package:colors/ui/widgets/game/cell_interactive.dart'; class SelectColorBar extends StatelessWidget { - const SelectColorBar({super.key, required this.myProvider}); - - final Data myProvider; + const SelectColorBar({super.key}); @override Widget build(BuildContext context) { - final int maxValue = myProvider.colorsCount; - final ColorScheme colorScheme = Theme.of(context).colorScheme; + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final double blockWidth = MediaQuery.of(context).size.width; + final ColorScheme colorScheme = Theme.of(context).colorScheme; + final int maxValue = gameState.currentGame.gameSettings.colorsCount; - return Container( - margin: const EdgeInsets.all(2), - padding: const EdgeInsets.all(2), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - for (int value = 1; value <= maxValue; value++) - Column( - children: [ - Cell(value).interactiveWidget(myProvider, colorScheme), - ], - ), - ], - ), - ], - ), + return Table( + defaultColumnWidth: const IntrinsicColumnWidth(), + children: [ + TableRow( + children: [ + for (int value = 1; value <= maxValue; value++) + Column( + children: [ + InteractiveCell( + cell: Cell(value), + colorScheme: colorScheme, + currentGame: gameState.currentGame, + itemWidth: blockWidth / maxValue, + ), + ], + ), + ], + ), + ], + ); + }, ); } } diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart new file mode 100644 index 0000000..b3850be --- /dev/null +++ b/lib/ui/widgets/global_app_bar.dart @@ -0,0 +1,82 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; + +import 'package:colors/config/menu.dart'; +import 'package:colors/cubit/game_cubit.dart'; +import 'package:colors/cubit/nav_cubit.dart'; +import 'package:colors/models/game.dart'; +import 'package:colors/ui/widgets/helpers/app_titles.dart'; + +class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { + const GlobalAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + return BlocBuilder<NavCubit, int>( + builder: (BuildContext context, int pageIndex) { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + final Game currentGame = gameState.currentGame; + + final List<Widget> menuActions = []; + + if (currentGame.isRunning) { + menuActions.add(TextButton( + onPressed: null, + onLongPress: () => gameCubit.quitGame(), + child: const Image( + image: AssetImage('assets/icons/button_back.png'), + fit: BoxFit.fill, + ), + )); + } else { + if (pageIndex == Menu.indexGame) { + // go to Settings page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToSettingsPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemSettings.icon, + )); + + // go to About page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToAboutPage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemAbout.icon, + )); + } else { + // back to Home page + menuActions.add(ElevatedButton( + onPressed: () { + context.read<NavCubit>().goToGamePage(); + }, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + ), + child: Menu.menuItemGame.icon, + )); + } + } + + return AppBar( + title: const AppTitle(text: 'app_name'), + actions: menuActions, + ); + }, + ); + }, + ); + } + + @override + Size get preferredSize => const Size.fromHeight(50); +} diff --git a/lib/ui/widgets/helpers/app_titles.dart b/lib/ui/widgets/helpers/app_titles.dart new file mode 100644 index 0000000..7cbbb20 --- /dev/null +++ b/lib/ui/widgets/helpers/app_titles.dart @@ -0,0 +1,17 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class AppTitle extends StatelessWidget { + const AppTitle({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + tr(text), + textAlign: TextAlign.start, + style: Theme.of(context).textTheme.headlineLarge!.apply(fontWeightDelta: 2), + ); + } +} diff --git a/lib/ui/widgets/header_app.dart b/lib/ui/widgets/helpers/header_app.dart similarity index 77% rename from lib/ui/widgets/header_app.dart rename to lib/ui/widgets/helpers/header_app.dart index bf54b77..b5c5be0 100644 --- a/lib/ui/widgets/header_app.dart +++ b/lib/ui/widgets/helpers/header_app.dart @@ -8,15 +8,16 @@ class AppHeader extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( + return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( tr(text), textAlign: TextAlign.start, - style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2), + style: Theme.of(context).textTheme.headlineSmall!.apply(fontWeightDelta: 2), ), + const SizedBox(height: 8), ], ); } diff --git a/lib/ui/widgets/helpers/outlined_text_widget.dart b/lib/ui/widgets/helpers/outlined_text_widget.dart new file mode 100644 index 0000000..8e33709 --- /dev/null +++ b/lib/ui/widgets/helpers/outlined_text_widget.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +class OutlinedText extends StatelessWidget { + const OutlinedText({ + super.key, + required this.text, + required this.fontSize, + required this.textColor, + required this.outlineColor, + }); + + final String text; + final double fontSize; + final Color textColor; + final Color outlineColor; + + @override + Widget build(BuildContext context) { + final double delta = fontSize / 35; + + return Text( + text, + style: TextStyle( + inherit: true, + fontSize: fontSize, + fontWeight: FontWeight.w600, + color: textColor, + shadows: [ + Shadow( + offset: Offset(-delta, -delta), + color: outlineColor, + ), + Shadow( + offset: Offset(delta, -delta), + color: outlineColor, + ), + Shadow( + offset: Offset(delta, delta), + color: outlineColor, + ), + Shadow( + offset: Offset(-delta, delta), + color: outlineColor, + ), + ], + ), + ); + } +} diff --git a/lib/ui/widgets/home/button_game_start_new.dart b/lib/ui/widgets/home/button_game_start_new.dart deleted file mode 100644 index 80466e2..0000000 --- a/lib/ui/widgets/home/button_game_start_new.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:colors/provider/data.dart'; -import 'package:colors/ui/layout/parameters.dart'; -import 'package:colors/utils/game_utils.dart'; - -class StartNewGameButton extends StatelessWidget { - const StartNewGameButton({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(Parameters.blockMargin), - padding: const EdgeInsets.all(Parameters.blockPadding), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - Parameters.buildDecorationImageWidget(), - Column( - children: [ - TextButton( - child: Parameters.buildImageContainerWidget('button_start'), - onPressed: () => GameUtils.startNewGame(myProvider), - ), - ], - ), - Parameters.buildDecorationImageWidget(), - ], - ), - ], - ), - ); - } -} diff --git a/lib/ui/widgets/parameters.dart b/lib/ui/widgets/parameters.dart new file mode 100644 index 0000000..55525a9 --- /dev/null +++ b/lib/ui/widgets/parameters.dart @@ -0,0 +1,121 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; + +import 'package:colors/config/default_game_settings.dart'; +import 'package:colors/config/default_global_settings.dart'; +import 'package:colors/cubit/settings_game_cubit.dart'; +import 'package:colors/cubit/settings_global_cubit.dart'; +import 'package:colors/ui/painters/parameter_painter.dart'; +import 'package:colors/ui/widgets/button_game_start_new.dart'; + +class Parameters extends StatelessWidget { + const Parameters({super.key}); + + final double separatorHeight = 8.0; + + @override + Widget build(BuildContext context) { + final List<Widget> lines = []; + + lines.add(SizedBox(height: separatorHeight)); + + // Game settings + for (String code in DefaultGameSettings.availableParameters) { + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: false, + ), + )); + + lines.add(SizedBox(height: separatorHeight)); + } + + lines.add(SizedBox(height: separatorHeight)); + lines.add(const Expanded(child: StartNewGameButton())); + lines.add(SizedBox(height: separatorHeight)); + + // Global settings + for (String code in DefaultGlobalSettings.availableParameters) { + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: true, + ), + )); + + lines.add(SizedBox(height: separatorHeight)); + } + + return Column( + children: lines, + ); + } + + List<Widget> buildParametersLine({ + required String code, + required bool isGlobal, + }) { + final List<Widget> parameterButtons = []; + + final List<String> availableValues = isGlobal + ? DefaultGlobalSettings.getAvailableValues(code) + : DefaultGameSettings.getAvailableValues(code); + + if (availableValues.length <= 1) { + return []; + } + + for (String value in availableValues) { + final Widget parameterButton = BlocBuilder<GameSettingsCubit, GameSettingsState>( + builder: (BuildContext context, GameSettingsState gameSettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + final GameSettingsCubit gameSettingsCubit = + BlocProvider.of<GameSettingsCubit>(context); + final GlobalSettingsCubit globalSettingsCubit = + BlocProvider.of<GlobalSettingsCubit>(context); + + final String currentValue = isGlobal + ? globalSettingsCubit.getParameterValue(code) + : gameSettingsCubit.getParameterValue(code); + + final bool isActive = (value == currentValue); + + final double displayWidth = MediaQuery.of(context).size.width; + final double itemWidth = displayWidth / availableValues.length - 30; + + return TextButton( + child: Container( + margin: const EdgeInsets.all(0), + padding: const EdgeInsets.all(0), + child: CustomPaint( + size: Size(itemWidth, itemWidth), + willChange: false, + painter: ParameterPainter( + code: code, + value: value, + isSelected: isActive, + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ), + isComplex: true, + ), + ), + onPressed: () => isGlobal + ? globalSettingsCubit.setParameterValue(code, value) + : gameSettingsCubit.setParameterValue(code, value), + ); + }, + ); + }, + ); + + parameterButtons.add(parameterButton); + } + + return parameterButtons; + } +} diff --git a/lib/ui/widgets/settings/theme_card.dart b/lib/ui/widgets/settings/theme_card.dart index 5fd7bd7..834c67d 100644 --- a/lib/ui/widgets/settings/theme_card.dart +++ b/lib/ui/widgets/settings/theme_card.dart @@ -1,5 +1,5 @@ -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; import 'package:colors/cubit/theme_cubit.dart'; diff --git a/lib/utils/board_utils.dart b/lib/utils/board_utils.dart deleted file mode 100644 index 7a63ce8..0000000 --- a/lib/utils/board_utils.dart +++ /dev/null @@ -1,168 +0,0 @@ -import 'dart:math'; -import 'dart:async'; - -import 'package:colors/entities/cell.dart'; -import 'package:colors/provider/data.dart'; -import 'package:colors/utils/tools.dart'; - -class BoardUtils { - static printGrid(List board) { - const String stringValues = '012345678'; - printlog(''); - printlog('-------'); - for (int rowIndex = 0; rowIndex < board.length; rowIndex++) { - String row = ''; - for (int colIndex = 0; colIndex < board[rowIndex].length; colIndex++) { - row += stringValues[board[rowIndex][colIndex].value]; - } - printlog(row); - } - printlog('-------'); - printlog(''); - } - - static createNewBoard(Data myProvider) { - final int boardSize = myProvider.boardSize; - final int maxValue = myProvider.colorsCount; - - Random rand = Random(); - - Board board = []; - for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { - List<Cell> row = []; - for (int colIndex = 0; colIndex < boardSize; colIndex++) { - final int value = 1 + rand.nextInt(maxValue); - row.add(Cell(value)); - } - board.add(row); - } - printGrid(board); - - myProvider.resetGame(); - myProvider.updateBoard(board); - myProvider.updateMaxMovesCount(computeMaxMovesCountLimit(myProvider)); - - final int initProgress = BoardUtils.getSiblingFillableCells(myProvider, 0, 0, [ - [0, 0] - ]).length; - myProvider.updateProgress(initProgress); - } - - static int computeMaxMovesCountLimit(myProvider) { - final int boardSize = myProvider.boardSize; - final int colorsCount = myProvider.colorsCount; - - final int baseMaxMovesCount = (30 * (boardSize * colorsCount) / (17 * 6)).round(); - final int deltaFromLevel = - myProvider.getMovesCountLimitDeltaFromLevelParameter(myProvider.parameterLevel); - - return baseMaxMovesCount + deltaFromLevel; - } - - static fillBoardFromFirstCell(Data myProvider, int value) { - List<List<int>> cellsToFill = BoardUtils.getSiblingFillableCells(myProvider, 0, 0, [ - [0, 0] - ]); - final int progressBeforeMove = cellsToFill.length; - - myProvider.incrementMovesCount(); - - // Sort cells from the closest to the furthest, relatively to the top left corner - cellsToFill - .sort((a, b) => (pow(a[0], 2) + pow(a[1], 2)).compareTo(pow(b[0], 2) + pow(b[1], 2))); - - const interval = Duration(milliseconds: 10); - int cellIndex = 0; - myProvider.updateAnimationInProgress(true); - Timer.periodic( - interval, - (Timer timer) { - if (cellIndex < cellsToFill.length) { - myProvider.updateCellValue( - cellsToFill[cellIndex][1], cellsToFill[cellIndex][0], value); - cellIndex++; - } else { - timer.cancel(); - - int progressAfterMove = BoardUtils.getSiblingFillableCells(myProvider, 0, 0, [ - [0, 0] - ]).length; - int progressDelta = progressAfterMove - progressBeforeMove; - myProvider.updateProgressDelta(progressDelta); - myProvider.updateProgress(progressAfterMove); - - myProvider.updateAnimationInProgress(false); - - if (BoardUtils.checkBoardIsSolved(myProvider)) { - myProvider.updateGameWon(true); - } - } - }, - ); - } - - static List<List<int>> getSiblingFillableCells( - Data myProvider, - int row, - int col, - List<List<int>> siblingCells, - ) { - final Board board = myProvider.board; - final int boardSize = myProvider.boardSize; - - final int referenceValue = board[row][col].value; - - for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { - for (int deltaCol = -1; deltaCol <= 1; deltaCol++) { - if (deltaCol == 0 || deltaRow == 0) { - final int candidateRow = row + deltaRow; - final int candidateCol = col + deltaCol; - - if ((candidateRow >= 0 && candidateRow < boardSize) && - (candidateCol >= 0 && candidateCol < boardSize)) { - if (board[candidateRow][candidateCol].value == referenceValue) { - bool alreadyFound = false; - for (int index = 0; index < siblingCells.length; index++) { - if ((siblingCells[index][0] == candidateRow) && - (siblingCells[index][1] == candidateCol)) { - alreadyFound = true; - } - } - if (!alreadyFound) { - siblingCells.add([candidateRow, candidateCol]); - siblingCells = getSiblingFillableCells( - myProvider, - candidateRow, - candidateCol, - siblingCells, - ); - } - } - } - } - } - } - - return siblingCells; - } - - static bool checkBoardIsSolved(Data myProvider) { - final Board board = myProvider.board; - final int boardSize = myProvider.boardSize; - - // check grid is fully completed and does not contain conflict - int previousValue = board[0][0].value; - for (int row = 0; row < boardSize; row++) { - for (int col = 0; col < boardSize; col++) { - if (board[row][col].value == 0 || board[row][col].value != previousValue) { - return false; - } - previousValue = board[row][col].value; - } - } - - printlog('-> ok grid solved!'); - - return true; - } -} diff --git a/lib/utils/color_extensions.dart b/lib/utils/color_extensions.dart new file mode 100644 index 0000000..39c9322 --- /dev/null +++ b/lib/utils/color_extensions.dart @@ -0,0 +1,35 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +extension ColorExtension on Color { + Color darken([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = 1 - percent / 100; + return Color.fromARGB( + alpha, + (red * value).round(), + (green * value).round(), + (blue * value).round(), + ); + } + + Color lighten([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = percent / 100; + return Color.fromARGB( + alpha, + (red + ((255 - red) * value)).round(), + (green + ((255 - green) * value)).round(), + (blue + ((255 - blue) * value)).round(), + ); + } + + Color avg(Color other) { + final red = (this.red + other.red) ~/ 2; + final green = (this.green + other.green) ~/ 2; + final blue = (this.blue + other.blue) ~/ 2; + final alpha = (this.alpha + other.alpha) ~/ 2; + return Color.fromARGB(alpha, red, green, blue); + } +} diff --git a/lib/utils/color_theme.dart b/lib/utils/color_theme.dart index 476f82b..73d2e37 100644 --- a/lib/utils/color_theme.dart +++ b/lib/utils/color_theme.dart @@ -1,6 +1,8 @@ +import 'package:colors/config/default_global_settings.dart'; + class ColorTheme { static const Map<String, List<int>> borderColors = { - 'default': [ + DefaultGlobalSettings.skinValueColors: [ 0xffffff, 0xe63a3f, 0x708cfd, diff --git a/lib/utils/game_utils.dart b/lib/utils/game_utils.dart deleted file mode 100644 index 6b7e2cf..0000000 --- a/lib/utils/game_utils.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:colors/provider/data.dart'; -import 'package:colors/utils/board_utils.dart'; -import 'package:colors/utils/tools.dart'; - -class GameUtils { - static void quitGame(Data myProvider) { - myProvider.updateGameIsRunning(false); - } - - static void startNewGame(Data myProvider) { - printlog('Starting game'); - printlog('- level: ${myProvider.parameterLevel}'); - printlog('- size: ${myProvider.boardSize}'); - printlog('- colors: ${myProvider.colorsCount}'); - - BoardUtils.createNewBoard(myProvider); - - myProvider.updateGameIsRunning(true); - } -} diff --git a/lib/utils/tools.dart b/lib/utils/tools.dart index fd48b2b..8ed7a53 100644 --- a/lib/utils/tools.dart +++ b/lib/utils/tools.dart @@ -1,7 +1,15 @@ import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; void printlog(String message) { if (!kReleaseMode) { debugPrint(message); } } + +Widget buildImageContainerWidget(String imageAssetCode) { + return Image( + image: AssetImage('assets/icons/$imageAssetCode.png'), + fit: BoxFit.fill, + ); +} diff --git a/pubspec.lock b/pubspec.lock index 2885f1a..25b5be0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: bloc - sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" url: "https://pub.dev" source: hosted - version: "8.1.3" + version: "8.1.4" characters: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: c145aeb6584aedc7c862ab8c737c3277788f47488bfdf9bae0fe112bd0a4789c + sha256: "432698c31a488dd64c56d4759f20d04844baba5e9e4f2cb1abb9676257918b17" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" easy_logger: dependency: transitive description: @@ -106,31 +106,23 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1" + sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "8.1.5" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" flutter_localizations: dependency: transitive description: flutter source: sdk version: "0.0.0" - flutter_swipe: - dependency: "direct main" - description: - name: flutter_swipe - sha256: dc6541bac3a0545ce15a3fa15913f6250532062960bf6b0ad4562d02f14a8545 - url: "https://pub.dev" - source: hosted - version: "1.0.1" flutter_web_plugins: dependency: transitive description: flutter @@ -148,10 +140,10 @@ packages: dependency: transitive description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_parser: dependency: transitive description: @@ -164,10 +156,10 @@ packages: dependency: "direct main" description: name: hydrated_bloc - sha256: "00a2099680162e74b5a836b8a7f446e478520a9cae9f6032e028ad8129f4432d" + sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c url: "https://pub.dev" source: hosted - version: "9.1.4" + version: "9.1.5" intl: dependency: transitive description: @@ -208,32 +200,24 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - overlay_support: - dependency: "direct main" - description: - name: overlay_support - sha256: fc39389bfd94e6985e1e13b2a88a125fc4027608485d2d4e2847afe1b2bb339c - url: "https://pub.dev" - source: hosted - version: "2.1.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" + sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "8.0.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" path: - dependency: "direct main" + dependency: transitive description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" @@ -244,18 +228,18 @@ packages: dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.4" path_provider_foundation: dependency: transitive description: @@ -305,7 +289,7 @@ packages: source: hosted version: "2.1.8" provider: - dependency: "direct main" + dependency: transitive description: name: provider sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c @@ -313,21 +297,21 @@ packages: source: hosted version: "6.1.2" shared_preferences: - dependency: "direct main" + dependency: transitive description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_foundation: dependency: transitive description: @@ -356,10 +340,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -433,18 +417,18 @@ packages: dependency: transitive description: name: web - sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.5.1" win32: dependency: transitive description: name: win32 - sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.5.0" xdg_directories: dependency: transitive description: @@ -454,5 +438,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.3.0-279.1.beta <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5e4adf9..e6b8af0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: colors description: Fill the board, a colorfull game! publish_to: 'none' -version: 0.0.38+38 +version: 0.0.39+39 environment: sdk: '^3.0.0' @@ -14,15 +14,10 @@ dependencies: easy_localization: ^3.0.1 equatable: ^2.0.5 flutter_bloc: ^8.1.1 - flutter_swipe: ^1.0.1 hive: ^2.2.3 hydrated_bloc: ^9.0.0 - overlay_support: ^2.1.0 - shared_preferences: ^2.2.1 - package_info_plus: ^5.0.1 - path: ^1.9.0 + package_info_plus: ^8.0.0 path_provider: ^2.0.11 - provider: ^6.0.5 unicons: ^2.1.1 dev_dependencies: @@ -32,7 +27,6 @@ flutter: uses-material-design: true assets: - assets/icons/ - - assets/skins/ - assets/translations/ fonts: -- GitLab