diff --git a/android/app/build.gradle b/android/app/build.gradle
index c8ef79082898053c7143502554fda40840b0eb5c..c49f6d91e764c06d2c70dacc143444d661a0bbd4 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.sudoku"
 
     defaultConfig {
diff --git a/android/gradle.properties b/android/gradle.properties
index 142ae7bb643e141eab9405dc204c1c1bd653816f..5a0756cd02457428e3b8367e76eb2061b39f8a81 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.1.18
-app.versionCode=67
+app.versionName=0.1.19
+app.versionCode=68
diff --git a/assets/files/templates.json b/assets/files/templates.json
deleted file mode 100644
index 3a37ebd141958e36c6ccbb97e146eacae2f75497..0000000000000000000000000000000000000000
--- a/assets/files/templates.json
+++ /dev/null
@@ -1,355 +0,0 @@
-{
-  "templates": {
-    "2x2": {
-      "easy": [
-        "4020204332141032",
-        "0302043100133124",
-        "3240412303000432",
-        "4231014200230304",
-        "0310012332001032",
-        "2300143041200201",
-        "2401310042130020",
-        "3040142020044302",
-        "4031304020131004",
-        "4231030401400402",
-        "4302120004210103",
-        "0000421324010102",
-        "0010310042301320",
-        "0302420021340400",
-        "2400132002030102",
-        "3204002023004032",
-        "4013310010420001",
-        "4132030102001003",
-        "0000302110404210",
-        "0001300002131302"
-      ],
-      "medium": [
-        "2403000000201000",
-        "0000001004323000",
-        "0000103000203004",
-        "0000201010030040",
-        "0001403000130000",
-        "0002030101030000",
-        "0003004041002000",
-        "0003204030000400",
-        "0004003003022000",
-        "0004100040200200",
-        "0004402010000200",
-        "0021000040002030",
-        "0024000030000403",
-        "0024000030004100",
-        "0030010000000314",
-        "0032000003404000",
-        "0102001000204000",
-        "0140000003041000",
-        "0203000101004000",
-        "0300120000020040"
-      ],
-      "hard": [
-        "1000040300200000",
-        "1000200000020003",
-        "1000300200040000",
-        "1000400000200010",
-        "1000400100000020",
-        "1400000030200000",
-        "2000001000403000",
-        "2000400100400000",
-        "2000400300000040",
-        "2030002000000100",
-        "2100000000300200",
-        "3000002000001040",
-        "3002000000140000",
-        "3002040000030000",
-        "3100000000000204",
-        "4000000000302010",
-        "4000000020010003",
-        "4000000200300300",
-        "4000300200000030",
-        "4000300400000020"
-      ],
-      "nightmare": [
-        "1000200300040000",
-        "1002000300004000",
-        "1004000200003000",
-        "1400000000002030",
-        "2000100200000030",
-        "2000104000300000",
-        "2001030000020000",
-        "2010000000000403",
-        "2010000043000000",
-        "2030000000004001",
-        "2300000010000002",
-        "3000002000040100",
-        "3001000000002400",
-        "3002040000000003",
-        "3020000000000403",
-        "4000003000101000",
-        "4000031000300000",
-        "4000204000000003",
-        "4002000000000103",
-        "4300000000200400"
-      ]
-    },
-    "3x2": {
-      "easy": [
-        "425361136520241053003142350016610230",
-        "123405506012002530354126230641461050",
-        "215460640001050612160350501246426035",
-        "143560526041410023362054201030034010",
-        "314065056314461023500106030051005632",
-        "620000305106504362263014136005052631",
-        "015204064315400051106003523040041532",
-        "020306346152050010614200061403432061",
-        "065400342000534201620304206143403605",
-        "305010421635106503530401650002204006",
-        "342016106003403650020301064100531064",
-        "413625265300120403050010002164600502",
-        "512406300512200054030620154200603140",
-        "000040540231461050230164154020600015",
-        "040203032045050026261534600052420001",
-        "160052020060536010240035050320312540",
-        "210036630050542603063024300145400060",
-        "241530500004050312002405400253025601",
-        "301500050040432600610230560123123400",
-        "614205003016040301136542400003060054"
-      ],
-      "medium": [
-        "050000000251001300040102410500300004",
-        "460001005042000153000000530004620000",
-        "000000500063040256060000423005100002",
-        "004600000000102300506420050060021500",
-        "005000041000500120210503400005003002",
-        "005000300004000006003020002015150243",
-        "120000540000250064600100000020002015",
-        "200010400600001520500160000040040206",
-        "450300300240020000040502200006600020",
-        "504000310400043050100000030506060001",
-        "002006000502325400140300050000200004",
-        "600013000050000100025060060230201500",
-        "000040100523020000600050002100301002",
-        "000560005040000230600015530000410000",
-        "040035000106520000300004062000405000",
-        "050010000003104300020060005100001230",
-        "056002200005002001400050000600023010",
-        "060000010062026310050006005400000003",
-        "461000000001010520602000003260000100",
-        "500040146035450000001000005000030100"
-      ],
-      "hard": [
-        "060210050300003450000000020100001000",
-        "104020002000000200013050500301000000",
-        "210000004300020540000001060000001400",
-        "300260500000002006000504000001010040",
-        "400000001640000002500034100000003010",
-        "400610200340004001000200050000300000",
-        "500100000260000004052000000030100450",
-        "502000000000004360000005030240006050",
-        "600001042006000000060003000500001604",
-        "602000000004000163010000501400000001",
-        "000000602000000005401300300621000030",
-        "000006100004020030000061030102005000",
-        "000020000615150000003000046003000400",
-        "000046000000206004003000100032000601",
-        "000400000003000006410500003650065000",
-        "000500050601003006040100004000300400",
-        "003000000602000001000250300005420003",
-        "006401000500000300100000020050001603",
-        "010000006000302400000000403100160003",
-        "010403000001040002000500300000065200"
-      ],
-      "nightmare": [
-        "000006020500050003100002300000500600",
-        "000060600500000040031000025000060003",
-        "001024000300630000000003000000065001",
-        "003000000005060010100402014000000001",
-        "004000063000600002000050001030200004",
-        "004001000600200050041030020000100000",
-        "006000040100002003600002000536000000",
-        "006100000030000600000040054300030050",
-        "030045020000006000003010500002000006",
-        "060000000503604000000002105030000001",
-        "060000102050000042000010005000000034",
-        "305004000010000200050000046050000002",
-        "400000001032000020000500030040500001",
-        "406300300100000000000513004006000000",
-        "502010000004000000010020030000100540",
-        "600000050000006004200005000150010060",
-        "600010003000065000000000200106000520",
-        "000000005002000000431000002040000305",
-        "003001020000400005060400600000001000",
-        "200016000000006000000002000500604300"
-      ]
-    },
-    "3x3": {
-      "easy": [
-        "751283946482769351936451827629074138045398672378610095217046583803025764564837210",
-        "560810042803764150010320876438671025256400731791502684379006518645187093182953467",
-        "054283906007915032239674001390167008745020169601459327923846715406501203518732694",
-        "840257069026039478000846302362510907700362540154098603235681794017920836680473215",
-        "281600947960271508070894012010946375496730081530128496629017853140082009750069124",
-        "710290485490517602260384970047650298829470100651928743986045307170809504504702809",
-        "070081395814035620953276814200158743045369081138704069060847152521000070487512030",
-        "401975026060128309289306175305009714790512083816030592143000968972063451650400237",
-        "100398465305104800890002371632700598009603710471985206253410907746039102918057643",
-        "548009210910426005007581394401963782826174950379850460105007600694218537000695108",
-        "010428009920653800480017623863079245594206730271534960700860300008042190102795486",
-        "459203017387415290000798054062307900731984600040020030674139082198572403523800179",
-        "700009816980016720651872409460208351025347608308165004006924187814050002279081043",
-        "400057210728031459500402670000389026800165934963720085317900560284006301609203847",
-        "050980043197643285048572196589307402000000010710068359435120900971835020820700031",
-        "410000050926570108350410729030045960895067402004032810271308604083094271049720583",
-        "597631004128907305600508179002059046480763002906200730735492618204806053000010427",
-        "020359174035187060791406800000534006600802401300761598268910347510600082073048610",
-        "028569470043180650507204018800400720090608031200703006480070195015942387079805264",
-        "095007641640589020073006859104203590307095006580614702950060070418730960736001084"
-      ],
-      "medium": [
-        "043700080007085321000360700500890070080547263024610058405130890208074036369058010",
-        "060300010549000026071900548654130709093460050102570604425603870800050200907201405",
-        "304008765859000300627453000793800002000100430146325007275030640038060001401580973",
-        "475631002016940300903870014080106740040003006702584139004009078090008401608417900",
-        "020196030790080206306702100070430028060020301083671050807300060012057093935264010",
-        "280315009360070500540869320070000031026138975138050200700500692650001700093720004",
-        "574023098239080064106947003860204031000000605340006280412309050000062010093805470",
-        "602503010030740080417086003854012006096070501020605000279008164040160278008427005",
-        "703004058090065047840070190671509800000406725254300619026908401010040563000600902",
-        "920451030045026079061080045170208400294500600008040912412070308050830001703160090",
-        "014095070020140050635280140700514900000903867093608500002439785300050002070860430",
-        "207006158891574620000000700028030471053420900900081205002040569039100047486700300",
-        "304027095501043200200000010892070140615400720000281956006010472708002501123050800",
-        "320608047700420010060197000573001008000530071416089020008302000297815364630970002",
-        "905624083000038070320001406457092001209500748100070009092147360736000910510360000",
-        "147080526030070000980254071300460907004013602008705010462897005859000004700046200",
-        "470308605950260304003050000200005070005603142300192506530000000104586709086034251",
-        "804250931090830046170040582020080400000702059300094020010520094508610003002408165",
-        "843106057126050400700004060580600721001205094279010036300001005000902018918503600",
-        "946010350001000002730040186250481093090700020604030875028000500105608234060023708"
-      ],
-      "hard": [
-        "000050000020190070036740000060400800503008704004009100601900230209007600008020917",
-        "504700003810093200023061800701026009002300008380000501000030000049100050208000090",
-        "006300809780590023003008670172849365000050000050000007000080730000205010000060090",
-        "050040107900817605071600900000060700000080003300002500020401006607200409090036800",
-        "070408000001000900836010020007002830100500762000607100760000210508701003003000079",
-        "083009001651000008700518000007986100860000429019000006500040000906053010000007300",
-        "200568703009173004003200000907000512000005400032700800080000001300600240720410000",
-        "370280106009700020000000073068070000590430060700010490850090000930108507046000000",
-        "509040078401600020700005600070000460180070500056412300042000800890000003005008700",
-        "635000010017030040804000000963100200000042090040000871001053960000007400006429030",
-        "470350009000008247809470500000000726000926850000700000708000105305810902000000070",
-        "600905030200067800097400020024050001000080950809010003000096002480300019030040700",
-        "140000083027005140600000900700236051403050069000014000000700002000090610094000530",
-        "700520400001708000060000100050000678000480210608005000000130894300040000149057002",
-        "907000060620000501043069008070004000090057000400908070350000010060091052100006904",
-        "513740600020160005000080300700000000458900026000054097004000060082006500000400738",
-        "000000200000300081200080396071020038090400107000003509000034000807209000023750810",
-        "000782030214305090000109500058201000439600000007000600300410007900000403700003002",
-        "003900000047080000956002378000065014800730500005200000000129000090070420021300060",
-        "005093100690080740128060000000008000061400050709600010904007020080002007050000481"
-      ],
-      "nightmare": [
-        "058001093200030004000200071905420000000007050000100000080304060703900005006810700",
-        "060140000720000000040875060070000493092007800004050000000090740000060025058004001",
-        "068070000003004000000180370900407601000901007000030040009043108057000000030602090",
-        "005208010000000809128690004000020000006900430009040208080014000004002700000009600",
-        "009007400030600000000485000200030107000070020170204600546700092800000040010000050",
-        "020500603000300004009024750050200006008000200012603000034000000507000001280900070",
-        "090304005000200490050086302300009250900000060070050130063000710020000500010000000",
-        "390007801508360020040950607000000000800003910700040005000030700010000086000200040",
-        "012034050000009640030600000784001300003080060000300078000002030005006000068000907",
-        "000000000050078200000590870600200100007004500004003008500000723040000006276030401",
-        "002000008070040031500890007009200000000503040040000150610000000000368700780150003",
-        "800000650300000080005007002108790400600050100052040060003000200000019000500003748",
-        "800004305000000790001000000010805000090100000200600184700200041402006070000037026",
-        "000000012240601009000300050600100020000700003320040605460008001900000008000036900",
-        "000409003750060040000000206000031079000000832500702600400007000000314060006000001",
-        "000000500009106430060090000008000050004000982300980007000035610017000090000000270",
-        "073002600200105008000000000729306100040000900000008004000003000007560001098200006",
-        "800003000050001079006079003000200001900700060000010305032090506000002000087000040",
-        "000800000039605400008000603005390000700006000380170004002900100004020306010000000",
-        "200000090500206700816700003400100000000050030900084520000000807000620050000000062"
-      ]
-    },
-    "4x4": {
-      "easy": [
-        "00B6483AE20FDC91GA8F0E659D3172B42D9CGF71406B538E03E42B9D8C57GFA6C2G06184DFEA9B7596105GE724C3FAD8A45DF32B6709E1CGEF78A9DCB01G64327860BC423GAE19FD30AED51F7040CG2BBGF13000C9D20507DC02E7G9F1B508634EC9825613FDB7GA81D79AFG00263E4CFB3G1D00A0042659650A74B30E9C8D1F",
-        "G8D13540972EFB0A3BC980EDA61457G2AF5721B683CGED0404267AG9DBF53018C7381BAFEGD92645BGA40EC7056018F95DF098647CA1GE3B916E532008BFC07D4EGA091BC20D0F8385B00732G906D4C16C1D0G00B473A92E297300DEF18060B0F38CG09A5047B1E602E5B6831A9C40DF064BED050FG093ACDA9G40F16E3B8257",
-        "48F0071693BC5DEA613CD5E80G0AB42029EA3C4B8051F0600D5B9F0G0426831C0C057634129E0B0G9218GB05F0A0E6430060E82C4530D9F10E431AF9D86GC75213865907GFE24ACD0000C1G36A842590C000FD6A5003700E059F4E82CB7D0106E40D625107G0003B3B790GCD014F6EA05601B47F3EC9G2D88FG2A09EB0D51C74",
-        "0381D0GA9FB06C20G0600842E03CFD50EFC5003BD42A1070A24D0F05800G3B9E00B30CFG19AE24D6800C041070F0G9EB4E0F00B902G8C531091G2DE35C4B708F0030A28CG617DEF0DC780B5F2E94AG130006E0D70A83024C2GAE439005D006B71D0230C40BE690A894F7G16EA852B0CDC6EAB9203D714FG5380BF7AD0G09E162",
-        "1DEF0A5C47209B865B7948D20E003AG000000763D0005E4F6003BE0085C0120D01026500E3D480C0030D841EC0F9002B8F9C020BAG15E364E64G093002B8AD51B0D8A3460157FC9GCGF19D853406200A40362FC7BA9GD01E92A0EGB1FC8D6430D80B7194GF3EC5A2GAC406285D700003F9253BEG680041D730105CAD9B42G608",
-        "G9D05382CEA06BF445EC19D0G06F0780F08070A0451D009EA700G4FE9B830C0D6C159B40AFG0D8E783B020107000FA0020FDA00059C4B163740AED6008300905C0A0F694D0EG851BE64080201C5B73G95130C0G7FA904D20DG9831B52046CEAF1D700208E40A960C382E00C96D75AFB1BFC407E031295GD89A56DF018GBCE472",
-        "0DC63B47F058G20AF0102E06DBAGC30032A009FD06E048500GE8005A3490DB6F2AFCE0005G01B0085047AGCFB230169E1E905003008A0700B63G1D78E9F45A2CAF736000C5198GDB600298DGA740E0F300B9FA3008DE05160C8E7500036F29A44900D3A00EB561C78705BFE90A0600G2E3008C600DG790B0C00147009F23A08D",
-        "AE3G6B980120DF0C0C007040900D06EA97185D0F00642BG3460523CAE0B0098004500F120EC798A6F06AD00E0G980254C829B076F403GD0E70E100A96052C3B0D04007GC50E60A910F8796B14A3CE5D2EA0600000710B4000193EA8020DF6C702DGC0057864EF13B15B0CEF0327A8G09037F0820D9G100C589AE106305F04720",
-        "08030C4B0D102A096F940E0580A203071A000F023G4B8C000B000A300F0E0645EC7B24509068F03DA08GB7CF03D402E0D26918A3G0EFB40000F00D9EB00C08GA0105C3D968FAEG727EAC06B020034D9036D2A5F4E9700B0189GFE20004BDA0635GC600EAD2893714FD3A518C4E07692B900E4G20A631500804180076F0C50E0G",
-        "0900FGE8071A24606G502D9B00CF73A10F17A0408D23G0B02400510000008E0FGBFAE2C0D1050843C8040AF630001000769ED5310080F2GAD03187040F60C09EA10G30020507D084534D0E0G20009AF682CF04DA0E00050097B6C85F030DE12GFCG9400E7AD856024A63GC2DF901B7E80D726F000C4E0G00BE8519A06230400D",
-        "D0B09E8G203610C4716GF024B590380D00980D0CE0FG0075ECA205B7400060G9A00EB0060GD00407002D005FC6478B30G45602030EB0C0D0073B48ED02A9F05G0G000C0ED47093868D07003A91EFG50B02437098GC5BE0AF0EF5G4D0380A712C56E103A90F0007B000D080016B205G933000D06279C5A0F12B79EFG5A3104C68",
-        "0E000648F0C1D352000F02GD00B507016000F000D340G0BE0000C105A090F846930C671B2AGD0F850A8004F2EC03961B00D1G8AC0564E200702459030F18CGA0G4530EB0C0D7092F8DB0430051290AG00F090C2G00AB5DE727CA0D0680FGB000E9060F81GB30250AD1A030C9420F7E680G7025640DE03BF935F2AB0E79801400",
-        "2BD7G0F0C0010590A419E67B50F3G2CD8305D00C7400BE10CF0641020B00A3780030AF0G490DE020E904B706F0008GD0FD205000G0081B07B68G300D007A49050548706F3E0BCDG0DEBA180527GC3F46706FC0E3A004918B90C000D0168F57EA0A0BFD006C1970303C0295B080470A0E007080000F3E2C59089E6037D2A0F4B1",
-        "032E0B061AC89DG4G0400C012B5930006B80290GE04D51C090C5084D0G0F002BADE090020613C8054G685FAE02BC07D300000DG4F50E0B000259601008D4000018A4G07FC936E20DE60214905D07GCAF5F0G0203A400867909D0E0680F2G4531000C008A07050002207601398EG0D4FAD0GB462501FA79008A0100D70C62B050",
-        "FGE427ACB09056D15C72BDE9G416A00000B6F301720EG490091064000C0D27EB010CA00240GB3079089A0070053162G06E07810B0D29000F20FGE50307001CBD8B20000006D4931A03A19000EB007D0540C01E0A39780G629760003520AG0EC0E54072B09A60D13GC600G91F8302E05770G930CD1F05002000835000DGB7C0F6",
-        "008A3259016FG0D4CD5070063E9A028134960G0807D00FE00F20D0BE0G08960061EFA54G0372D8C9G3ABFC20608901705040E890AF1D030G0970B163ECG0000006C809021030FE0A0010C03A042G7050F0G0500189AEC040A0B0GE8F7DC6291300D003AC48000590903C00E5G60184AD000590G400E31C0B0A0E801B905C00F0",
-        "16G20E8C50A00F490FD03190G70405007E390D450C00A2G8C040A6F028091D7E008G07C9A0406B53B0564GE0D00098000A20001B69750G004C006250EGB0D100A20FC9G6850BE431G8C12B7D009006FA000005AEF0G07082596EF400721000DBDG0000600E210A0C9500GFB70386001D60089A21CD50F0B0031BE0049AFG8705",
-        "247301050B000FDCCD800GBE450F10066F0007C908GD003EG90E00400C360080AC541E9B00D708FG90G27A0061080BC0D0300C800A5G00E701F7G600E40B30000B08092GF000CD64106FEB0A8D4C000052ED0413090AF0704309F006G200AEB1FA90000C57B4830DE8DC05A03G92704F302140F0C0AE0009B04693G7DF81EC2A",
-        "A064E087DFC3B29190080A0CEB106400G00093D460700F0AF00E006000A0D85758BC64F0A0E900G004G7B1A3F856C92E13900075B40D06AFE6FA800D312C754BB056A010C00G3D742A0G30BF0E009C6881E90020000F00B50703D590060000E2C00D263A40075B0039050B4800DEA0F0700F0D01906B00800G400FC01A852ED3",
-        "00B0070800E605FD800000G1039D0C0200D5FB49000C600130G9C05DF0B0870E0286B09FE5G731C40701AC649D8300009DC4G870100006500F0B1205006008975G02D31C04FA097019FC0EBA27DG5308030009F0B6C01DEG7B0D048G0E19CA2F0C0G70AEDB302F10059E41320000GB8CB0170006G20000D30A2380DB00407060"
-      ],
-      "medium": [
-        "00B6483AE20FDC91GA8F0E659D3172B42D9CGF71406B538E03E42B9D8C57GFA6C2G06184DFEA9B7596105GE724C3FAD8A45DF32B6709E1CGEF78A9DCB01G64327860BC423GAE19FD30AED51F7040CG2BBGF13000C9D20507DC02E7G9F1B508634EC9825613FDB7GA81D79AFG00263E4CFB3G1D00A0042659650A74B30E9C8D1F",
-        "G8D13540972EFB0A3BC980EDA61457G2AF5721B683CGED0404267AG9DBF53018C7381BAFEGD92645BGA40EC7056018F95DF098647CA1GE3B916E532008BFC07D4EGA091BC20D0F8385B00732G906D4C16C1D0G00B473A92E297300DEF18060B0F38CG09A5047B1E602E5B6831A9C40DF064BED050FG093ACDA9G40F16E3B8257",
-        "48F0071693BC5DEA613CD5E80G0AB42029EA3C4B8051F0600D5B9F0G0426831C0C057634129E0B0G9218GB05F0A0E6430060E82C4530D9F10E431AF9D86GC75213865907GFE24ACD0000C1G36A842590C000FD6A5003700E059F4E82CB7D0106E40D625107G0003B3B790GCD014F6EA05601B47F3EC9G2D88FG2A09EB0D51C74",
-        "0381D0GA9FB06C20G0600842E03CFD50EFC5003BD42A1070A24D0F05800G3B9E00B30CFG19AE24D6800C041070F0G9EB4E0F00B902G8C531091G2DE35C4B708F0030A28CG617DEF0DC780B5F2E94AG130006E0D70A83024C2GAE439005D006B71D0230C40BE690A894F7G16EA852B0CDC6EAB9203D714FG5380BF7AD0G09E162",
-        "1DEF0A5C47209B865B7948D20E003AG000000763D0005E4F6003BE0085C0120D01026500E3D480C0030D841EC0F9002B8F9C020BAG15E364E64G093002B8AD51B0D8A3460157FC9GCGF19D853406200A40362FC7BA9GD01E92A0EGB1FC8D6430D80B7194GF3EC5A2GAC406285D700003F9253BEG680041D730105CAD9B42G608",
-        "G9D05382CEA06BF445EC19D0G06F0780F08070A0451D009EA700G4FE9B830C0D6C159B40AFG0D8E783B020107000FA0020FDA00059C4B163740AED6008300905C0A0F694D0EG851BE64080201C5B73G95130C0G7FA904D20DG9831B52046CEAF1D700208E40A960C382E00C96D75AFB1BFC407E031295GD89A56DF018GBCE472",
-        "0DC63B47F058G20AF0102E06DBAGC30032A009FD06E048500GE8005A3490DB6F2AFCE0005G01B0085047AGCFB230169E1E905003008A0700B63G1D78E9F45A2CAF736000C5198GDB600298DGA740E0F300B9FA3008DE05160C8E7500036F29A44900D3A00EB561C78705BFE90A0600G2E3008C600DG790B0C00147009F23A08D",
-        "AE3G6B980120DF0C0C007040900D06EA97185D0F00642BG3460523CAE0B0098004500F120EC798A6F06AD00E0G980254C829B076F403GD0E70E100A96052C3B0D04007GC50E60A910F8796B14A3CE5D2EA0600000710B4000193EA8020DF6C702DGC0057864EF13B15B0CEF0327A8G09037F0820D9G100C589AE106305F04720",
-        "08030C4B0D102A096F940E0580A203071A000F023G4B8C000B000A300F0E0645EC7B24509068F03DA08GB7CF03D402E0D26918A3G0EFB40000F00D9EB00C08GA0105C3D968FAEG727EAC06B020034D9036D2A5F4E9700B0189GFE20004BDA0635GC600EAD2893714FD3A518C4E07692B900E4G20A631500804180076F0C50E0G",
-        "0900FGE8071A24606G502D9B00CF73A10F17A0408D23G0B02400510000008E0FGBFAE2C0D1050843C8040AF630001000769ED5310080F2GAD03187040F60C09EA10G30020507D084534D0E0G20009AF682CF04DA0E00050097B6C85F030DE12GFCG9400E7AD856024A63GC2DF901B7E80D726F000C4E0G00BE8519A06230400D",
-        "D0B09E8G203610C4716GF024B590380D00980D0CE0FG0075ECA205B7400060G9A00EB0060GD00407002D005FC6478B30G45602030EB0C0D0073B48ED02A9F05G0G000C0ED47093868D07003A91EFG50B02437098GC5BE0AF0EF5G4D0380A712C56E103A90F0007B000D080016B205G933000D06279C5A0F12B79EFG5A3104C68"
-      ],
-      "hard": [
-        "B329800F0140D0A0G700E0290500B6316058G01D2F00C079010CB005G00928FE00BG000E00230D073DF009G00C08001480E002F107G0A39C0A1034DB695F00G27000A08C90010000000B0E325G871006AF010090D000GCE820GED1B60AF07903F0CD96E0701405B000801D50020G074004302008FD9561CG0G754FC030E09000",
-        "G000070A9008C00BD040C02B603GE9050200G3091004D8A000690105E0A0F0326002A0D050EC00FG8FA06017300D5C0E0050F9B3000A20063G905E000600B08D1030BC0D20F0GE602E00958FD00700C4900F060EBA8372015086027004009BDF00F0ED9000C1602046D01AF070928000B92G705CF30610EA01E02036A8D54FG0",
-        "00G05A0E300000009C0A0004G201705EF07E1000A6B50G40010870DF904CB00A2D6430G510CE000FAE0G0180042FC0D3CF05AB42000D6E017010E0FC000024B50000C00025E40D86G9C10F0B006045E250ED2038B1G7900C0A0200E0FCD93B1700A3G000CF0B0804000C80B0E00000908B90F0C100A0023D1G200300D856EC7B",
-        "08B007E90F03042D0040830G00D2E015950DAC0B08013600E0F750D09C04B08G8000007102009E6CD000BE300706080A00E000A639087001700A28900G4E003B60C0028E0590A0D03A8E95CF4D17600204090B1D0A3G8C0EF1000A632000570400600050G02000E04E920FG0C65D1BA0C0031628B4EA0F0010A5ED04007002C6",
-        "10F3000C0A600502A0C8F9B320106D70B0G0052D0EF70000D2004600C09B00E000B0000G9C00A6585C6A3840ED009B07009F6B00A15402D33GD2AF50B780E40C8F30009460BA100EG500B3000901082600A9G00000CEF00B000001F0G340D9A003007EDF14028A60900714365F0C2EBGF42G0A8B000057016A002CG08070000D",
-        "CF004000906020107428000900010600D0050000230E70409310800B7C4DEFG5514003BC00800G6F000079F0G4250A000ED76A8G00FC35B0G8A0E4000B03927039040502000AG0010DF10G98300467A06A0GD743000B080080020F0EC09G04D0F5BD30E040G9A000420C9BGFE0108D50A08E521607CF0B30179608040500FCEG",
-        "30400000070005000FC84050B000D970E0D003A04000F6GB0050E9B8CF0G010000B005E4800300909E36B100F02D075A000400000000E30170A090236E00C400276E001B5G349F8054002E3FD006G000ADFC000609B70E148003D479A10E52600C8G300596721A00F31A0B00G0E57C4606E010GC30AF2800D07260FE0840B035",
-        "0B0G017A00C080390390D240700BC0G01420000CGA900F5000CF0GB00328040ADA30000000G7000FB1560AF08D009740G040CD07250F00A079F0G004301A00D8C074B0D86E300190060BA0C900D150000010F70G00A548B6008A0E01B042000C80A1E06000B00DC702000C005000G084476520GD0C03F001FCBD48A31G0E2965",
-        "193580400A6DF200AEG010B0040758D68B0DF50C129G4007020706005008100AF60C7D2A050304E0750B010F4009030800025C90670E0D0F409AEB800C0F200568002A7000F1350GD009B3F000C46080B3040G00A002E00DG001000836D007C05C700010D9008A0E0006A802000C0F549AF80EC700500G0000DG30560E8A00B1",
-        "8076FGE9500DB1C0B2A934100000807004050B0C900A0306F000006A38BG940537600C82DB04G009090B0000760C0003D08049300E02670C0G04E6700980DA02000008G000091F5790070001BF650D48100000B0200700A00A0E00FD83C12B9G08900FC0G00B0260A650GD080700EC0B41B06200C50EA8DFC0DFBA53004800G1",
-        "C4D0FB83050200060G105000D009A70B89050070310600CDE000D0028CA0300001C2070D089A6400400000A90D0EB0006009004B00F38C000D0E6F0804C19G2A0800CD07900B56020F000805001D003C060DB020CE05F8099000E3GA4680D07105A4700F100C0EB8FC080254E0D7136G070G81BEA3040F95000B309C0G580DA0",
-        "0000AE000B0264080960000070C01A00B3080074E1A0050D10F0DG006895B0E70G0BC13D2F007605000086EB045AC2008C0300451009FD0E00E5F9A70C0301000B4920C600000G73F60000095738000AE8GD070002460C907030E41809GC0B6F0F709360400BE0D004B0700E0A00300G000G1C020360A00B310C0A0F9GED0006",
-        "0G0BC01D72000405000860320GD0BE7C0100G500AF00093D407D0EF830C01200F0070009GD010080004A80E0900F2D510200D0B0003ACG0F1C00020G8600009A001638705E0DG0B0083FE00C6B9G5A10A00G0B00F002360427B50DG6000C980E0A049F0E01G30500700E006A00004BC380920G00B0F0D1E6CD00730B040EA0G9",
-        "95AE0008GF7D000B80005200B4EC0FA323C00FD090600408B004GE1C30280D00600020CE008795F0020C0050003F00D0F0E70B0020153A400A9304076CDB80020008E104079A560G3B0FD0028146EC00A149000650BED83FE050B80F00000214C0F2000510A00G0E403A002G00000007G06089E07BF000C07E1B0003C0G20900",
-        "B1C047820003056900000903060B0E0C08030065D29C1GA79000FACD18G00043870CA5160E304FBGG20F74300A00C800A03BD0F864C090020001200B7G80A03000G000B05002000D09BD630A01E07080000050700CD6GA0B00E090D083B0005F0D4000A00000F0GE2F003D57G90EB000E0700000ADF139003GA68F9EC0205701",
-        "G0DA30004010085004080000305000E0002085E060A94BG00390704B8000A26DEB0900F003006A08A8F50B040090D301207DC090000850BE063C005D1GE09F720500B20AE14D398619034EC0062A000BD00EG300B07F010507A6D00F003CE00G7064000002B0000A0C1264AEF08G05070G00003750000C0F900F2G0C7ED08004",
-        "00700300A0DC000F960C85EF04G230A03A0109D200070000800GC00A0F530ED00036700190F00040008F040G75EA9D36000E0B39002D7C00270008F043C051BE00000000F000C0250D03027C0005B010C002010BE064807G14005080C2BG00E0F0C00000D00E6791794A0DB5001F00C0E2001F6750A000G36310EC940G78F05B",
-        "0F0B206D000085CA0170B00000280G00G80400EF05007000A60000G907D0130E40F0GC000D000E600C080BF6G04100720B0009000006040G63G07D40020A0005C0409287D0E00B13300654D0B002E0G0090560007384D0FC00B0FE10A90C5847000900002A3FG0088G0DC1240B0E0006FE3205900C1DA7B475613F0A080G20ED",
-        "4C1A0F090GBE3D570GD35000C08A000F0020CEBA041D8906E900000D05F0C00A0E897GF40200000024C009A800D50EF01BF0035097000602D0060B000E000894060430G0F0095A0080GE4A90DC00BF01000000E048A6237052010076B00004DC0100B00F5A0360E0700290DGE00000A500EF07612DGB40000D6B05030F741G00",
-        "G60B9F0DEC0A4508F10040200000DA004AC2580B0G000F900E0000C35F00020BE2B306A000975400000000004083EC2A1F003500006E000754002E00DA00B1366000009FBE02AD040DG06705C0000900380ED14A907G2BC0AB090200801D0050B5600C37F2001009D340B0G160E000020701E456A039F00000EAFD0000CB6305"
-      ],
-      "nightmare": [
-        "0000AE000B0264080960000070C01A00B3080074E1A0050D10F0DG006895B0E70G0BC13D2F007605000086EB045AC2008C0300451009FD0E00E5F9A70C0301000B4920C600000G73F60000095738000AE8GD070002460C907030E41809GC0B6F0F709360400BE0D004B0700E0A00300G000G1C020360A00B310C0A0F9GED0006",
-        "0G0BC01D72000405000860320GD0BE7C0100G500AF00093D407D0EF830C01200F0070009GD010080004A80E0900F2D510200D0B0003ACG0F1C00020G8600009A001638705E0DG0B0083FE00C6B9G5A10A00G0B00F002360427B50DG6000C980E0A049F0E01G30500700E006A00004BC380920G00B0F0D1E6CD00730B040EA0G9",
-        "95AE0008GF7D000B80005200B4EC0FA323C00FD090600408B004GE1C30280D00600020CE008795F0020C0050003F00D0F0E70B0020153A400A9304076CDB80020008E104079A560G3B0FD0028146EC00A149000650BED83FE050B80F00000214C0F2000510A00G0E403A002G00000007G06089E07BF000C07E1B0003C0G20900",
-        "B1C047820003056900000903060B0E0C08030065D29C1GA79000FACD18G00043870CA5160E304FBGG20F74300A00C800A03BD0F864C090020001200B7G80A03000G000B05002000D09BD630A01E07080000050700CD6GA0B00E090D083B0005F0D4000A00000F0GE2F003D57G90EB000E0700000ADF139003GA68F9EC0205701",
-        "G0DA30004010085004080000305000E0002085E060A94BG00390704B8000A26DEB0900F003006A08A8F50B040090D301207DC090000850BE063C005D1GE09F720500B20AE14D398619034EC0062A000BD00EG300B07F010507A6D00F003CE00G7064000002B0000A0C1264AEF08G05070G00003750000C0F900F2G0C7ED08004",
-        "00700300A0DC000F960C85EF04G230A03A0109D200070000800GC00A0F530ED00036700190F00040008F040G75EA9D36000E0B39002D7C00270008F043C051BE00000000F000C0250D03027C0005B010C002010BE064807G14005080C2BG00E0F0C00000D00E6791794A0DB5001F00C0E2001F6750A000G36310EC940G78F05B",
-        "0F0B206D000085CA0170B00000280G00G80400EF05007000A60000G907D0130E40F0GC000D000E600C080BF6G04100720B0009000006040G63G07D40020A0005C0409287D0E00B13300654D0B002E0G0090560007384D0FC00B0FE10A90C5847000900002A3FG0088G0DC1240B0E0006FE3205900C1DA7B475613F0A080G20ED",
-        "4C1A0F090GBE3D570GD35000C08A000F0020CEBA041D8906E900000D05F0C00A0E897GF40200000024C009A800D50EF01BF0035097000602D0060B000E000894060430G0F0095A0080GE4A90DC00BF01000000E048A6237052010076B00004DC0100B00F5A0360E0700290DGE00000A500EF07612DGB40000D6B05030F741G00",
-        "G60B9F0DEC0A4508F10040200000DA004AC2580B0G000F900E0000C35F00020BE2B306A000975400000000004083EC2A1F003500006E000754002E00DA00B1366000009FBE02AD040DG06705C0000900380ED14A907G2BC0AB090200801D0050B5600C37F2001009D340B0G160E000020701E456A039F00000EAFD0000CB6305",
-        "GB824DAC100300500400B2938050000E0951G700A20D000C00300F150G90B0020019000070600F30CFA7004BD00000003605A02E0008007040G0F6075000010D25EB046030090A0F0CF6E5B0GD80042790007AG0E00F6D01D07A03800B2000E000040C70B80EG610012G0EF003AC790B000D0B3109G5A2C08A0C00062071DEF3",
-        "G003C00820015006F00C5D04G09010A712003A70050C8BD90A0021E000B003FG30000007A00F008E05B0F02090863G7098A0B05310D00020D60E80003400F15BBD0906A0583EG000AG30004000000508000098004F0D07B20F00D0G00A100C9300F0E000B24A00CD70500201DG00083FE08D40B0FC702AG1000A070F806304E0",
-        "00ABFE5006009081FD7496G3000805206C528710F000D4E3918EAC0200306F0700DGB3600501F00C09B00F0100G00700163000000B0AG050450700C00080B60080C0E500091F00A0G01AC0300E6B400FDF0502B0G3A0C0987069G00F004C1B05040100000000000D000071284G0E500907004B0502D08100BA000D9010567200",
-        "70200805ECG1490D840G0920070D00000F51E00DB00602370D00C7A00040B5GE03A00D680B2E105990DE503081AF00CBB0F000000470E8D0008500000D000G0AE26FD07C000090B0000403E6A009C7200B70800006F030E5G093A0127000008409G841C06E0A53F02EB7F053081G040000C0GB000F020000F0002AD000000B10",
-        "00900DEF6210005A60CAB9420D5700E0000E0007000C40080075AC000F9E60001E80FG60040A507090GB20000300140CF0070AC498B1G020C020010DF0E0806020AF0E96B70G30D0760410D50AF9E000E300C0FG000800000G1C7B3000D290800900E000G53B0007B0000351700400000030G708200DC940G000D0A0108F2B0E",
-        "0105D082GCA0E007000063CB70D04008DEGC7F0430B850090700000509000F0D007810F02E0AG0CB00CDA70008G01062F00600BECD50800090008G000400DE0000000801E000000006B10CGD90800A040CD00003A671B09000FE9060B0CD71530002E03F87940CG100E00A0800FC90D00093000G5A00F076000F4597D0000200",
-        "2000DG601B097000F7000AB100050090DCE84925G30A000005007CF86E0D0G30G000135D0008C94F000620809FG000500A8F04000000E1670009060701E302AG0053C74G80960DF0000A0109005008000G028500BC0E9070B89D00A60030540C000008G00010F60006BE00040980G00082F0A000740010D040010F7CE5023A00",
-        "89506B0000700E0F00E003G861CF5DB00C1B570E4820G36A06G00001005A09470035BE00000G700CE8670G35B0000094B2D00AF000E5600G0004000276A000E00GA000860E4000730B00A00F0037400603080D002060905E5D46E070F90B00080481009G0700CB35000904B0C000E001250071E00004A08D000D00000208F000",
-        "ED0CG300820B01AF0000000065040C3003G1F0400D90607E608BA7C0FE0G0200A040000508F7006B3B0600F20409GDC0C52FD07000GA000801E00CBG53000700D8043600000F9G01003750G000A2E00C09C20080006E3A00G060000A3100740000000D082G060097000E40309F81A0G0970G2060D000800446100000E700D003",
-        "000003G0F040E56010E60A0D7800F00400009F0C010E003A0DF07000C60A08120E0C00BG5A0160496000000000008A000934500062EB1000G21BA0E60F9D7000D0G000030B02A170B0A008250G06040D00C0D10704F50EG05800B00000D00000F00A1002D3604B07030700DB1EA00086EB00G57F09C003A081000E00B0270D0C",
-        "1G05B289ADE0000608070500000300A003E04D00620G0B8062C0G0A3589B400000000E0G00450000047000003A6C0G00AD8003CB000204000C037000DB89A5008769DGB0C3A1FE05D02GF007800000C100140030B90D7AG8003C80250070B09000G008000CB00F00719004E62500G8DA0F0800G0760000000000105000D8C069"
-      ]
-    }
-  }
-}
diff --git a/assets/fonts/Nunito-Bold.ttf b/assets/fonts/Nunito-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..6519feb781449ebe0015cbc74dfd9e13110fbba9
Binary files /dev/null and b/assets/fonts/Nunito-Bold.ttf differ
diff --git a/assets/fonts/Nunito-Light.ttf b/assets/fonts/Nunito-Light.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..8a0736c41cd6c2a1225d356bf274de1d0afc3497
Binary files /dev/null and b/assets/fonts/Nunito-Light.ttf differ
diff --git a/assets/fonts/Nunito-Medium.ttf b/assets/fonts/Nunito-Medium.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..88fccdc0638b6f5d6ac49d9d269dc3d518618ad1
Binary files /dev/null and b/assets/fonts/Nunito-Medium.ttf differ
diff --git a/assets/fonts/Nunito-Regular.ttf b/assets/fonts/Nunito-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..e7b8375a896ef0cd8e06730a78c84532b377e784
Binary files /dev/null and b/assets/fonts/Nunito-Regular.ttf differ
diff --git a/assets/icons/button_delete_saved_game.png b/assets/icons/button_delete_saved_game.png
deleted file mode 100644
index 5e4f217689b11e444b7163557d7e5d68f3bbfe7d..0000000000000000000000000000000000000000
Binary files a/assets/icons/button_delete_saved_game.png and /dev/null differ
diff --git a/assets/icons/button_resume_game.png b/assets/icons/button_resume_game.png
deleted file mode 100644
index b2ea0a02d05e42377eb551a4b51428b511a32f5d..0000000000000000000000000000000000000000
Binary files a/assets/icons/button_resume_game.png and /dev/null differ
diff --git a/assets/icons/level_easy.png b/assets/icons/level_easy.png
deleted file mode 100644
index aa9e2485272c12fd99d9e1faae5d71d027ee5431..0000000000000000000000000000000000000000
Binary files a/assets/icons/level_easy.png and /dev/null differ
diff --git a/assets/icons/level_hard.png b/assets/icons/level_hard.png
deleted file mode 100644
index 433c686b984fd22c4966def3dedcaf7384951810..0000000000000000000000000000000000000000
Binary files a/assets/icons/level_hard.png and /dev/null differ
diff --git a/assets/icons/level_medium.png b/assets/icons/level_medium.png
deleted file mode 100644
index 213f2ca844afb0cea47cc4bdd1b865433b068b59..0000000000000000000000000000000000000000
Binary files a/assets/icons/level_medium.png and /dev/null differ
diff --git a/assets/icons/level_nightmare.png b/assets/icons/level_nightmare.png
deleted file mode 100644
index df4182c1e228bbb33c5546d2df38d16ca1edd4f2..0000000000000000000000000000000000000000
Binary files a/assets/icons/level_nightmare.png and /dev/null differ
diff --git a/assets/icons/size_2x2.png b/assets/icons/size_2x2.png
deleted file mode 100644
index a8ac1675f003cf6815af46243f6a1cabaaef39af..0000000000000000000000000000000000000000
Binary files a/assets/icons/size_2x2.png and /dev/null differ
diff --git a/assets/icons/size_3x2.png b/assets/icons/size_3x2.png
deleted file mode 100644
index 9b4aa9a8f2a5e4d81c6029749ba52202973e69f3..0000000000000000000000000000000000000000
Binary files a/assets/icons/size_3x2.png and /dev/null differ
diff --git a/assets/icons/size_3x3.png b/assets/icons/size_3x3.png
deleted file mode 100644
index 77d6fb098cb8fb69f3632afcf2bef65c2d15ef4d..0000000000000000000000000000000000000000
Binary files a/assets/icons/size_3x3.png and /dev/null differ
diff --git a/assets/icons/size_4x4.png b/assets/icons/size_4x4.png
deleted file mode 100644
index 9ed5ea90e13d2eb476c430a7a01108b207dcf0b0..0000000000000000000000000000000000000000
Binary files a/assets/icons/size_4x4.png and /dev/null differ
diff --git a/assets/icons/skin_default.png b/assets/icons/skin_digits.png
similarity index 100%
rename from assets/icons/skin_default.png
rename to assets/icons/skin_digits.png
diff --git a/assets/skins/default_1.png b/assets/skins/digits_1.png
similarity index 100%
rename from assets/skins/default_1.png
rename to assets/skins/digits_1.png
diff --git a/assets/skins/default_10.png b/assets/skins/digits_10.png
similarity index 100%
rename from assets/skins/default_10.png
rename to assets/skins/digits_10.png
diff --git a/assets/skins/default_11.png b/assets/skins/digits_11.png
similarity index 100%
rename from assets/skins/default_11.png
rename to assets/skins/digits_11.png
diff --git a/assets/skins/default_12.png b/assets/skins/digits_12.png
similarity index 100%
rename from assets/skins/default_12.png
rename to assets/skins/digits_12.png
diff --git a/assets/skins/default_13.png b/assets/skins/digits_13.png
similarity index 100%
rename from assets/skins/default_13.png
rename to assets/skins/digits_13.png
diff --git a/assets/skins/default_14.png b/assets/skins/digits_14.png
similarity index 100%
rename from assets/skins/default_14.png
rename to assets/skins/digits_14.png
diff --git a/assets/skins/default_15.png b/assets/skins/digits_15.png
similarity index 100%
rename from assets/skins/default_15.png
rename to assets/skins/digits_15.png
diff --git a/assets/skins/default_16.png b/assets/skins/digits_16.png
similarity index 100%
rename from assets/skins/default_16.png
rename to assets/skins/digits_16.png
diff --git a/assets/skins/default_2.png b/assets/skins/digits_2.png
similarity index 100%
rename from assets/skins/default_2.png
rename to assets/skins/digits_2.png
diff --git a/assets/skins/default_3.png b/assets/skins/digits_3.png
similarity index 100%
rename from assets/skins/default_3.png
rename to assets/skins/digits_3.png
diff --git a/assets/skins/default_4.png b/assets/skins/digits_4.png
similarity index 100%
rename from assets/skins/default_4.png
rename to assets/skins/digits_4.png
diff --git a/assets/skins/default_5.png b/assets/skins/digits_5.png
similarity index 100%
rename from assets/skins/default_5.png
rename to assets/skins/digits_5.png
diff --git a/assets/skins/default_6.png b/assets/skins/digits_6.png
similarity index 100%
rename from assets/skins/default_6.png
rename to assets/skins/digits_6.png
diff --git a/assets/skins/default_7.png b/assets/skins/digits_7.png
similarity index 100%
rename from assets/skins/default_7.png
rename to assets/skins/digits_7.png
diff --git a/assets/skins/default_8.png b/assets/skins/digits_8.png
similarity index 100%
rename from assets/skins/default_8.png
rename to assets/skins/digits_8.png
diff --git a/assets/skins/default_9.png b/assets/skins/digits_9.png
similarity index 100%
rename from assets/skins/default_9.png
rename to assets/skins/digits_9.png
diff --git a/assets/translations/en.json b/assets/translations/en.json
new file mode 100644
index 0000000000000000000000000000000000000000..a815023fbd03632f2baeabd9374552df05b6d5a1
--- /dev/null
+++ b/assets/translations/en.json
@@ -0,0 +1,3 @@
+{
+  "app_name": "Sudoku"
+}
diff --git a/assets/translations/fr.json b/assets/translations/fr.json
new file mode 100644
index 0000000000000000000000000000000000000000..a815023fbd03632f2baeabd9374552df05b6d5a1
--- /dev/null
+++ b/assets/translations/fr.json
@@ -0,0 +1,3 @@
+{
+  "app_name": "Sudoku"
+}
diff --git a/fastlane/metadata/android/en-US/changelogs/68.txt b/fastlane/metadata/android/en-US/changelogs/68.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d0bff939e5ed989737fa03356b1a04b9996d46b8
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/68.txt
@@ -0,0 +1 @@
+Global improve application design.
diff --git a/fastlane/metadata/android/fr-FR/changelogs/68.txt b/fastlane/metadata/android/fr-FR/changelogs/68.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b11bb3eba14ffff1b9e74b0d019957f5c8e6ce6f
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/68.txt
@@ -0,0 +1 @@
+Amélioration globale de la conception de l'application.
diff --git a/icons/build_game_icons.sh b/icons/build_game_icons.sh
index 4f0f02f7db8b7dc424ca226e225d7e7ef8899b2a..03cd74fc54841271f1cb1f54dfa5c00e75a2f8f6 100755
--- a/icons/build_game_icons.sh
+++ b/icons/build_game_icons.sh
@@ -18,8 +18,6 @@ ICON_SIZE=192
 AVAILABLE_GAME_IMAGES="
   button_back
   button_start
-  button_resume_game
-  button_delete_saved_game
   button_help
   button_show_conflicts
   game_win
@@ -27,15 +25,9 @@ AVAILABLE_GAME_IMAGES="
   cell_empty
 "
 
-# Settings images
-AVAILABLES_GAME_SETTINGS="
-  level:easy,medium,hard,nightmare
-  size:2x2,3x2,3x3,4x4
-"
-
 # Skins
 AVAILABLE_SKINS="
-  default
+  digits
   food
   monsters
   nature
@@ -103,19 +95,6 @@ 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"
 
@@ -145,12 +124,6 @@ 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
diff --git a/icons/button_delete_saved_game.svg b/icons/button_delete_saved_game.svg
deleted file mode 100644
index ac7eefef476f761903fe781b8c86d0c94323550a..0000000000000000000000000000000000000000
--- a/icons/button_delete_saved_game.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 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#ee7d49" stroke="#fff" stroke-width=".238"/><path d="m61.07 35.601-1.7399 27.837c-0.13442 2.1535-1.9205 3.8312-4.0781 3.8312h-16.84c-2.1576 0-3.9437-1.6777-4.0781-3.8312l-1.7399-27.837h-2.6176c-0.84621 0-1.5323-0.68613-1.5323-1.5323 0-0.84655 0.68613-1.5323 1.5323-1.5323h33.711c0.84621 0 1.5323 0.68578 1.5323 1.5323 0 0.84621-0.68613 1.5323-1.5323 1.5323zm-3.2617 0h-21.953l1.4715 26.674c0.05985 1.0829 0.95531 1.9305 2.0403 1.9305h14.929c1.085 0 1.9804-0.84757 2.0403-1.9305zm-10.977 3.0647c0.78977 0 1.4301 0.6403 1.4301 1.4301v19.614c0 0.78977-0.6403 1.4301-1.4301 1.4301s-1.4301-0.6403-1.4301-1.4301v-19.614c0-0.78977 0.6403-1.4301 1.4301-1.4301zm-6.1293 0c0.80004 0 1.4588 0.62935 1.495 1.4286l0.89647 19.719c0.03182 0.70016-0.50998 1.2933-1.2101 1.3255-0.01915 7.02e-4 -0.03831 1e-3 -0.05781 1e-3 -0.74462 0-1.3596-0.58215-1.4003-1.3261l-1.0757-19.719c-0.0407-0.74701 0.53188-1.3852 1.2786-1.4259 0.02462-0.0014 0.04926-2e-3 0.07388-2e-3zm12.259 0c0.74804 0 1.3541 0.60609 1.3541 1.3541 0 0.02462-3.28e-4 0.04926-0.0017 0.07388l-1.0703 19.618c-0.04379 0.80106-0.70597 1.4281-1.5081 1.4281-0.74804 0-1.3541-0.60609-1.3541-1.3541 0-0.02462 3.49e-4 -0.04925 0.0017-0.07388l1.0703-19.618c0.04379-0.80106 0.70597-1.4281 1.5081-1.4281zm-10.216-12.259h8.1728c2.2567 0 4.086 1.8293 4.086 4.086v2.0433h-16.344v-2.0433c0-2.2567 1.8293-4.086 4.086-4.086zm0.20453 3.0647c-0.67725 0-1.2259 0.54863-1.2259 1.2259v1.8388h10.215v-1.8388c0-0.67725-0.54863-1.2259-1.2259-1.2259z" fill="#fff" fill-rule="evenodd" stroke="#bd4812" stroke-width=".75383"/></svg>
diff --git a/icons/button_resume_game.svg b/icons/button_resume_game.svg
deleted file mode 100644
index 6ad8b64202d0e70f898c16c520e756fe8a934add..0000000000000000000000000000000000000000
--- a/icons/button_resume_game.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 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#49a1ee" stroke="#fff" stroke-width=".238"/><path d="m39.211 31.236c-0.84086-0.84489-2.9911-0.84489-2.9911 0v34.329c0 0.84594 2.1554 0.84594 2.9993 0l28.178-15.637c0.84392-0.84086 0.85812-2.2091 0.01623-3.053z" fill="#fefeff" stroke="#105ca1" stroke-linecap="round" stroke-linejoin="round" stroke-width="6.1726"/><path d="m40.355 33.714c-0.71948-0.72294-2.5594-0.72294-2.5594 0v29.373c0 0.72383 1.8442 0.72383 2.5663 0l24.11-13.38c0.7221-0.71948 0.73426-1.8902 0.01389-2.6124z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.225"/><path d="m28.369 66.919v-37.591" fill="#105ca2" stroke="#105ca2" stroke-linecap="round" stroke-width="4.0337"/></svg>
diff --git a/icons/level_easy.svg b/icons/level_easy.svg
deleted file mode 100644
index 30048ce976f10fba1ad4233115035786a65d35de..0000000000000000000000000000000000000000
--- 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 976249e8b0d2274b791d00e128be518d29a03731..0000000000000000000000000000000000000000
--- 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 e70fd60d179b05a0db5701e4b7858c214887c6be..0000000000000000000000000000000000000000
--- 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 87f28a3defc23c6fe95980cb376efbf56d6885cc..0000000000000000000000000000000000000000
--- 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_2x2.svg b/icons/size_2x2.svg
deleted file mode 100644
index 1959de3b39b5df72eb27801268ffc22f6fd271ae..0000000000000000000000000000000000000000
--- a/icons/size_2x2.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(-5e-4 -17.966)"><rect x="27.579" y="45.549" width="43.928" height="43.924" fill="#fff"/><path d="m22.09 40.056v55.821h55.821v-55.821zm49.417 49.417h-19.218v-19.218h19.218zm-0.0022-24.706h-19.217v-19.218h19.218zm-43.926-19.218h19.218v19.218h-19.218zm0 24.71h19.218v19.218h-19.218z" stroke-width="2.7453"/></g></svg>
diff --git a/icons/size_3x2.svg b/icons/size_3x2.svg
deleted file mode 100644
index fb37d7647416ad4bfff7cc6b0084a6c57f87ca93..0000000000000000000000000000000000000000
--- a/icons/size_3x2.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(0 -18.024)"><rect x="19.174" y="49.114" width="60.385" height="36.983" fill="#fff"/><path d="m14.151 44.442v47.163h71.698v-47.163zm65.408 4.6759v15.58h-16.268v-15.58zm0 21.399v15.58h-16.268v-15.58zm-22.057-21.4v15.58h-16.268v-15.58zm0 21.399v15.58h-16.268v-15.58zm-38.328-21.402h16.268v15.58h-16.268zm0 21.399h16.268v15.58h-16.268z" stroke-width="2.422"/></g></svg>
diff --git a/icons/size_3x3.svg b/icons/size_3x3.svg
deleted file mode 100644
index 6abc920ef7ea59ddfda5956cfc784fb395952926..0000000000000000000000000000000000000000
--- a/icons/size_3x3.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(0 -19.83)"><rect x="25.631" y="45.406" width="48.427" height="48.427" fill="#fefdfd"/><path d="m21.909 41.68v56.301h56.182v-56.301zm35.388 20.489v14.901h-14.901v-14.901zm-14.901-3.7252v-13.038h14.901v13.038zm31.662 35.389h-13.038v-13.038h13.038zm0-16.762h-13.038v-14.901h13.038zm-16.762 3.7252v13.038h-14.901v-13.038zm16.762-22.352h-13.038v-13.038h13.038zm-48.427-13.038h13.038v13.038h-13.038zm0 16.762h13.038v14.901h-13.038zm0 18.627h13.038v13.038h-13.038z" stroke-width="1.8627"/></g></svg>
diff --git a/icons/size_4x4.svg b/icons/size_4x4.svg
deleted file mode 100644
index 39c8e92ebdfd0f972c1204485df68cbb89a9e14a..0000000000000000000000000000000000000000
--- a/icons/size_4x4.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="#6041b0" stroke="#000" stroke-width="2"/>
-  <g transform="translate(0 -18.82)"><rect x="25.882" y="43.926" width="48" height="49.184" fill="#fff"/><path d="m23.09 41.131v55.377h53.819v-55.377zm25.359 15.368v11.176h-9.9925v-11.176zm-9.9925-2.794v-9.7789h9.9925v9.7789zm35.426 39.405h-9.7789v-9.7789h9.7789zm0-12.862h-9.7789v-9.7789h9.7789zm-12.862 0h-9.7789v-9.7789h9.7789zm0 3.0824v9.7789h-9.7789v-9.7789zm12.862-15.655h-9.7789v-11.176h9.7789zm-12.862 0h-9.7789v-11.176h9.7789zm-12.572 2.794v9.7789h-9.9925v-9.7789zm0 12.862v9.7789h-9.9925v-9.7789zm25.434-39.405v9.7789h-9.7789v-9.7789zm-12.862 9.7789h-9.7789v-9.7789h9.7789zm-35.138-9.7789h9.7789v9.7789h-9.7789zm0 12.572h9.7789v11.176h-9.7789zm0 13.971h9.7789v9.7789h-9.7789zm0 12.862h9.7789v9.7789h-9.7789z" stroke-width="1.3971"/></g></svg>
diff --git a/icons/skin_default.svg b/icons/skin_digits.svg
similarity index 100%
rename from icons/skin_default.svg
rename to icons/skin_digits.svg
diff --git a/icons/skins/default/1.svg b/icons/skins/digits/1.svg
similarity index 100%
rename from icons/skins/default/1.svg
rename to icons/skins/digits/1.svg
diff --git a/icons/skins/default/10.svg b/icons/skins/digits/10.svg
similarity index 100%
rename from icons/skins/default/10.svg
rename to icons/skins/digits/10.svg
diff --git a/icons/skins/default/11.svg b/icons/skins/digits/11.svg
similarity index 100%
rename from icons/skins/default/11.svg
rename to icons/skins/digits/11.svg
diff --git a/icons/skins/default/12.svg b/icons/skins/digits/12.svg
similarity index 100%
rename from icons/skins/default/12.svg
rename to icons/skins/digits/12.svg
diff --git a/icons/skins/default/13.svg b/icons/skins/digits/13.svg
similarity index 100%
rename from icons/skins/default/13.svg
rename to icons/skins/digits/13.svg
diff --git a/icons/skins/default/14.svg b/icons/skins/digits/14.svg
similarity index 100%
rename from icons/skins/default/14.svg
rename to icons/skins/digits/14.svg
diff --git a/icons/skins/default/15.svg b/icons/skins/digits/15.svg
similarity index 100%
rename from icons/skins/default/15.svg
rename to icons/skins/digits/15.svg
diff --git a/icons/skins/default/16.svg b/icons/skins/digits/16.svg
similarity index 100%
rename from icons/skins/default/16.svg
rename to icons/skins/digits/16.svg
diff --git a/icons/skins/default/2.svg b/icons/skins/digits/2.svg
similarity index 100%
rename from icons/skins/default/2.svg
rename to icons/skins/digits/2.svg
diff --git a/icons/skins/default/3.svg b/icons/skins/digits/3.svg
similarity index 100%
rename from icons/skins/default/3.svg
rename to icons/skins/digits/3.svg
diff --git a/icons/skins/default/4.svg b/icons/skins/digits/4.svg
similarity index 100%
rename from icons/skins/default/4.svg
rename to icons/skins/digits/4.svg
diff --git a/icons/skins/default/5.svg b/icons/skins/digits/5.svg
similarity index 100%
rename from icons/skins/default/5.svg
rename to icons/skins/digits/5.svg
diff --git a/icons/skins/default/6.svg b/icons/skins/digits/6.svg
similarity index 100%
rename from icons/skins/default/6.svg
rename to icons/skins/digits/6.svg
diff --git a/icons/skins/default/7.svg b/icons/skins/digits/7.svg
similarity index 100%
rename from icons/skins/default/7.svg
rename to icons/skins/digits/7.svg
diff --git a/icons/skins/default/8.svg b/icons/skins/digits/8.svg
similarity index 100%
rename from icons/skins/default/8.svg
rename to icons/skins/digits/8.svg
diff --git a/icons/skins/default/9.svg b/icons/skins/digits/9.svg
similarity index 100%
rename from icons/skins/default/9.svg
rename to icons/skins/digits/9.svg
diff --git a/lib/assets/grids.dart b/lib/assets/grids.dart
new file mode 100644
index 0000000000000000000000000000000000000000..c3cea19cbf5228a857f8031578c49d9d7beea3af
--- /dev/null
+++ b/lib/assets/grids.dart
@@ -0,0 +1,355 @@
+class SudokuGrids {
+  static const Map<String, Map<String, List<String>>> templates = {
+    '2x2': {
+      'easy': [
+        '4020204332141032',
+        '0302043100133124',
+        '3240412303000432',
+        '4231014200230304',
+        '0310012332001032',
+        '2300143041200201',
+        '2401310042130020',
+        '3040142020044302',
+        '4031304020131004',
+        '4231030401400402',
+        '4302120004210103',
+        '0000421324010102',
+        '0010310042301320',
+        '0302420021340400',
+        '2400132002030102',
+        '3204002023004032',
+        '4013310010420001',
+        '4132030102001003',
+        '0000302110404210',
+        '0001300002131302'
+      ],
+      'medium': [
+        '2403000000201000',
+        '0000001004323000',
+        '0000103000203004',
+        '0000201010030040',
+        '0001403000130000',
+        '0002030101030000',
+        '0003004041002000',
+        '0003204030000400',
+        '0004003003022000',
+        '0004100040200200',
+        '0004402010000200',
+        '0021000040002030',
+        '0024000030000403',
+        '0024000030004100',
+        '0030010000000314',
+        '0032000003404000',
+        '0102001000204000',
+        '0140000003041000',
+        '0203000101004000',
+        '0300120000020040'
+      ],
+      'hard': [
+        '1000040300200000',
+        '1000200000020003',
+        '1000300200040000',
+        '1000400000200010',
+        '1000400100000020',
+        '1400000030200000',
+        '2000001000403000',
+        '2000400100400000',
+        '2000400300000040',
+        '2030002000000100',
+        '2100000000300200',
+        '3000002000001040',
+        '3002000000140000',
+        '3002040000030000',
+        '3100000000000204',
+        '4000000000302010',
+        '4000000020010003',
+        '4000000200300300',
+        '4000300200000030',
+        '4000300400000020'
+      ],
+      'nightmare': [
+        '1000200300040000',
+        '1002000300004000',
+        '1004000200003000',
+        '1400000000002030',
+        '2000100200000030',
+        '2000104000300000',
+        '2001030000020000',
+        '2010000000000403',
+        '2010000043000000',
+        '2030000000004001',
+        '2300000010000002',
+        '3000002000040100',
+        '3001000000002400',
+        '3002040000000003',
+        '3020000000000403',
+        '4000003000101000',
+        '4000031000300000',
+        '4000204000000003',
+        '4002000000000103',
+        '4300000000200400'
+      ]
+    },
+    '3x2': {
+      'easy': [
+        '425361136520241053003142350016610230',
+        '123405506012002530354126230641461050',
+        '215460640001050612160350501246426035',
+        '143560526041410023362054201030034010',
+        '314065056314461023500106030051005632',
+        '620000305106504362263014136005052631',
+        '015204064315400051106003523040041532',
+        '020306346152050010614200061403432061',
+        '065400342000534201620304206143403605',
+        '305010421635106503530401650002204006',
+        '342016106003403650020301064100531064',
+        '413625265300120403050010002164600502',
+        '512406300512200054030620154200603140',
+        '000040540231461050230164154020600015',
+        '040203032045050026261534600052420001',
+        '160052020060536010240035050320312540',
+        '210036630050542603063024300145400060',
+        '241530500004050312002405400253025601',
+        '301500050040432600610230560123123400',
+        '614205003016040301136542400003060054'
+      ],
+      'medium': [
+        '050000000251001300040102410500300004',
+        '460001005042000153000000530004620000',
+        '000000500063040256060000423005100002',
+        '004600000000102300506420050060021500',
+        '005000041000500120210503400005003002',
+        '005000300004000006003020002015150243',
+        '120000540000250064600100000020002015',
+        '200010400600001520500160000040040206',
+        '450300300240020000040502200006600020',
+        '504000310400043050100000030506060001',
+        '002006000502325400140300050000200004',
+        '600013000050000100025060060230201500',
+        '000040100523020000600050002100301002',
+        '000560005040000230600015530000410000',
+        '040035000106520000300004062000405000',
+        '050010000003104300020060005100001230',
+        '056002200005002001400050000600023010',
+        '060000010062026310050006005400000003',
+        '461000000001010520602000003260000100',
+        '500040146035450000001000005000030100'
+      ],
+      'hard': [
+        '060210050300003450000000020100001000',
+        '104020002000000200013050500301000000',
+        '210000004300020540000001060000001400',
+        '300260500000002006000504000001010040',
+        '400000001640000002500034100000003010',
+        '400610200340004001000200050000300000',
+        '500100000260000004052000000030100450',
+        '502000000000004360000005030240006050',
+        '600001042006000000060003000500001604',
+        '602000000004000163010000501400000001',
+        '000000602000000005401300300621000030',
+        '000006100004020030000061030102005000',
+        '000020000615150000003000046003000400',
+        '000046000000206004003000100032000601',
+        '000400000003000006410500003650065000',
+        '000500050601003006040100004000300400',
+        '003000000602000001000250300005420003',
+        '006401000500000300100000020050001603',
+        '010000006000302400000000403100160003',
+        '010403000001040002000500300000065200'
+      ],
+      'nightmare': [
+        '000006020500050003100002300000500600',
+        '000060600500000040031000025000060003',
+        '001024000300630000000003000000065001',
+        '003000000005060010100402014000000001',
+        '004000063000600002000050001030200004',
+        '004001000600200050041030020000100000',
+        '006000040100002003600002000536000000',
+        '006100000030000600000040054300030050',
+        '030045020000006000003010500002000006',
+        '060000000503604000000002105030000001',
+        '060000102050000042000010005000000034',
+        '305004000010000200050000046050000002',
+        '400000001032000020000500030040500001',
+        '406300300100000000000513004006000000',
+        '502010000004000000010020030000100540',
+        '600000050000006004200005000150010060',
+        '600010003000065000000000200106000520',
+        '000000005002000000431000002040000305',
+        '003001020000400005060400600000001000',
+        '200016000000006000000002000500604300'
+      ]
+    },
+    '3x3': {
+      'easy': [
+        '751283946482769351936451827629074138045398672378610095217046583803025764564837210',
+        '560810042803764150010320876438671025256400731791502684379006518645187093182953467',
+        '054283906007915032239674001390167008745020169601459327923846715406501203518732694',
+        '840257069026039478000846302362510907700362540154098603235681794017920836680473215',
+        '281600947960271508070894012010946375496730081530128496629017853140082009750069124',
+        '710290485490517602260384970047650298829470100651928743986045307170809504504702809',
+        '070081395814035620953276814200158743045369081138704069060847152521000070487512030',
+        '401975026060128309289306175305009714790512083816030592143000968972063451650400237',
+        '100398465305104800890002371632700598009603710471985206253410907746039102918057643',
+        '548009210910426005007581394401963782826174950379850460105007600694218537000695108',
+        '010428009920653800480017623863079245594206730271534960700860300008042190102795486',
+        '459203017387415290000798054062307900731984600040020030674139082198572403523800179',
+        '700009816980016720651872409460208351025347608308165004006924187814050002279081043',
+        '400057210728031459500402670000389026800165934963720085317900560284006301609203847',
+        '050980043197643285048572196589307402000000010710068359435120900971835020820700031',
+        '410000050926570108350410729030045960895067402004032810271308604083094271049720583',
+        '597631004128907305600508179002059046480763002906200730735492618204806053000010427',
+        '020359174035187060791406800000534006600802401300761598268910347510600082073048610',
+        '028569470043180650507204018800400720090608031200703006480070195015942387079805264',
+        '095007641640589020073006859104203590307095006580614702950060070418730960736001084'
+      ],
+      'medium': [
+        '043700080007085321000360700500890070080547263024610058405130890208074036369058010',
+        '060300010549000026071900548654130709093460050102570604425603870800050200907201405',
+        '304008765859000300627453000793800002000100430146325007275030640038060001401580973',
+        '475631002016940300903870014080106740040003006702584139004009078090008401608417900',
+        '020196030790080206306702100070430028060020301083671050807300060012057093935264010',
+        '280315009360070500540869320070000031026138975138050200700500692650001700093720004',
+        '574023098239080064106947003860204031000000605340006280412309050000062010093805470',
+        '602503010030740080417086003854012006096070501020605000279008164040160278008427005',
+        '703004058090065047840070190671509800000406725254300619026908401010040563000600902',
+        '920451030045026079061080045170208400294500600008040912412070308050830001703160090',
+        '014095070020140050635280140700514900000903867093608500002439785300050002070860430',
+        '207006158891574620000000700028030471053420900900081205002040569039100047486700300',
+        '304027095501043200200000010892070140615400720000281956006010472708002501123050800',
+        '320608047700420010060197000573001008000530071416089020008302000297815364630970002',
+        '905624083000038070320001406457092001209500748100070009092147360736000910510360000',
+        '147080526030070000980254071300460907004013602008705010462897005859000004700046200',
+        '470308605950260304003050000200005070005603142300192506530000000104586709086034251',
+        '804250931090830046170040582020080400000702059300094020010520094508610003002408165',
+        '843106057126050400700004060580600721001205094279010036300001005000902018918503600',
+        '946010350001000002730040186250481093090700020604030875028000500105608234060023708'
+      ],
+      'hard': [
+        '000050000020190070036740000060400800503008704004009100601900230209007600008020917',
+        '504700003810093200023061800701026009002300008380000501000030000049100050208000090',
+        '006300809780590023003008670172849365000050000050000007000080730000205010000060090',
+        '050040107900817605071600900000060700000080003300002500020401006607200409090036800',
+        '070408000001000900836010020007002830100500762000607100760000210508701003003000079',
+        '083009001651000008700518000007986100860000429019000006500040000906053010000007300',
+        '200568703009173004003200000907000512000005400032700800080000001300600240720410000',
+        '370280106009700020000000073068070000590430060700010490850090000930108507046000000',
+        '509040078401600020700005600070000460180070500056412300042000800890000003005008700',
+        '635000010017030040804000000963100200000042090040000871001053960000007400006429030',
+        '470350009000008247809470500000000726000926850000700000708000105305810902000000070',
+        '600905030200067800097400020024050001000080950809010003000096002480300019030040700',
+        '140000083027005140600000900700236051403050069000014000000700002000090610094000530',
+        '700520400001708000060000100050000678000480210608005000000130894300040000149057002',
+        '907000060620000501043069008070004000090057000400908070350000010060091052100006904',
+        '513740600020160005000080300700000000458900026000054097004000060082006500000400738',
+        '000000200000300081200080396071020038090400107000003509000034000807209000023750810',
+        '000782030214305090000109500058201000439600000007000600300410007900000403700003002',
+        '003900000047080000956002378000065014800730500005200000000129000090070420021300060',
+        '005093100690080740128060000000008000061400050709600010904007020080002007050000481'
+      ],
+      'nightmare': [
+        '058001093200030004000200071905420000000007050000100000080304060703900005006810700',
+        '060140000720000000040875060070000493092007800004050000000090740000060025058004001',
+        '068070000003004000000180370900407601000901007000030040009043108057000000030602090',
+        '005208010000000809128690004000020000006900430009040208080014000004002700000009600',
+        '009007400030600000000485000200030107000070020170204600546700092800000040010000050',
+        '020500603000300004009024750050200006008000200012603000034000000507000001280900070',
+        '090304005000200490050086302300009250900000060070050130063000710020000500010000000',
+        '390007801508360020040950607000000000800003910700040005000030700010000086000200040',
+        '012034050000009640030600000784001300003080060000300078000002030005006000068000907',
+        '000000000050078200000590870600200100007004500004003008500000723040000006276030401',
+        '002000008070040031500890007009200000000503040040000150610000000000368700780150003',
+        '800000650300000080005007002108790400600050100052040060003000200000019000500003748',
+        '800004305000000790001000000010805000090100000200600184700200041402006070000037026',
+        '000000012240601009000300050600100020000700003320040605460008001900000008000036900',
+        '000409003750060040000000206000031079000000832500702600400007000000314060006000001',
+        '000000500009106430060090000008000050004000982300980007000035610017000090000000270',
+        '073002600200105008000000000729306100040000900000008004000003000007560001098200006',
+        '800003000050001079006079003000200001900700060000010305032090506000002000087000040',
+        '000800000039605400008000603005390000700006000380170004002900100004020306010000000',
+        '200000090500206700816700003400100000000050030900084520000000807000620050000000062'
+      ]
+    },
+    '4x4': {
+      'easy': [
+        '00B6483AE20FDC91GA8F0E659D3172B42D9CGF71406B538E03E42B9D8C57GFA6C2G06184DFEA9B7596105GE724C3FAD8A45DF32B6709E1CGEF78A9DCB01G64327860BC423GAE19FD30AED51F7040CG2BBGF13000C9D20507DC02E7G9F1B508634EC9825613FDB7GA81D79AFG00263E4CFB3G1D00A0042659650A74B30E9C8D1F',
+        'G8D13540972EFB0A3BC980EDA61457G2AF5721B683CGED0404267AG9DBF53018C7381BAFEGD92645BGA40EC7056018F95DF098647CA1GE3B916E532008BFC07D4EGA091BC20D0F8385B00732G906D4C16C1D0G00B473A92E297300DEF18060B0F38CG09A5047B1E602E5B6831A9C40DF064BED050FG093ACDA9G40F16E3B8257',
+        '48F0071693BC5DEA613CD5E80G0AB42029EA3C4B8051F0600D5B9F0G0426831C0C057634129E0B0G9218GB05F0A0E6430060E82C4530D9F10E431AF9D86GC75213865907GFE24ACD0000C1G36A842590C000FD6A5003700E059F4E82CB7D0106E40D625107G0003B3B790GCD014F6EA05601B47F3EC9G2D88FG2A09EB0D51C74',
+        '0381D0GA9FB06C20G0600842E03CFD50EFC5003BD42A1070A24D0F05800G3B9E00B30CFG19AE24D6800C041070F0G9EB4E0F00B902G8C531091G2DE35C4B708F0030A28CG617DEF0DC780B5F2E94AG130006E0D70A83024C2GAE439005D006B71D0230C40BE690A894F7G16EA852B0CDC6EAB9203D714FG5380BF7AD0G09E162',
+        '1DEF0A5C47209B865B7948D20E003AG000000763D0005E4F6003BE0085C0120D01026500E3D480C0030D841EC0F9002B8F9C020BAG15E364E64G093002B8AD51B0D8A3460157FC9GCGF19D853406200A40362FC7BA9GD01E92A0EGB1FC8D6430D80B7194GF3EC5A2GAC406285D700003F9253BEG680041D730105CAD9B42G608',
+        'G9D05382CEA06BF445EC19D0G06F0780F08070A0451D009EA700G4FE9B830C0D6C159B40AFG0D8E783B020107000FA0020FDA00059C4B163740AED6008300905C0A0F694D0EG851BE64080201C5B73G95130C0G7FA904D20DG9831B52046CEAF1D700208E40A960C382E00C96D75AFB1BFC407E031295GD89A56DF018GBCE472',
+        '0DC63B47F058G20AF0102E06DBAGC30032A009FD06E048500GE8005A3490DB6F2AFCE0005G01B0085047AGCFB230169E1E905003008A0700B63G1D78E9F45A2CAF736000C5198GDB600298DGA740E0F300B9FA3008DE05160C8E7500036F29A44900D3A00EB561C78705BFE90A0600G2E3008C600DG790B0C00147009F23A08D',
+        'AE3G6B980120DF0C0C007040900D06EA97185D0F00642BG3460523CAE0B0098004500F120EC798A6F06AD00E0G980254C829B076F403GD0E70E100A96052C3B0D04007GC50E60A910F8796B14A3CE5D2EA0600000710B4000193EA8020DF6C702DGC0057864EF13B15B0CEF0327A8G09037F0820D9G100C589AE106305F04720',
+        '08030C4B0D102A096F940E0580A203071A000F023G4B8C000B000A300F0E0645EC7B24509068F03DA08GB7CF03D402E0D26918A3G0EFB40000F00D9EB00C08GA0105C3D968FAEG727EAC06B020034D9036D2A5F4E9700B0189GFE20004BDA0635GC600EAD2893714FD3A518C4E07692B900E4G20A631500804180076F0C50E0G',
+        '0900FGE8071A24606G502D9B00CF73A10F17A0408D23G0B02400510000008E0FGBFAE2C0D1050843C8040AF630001000769ED5310080F2GAD03187040F60C09EA10G30020507D084534D0E0G20009AF682CF04DA0E00050097B6C85F030DE12GFCG9400E7AD856024A63GC2DF901B7E80D726F000C4E0G00BE8519A06230400D',
+        'D0B09E8G203610C4716GF024B590380D00980D0CE0FG0075ECA205B7400060G9A00EB0060GD00407002D005FC6478B30G45602030EB0C0D0073B48ED02A9F05G0G000C0ED47093868D07003A91EFG50B02437098GC5BE0AF0EF5G4D0380A712C56E103A90F0007B000D080016B205G933000D06279C5A0F12B79EFG5A3104C68',
+        '0E000648F0C1D352000F02GD00B507016000F000D340G0BE0000C105A090F846930C671B2AGD0F850A8004F2EC03961B00D1G8AC0564E200702459030F18CGA0G4530EB0C0D7092F8DB0430051290AG00F090C2G00AB5DE727CA0D0680FGB000E9060F81GB30250AD1A030C9420F7E680G7025640DE03BF935F2AB0E79801400',
+        '2BD7G0F0C0010590A419E67B50F3G2CD8305D00C7400BE10CF0641020B00A3780030AF0G490DE020E904B706F0008GD0FD205000G0081B07B68G300D007A49050548706F3E0BCDG0DEBA180527GC3F46706FC0E3A004918B90C000D0168F57EA0A0BFD006C1970303C0295B080470A0E007080000F3E2C59089E6037D2A0F4B1',
+        '032E0B061AC89DG4G0400C012B5930006B80290GE04D51C090C5084D0G0F002BADE090020613C8054G685FAE02BC07D300000DG4F50E0B000259601008D4000018A4G07FC936E20DE60214905D07GCAF5F0G0203A400867909D0E0680F2G4531000C008A07050002207601398EG0D4FAD0GB462501FA79008A0100D70C62B050',
+        'FGE427ACB09056D15C72BDE9G416A00000B6F301720EG490091064000C0D27EB010CA00240GB3079089A0070053162G06E07810B0D29000F20FGE50307001CBD8B20000006D4931A03A19000EB007D0540C01E0A39780G629760003520AG0EC0E54072B09A60D13GC600G91F8302E05770G930CD1F05002000835000DGB7C0F6',
+        '008A3259016FG0D4CD5070063E9A028134960G0807D00FE00F20D0BE0G08960061EFA54G0372D8C9G3ABFC20608901705040E890AF1D030G0970B163ECG0000006C809021030FE0A0010C03A042G7050F0G0500189AEC040A0B0GE8F7DC6291300D003AC48000590903C00E5G60184AD000590G400E31C0B0A0E801B905C00F0',
+        '16G20E8C50A00F490FD03190G70405007E390D450C00A2G8C040A6F028091D7E008G07C9A0406B53B0564GE0D00098000A20001B69750G004C006250EGB0D100A20FC9G6850BE431G8C12B7D009006FA000005AEF0G07082596EF400721000DBDG0000600E210A0C9500GFB70386001D60089A21CD50F0B0031BE0049AFG8705',
+        '247301050B000FDCCD800GBE450F10066F0007C908GD003EG90E00400C360080AC541E9B00D708FG90G27A0061080BC0D0300C800A5G00E701F7G600E40B30000B08092GF000CD64106FEB0A8D4C000052ED0413090AF0704309F006G200AEB1FA90000C57B4830DE8DC05A03G92704F302140F0C0AE0009B04693G7DF81EC2A',
+        'A064E087DFC3B29190080A0CEB106400G00093D460700F0AF00E006000A0D85758BC64F0A0E900G004G7B1A3F856C92E13900075B40D06AFE6FA800D312C754BB056A010C00G3D742A0G30BF0E009C6881E90020000F00B50703D590060000E2C00D263A40075B0039050B4800DEA0F0700F0D01906B00800G400FC01A852ED3',
+        '00B0070800E605FD800000G1039D0C0200D5FB49000C600130G9C05DF0B0870E0286B09FE5G731C40701AC649D8300009DC4G870100006500F0B1205006008975G02D31C04FA097019FC0EBA27DG5308030009F0B6C01DEG7B0D048G0E19CA2F0C0G70AEDB302F10059E41320000GB8CB0170006G20000D30A2380DB00407060'
+      ],
+      'medium': [
+        '00B6483AE20FDC91GA8F0E659D3172B42D9CGF71406B538E03E42B9D8C57GFA6C2G06184DFEA9B7596105GE724C3FAD8A45DF32B6709E1CGEF78A9DCB01G64327860BC423GAE19FD30AED51F7040CG2BBGF13000C9D20507DC02E7G9F1B508634EC9825613FDB7GA81D79AFG00263E4CFB3G1D00A0042659650A74B30E9C8D1F',
+        'G8D13540972EFB0A3BC980EDA61457G2AF5721B683CGED0404267AG9DBF53018C7381BAFEGD92645BGA40EC7056018F95DF098647CA1GE3B916E532008BFC07D4EGA091BC20D0F8385B00732G906D4C16C1D0G00B473A92E297300DEF18060B0F38CG09A5047B1E602E5B6831A9C40DF064BED050FG093ACDA9G40F16E3B8257',
+        '48F0071693BC5DEA613CD5E80G0AB42029EA3C4B8051F0600D5B9F0G0426831C0C057634129E0B0G9218GB05F0A0E6430060E82C4530D9F10E431AF9D86GC75213865907GFE24ACD0000C1G36A842590C000FD6A5003700E059F4E82CB7D0106E40D625107G0003B3B790GCD014F6EA05601B47F3EC9G2D88FG2A09EB0D51C74',
+        '0381D0GA9FB06C20G0600842E03CFD50EFC5003BD42A1070A24D0F05800G3B9E00B30CFG19AE24D6800C041070F0G9EB4E0F00B902G8C531091G2DE35C4B708F0030A28CG617DEF0DC780B5F2E94AG130006E0D70A83024C2GAE439005D006B71D0230C40BE690A894F7G16EA852B0CDC6EAB9203D714FG5380BF7AD0G09E162',
+        '1DEF0A5C47209B865B7948D20E003AG000000763D0005E4F6003BE0085C0120D01026500E3D480C0030D841EC0F9002B8F9C020BAG15E364E64G093002B8AD51B0D8A3460157FC9GCGF19D853406200A40362FC7BA9GD01E92A0EGB1FC8D6430D80B7194GF3EC5A2GAC406285D700003F9253BEG680041D730105CAD9B42G608',
+        'G9D05382CEA06BF445EC19D0G06F0780F08070A0451D009EA700G4FE9B830C0D6C159B40AFG0D8E783B020107000FA0020FDA00059C4B163740AED6008300905C0A0F694D0EG851BE64080201C5B73G95130C0G7FA904D20DG9831B52046CEAF1D700208E40A960C382E00C96D75AFB1BFC407E031295GD89A56DF018GBCE472',
+        '0DC63B47F058G20AF0102E06DBAGC30032A009FD06E048500GE8005A3490DB6F2AFCE0005G01B0085047AGCFB230169E1E905003008A0700B63G1D78E9F45A2CAF736000C5198GDB600298DGA740E0F300B9FA3008DE05160C8E7500036F29A44900D3A00EB561C78705BFE90A0600G2E3008C600DG790B0C00147009F23A08D',
+        'AE3G6B980120DF0C0C007040900D06EA97185D0F00642BG3460523CAE0B0098004500F120EC798A6F06AD00E0G980254C829B076F403GD0E70E100A96052C3B0D04007GC50E60A910F8796B14A3CE5D2EA0600000710B4000193EA8020DF6C702DGC0057864EF13B15B0CEF0327A8G09037F0820D9G100C589AE106305F04720',
+        '08030C4B0D102A096F940E0580A203071A000F023G4B8C000B000A300F0E0645EC7B24509068F03DA08GB7CF03D402E0D26918A3G0EFB40000F00D9EB00C08GA0105C3D968FAEG727EAC06B020034D9036D2A5F4E9700B0189GFE20004BDA0635GC600EAD2893714FD3A518C4E07692B900E4G20A631500804180076F0C50E0G',
+        '0900FGE8071A24606G502D9B00CF73A10F17A0408D23G0B02400510000008E0FGBFAE2C0D1050843C8040AF630001000769ED5310080F2GAD03187040F60C09EA10G30020507D084534D0E0G20009AF682CF04DA0E00050097B6C85F030DE12GFCG9400E7AD856024A63GC2DF901B7E80D726F000C4E0G00BE8519A06230400D',
+        'D0B09E8G203610C4716GF024B590380D00980D0CE0FG0075ECA205B7400060G9A00EB0060GD00407002D005FC6478B30G45602030EB0C0D0073B48ED02A9F05G0G000C0ED47093868D07003A91EFG50B02437098GC5BE0AF0EF5G4D0380A712C56E103A90F0007B000D080016B205G933000D06279C5A0F12B79EFG5A3104C68'
+      ],
+      'hard': [
+        'B329800F0140D0A0G700E0290500B6316058G01D2F00C079010CB005G00928FE00BG000E00230D073DF009G00C08001480E002F107G0A39C0A1034DB695F00G27000A08C90010000000B0E325G871006AF010090D000GCE820GED1B60AF07903F0CD96E0701405B000801D50020G074004302008FD9561CG0G754FC030E09000',
+        'G000070A9008C00BD040C02B603GE9050200G3091004D8A000690105E0A0F0326002A0D050EC00FG8FA06017300D5C0E0050F9B3000A20063G905E000600B08D1030BC0D20F0GE602E00958FD00700C4900F060EBA8372015086027004009BDF00F0ED9000C1602046D01AF070928000B92G705CF30610EA01E02036A8D54FG0',
+        '00G05A0E300000009C0A0004G201705EF07E1000A6B50G40010870DF904CB00A2D6430G510CE000FAE0G0180042FC0D3CF05AB42000D6E017010E0FC000024B50000C00025E40D86G9C10F0B006045E250ED2038B1G7900C0A0200E0FCD93B1700A3G000CF0B0804000C80B0E00000908B90F0C100A0023D1G200300D856EC7B',
+        '08B007E90F03042D0040830G00D2E015950DAC0B08013600E0F750D09C04B08G8000007102009E6CD000BE300706080A00E000A639087001700A28900G4E003B60C0028E0590A0D03A8E95CF4D17600204090B1D0A3G8C0EF1000A632000570400600050G02000E04E920FG0C65D1BA0C0031628B4EA0F0010A5ED04007002C6',
+        '10F3000C0A600502A0C8F9B320106D70B0G0052D0EF70000D2004600C09B00E000B0000G9C00A6585C6A3840ED009B07009F6B00A15402D33GD2AF50B780E40C8F30009460BA100EG500B3000901082600A9G00000CEF00B000001F0G340D9A003007EDF14028A60900714365F0C2EBGF42G0A8B000057016A002CG08070000D',
+        'CF004000906020107428000900010600D0050000230E70409310800B7C4DEFG5514003BC00800G6F000079F0G4250A000ED76A8G00FC35B0G8A0E4000B03927039040502000AG0010DF10G98300467A06A0GD743000B080080020F0EC09G04D0F5BD30E040G9A000420C9BGFE0108D50A08E521607CF0B30179608040500FCEG',
+        '30400000070005000FC84050B000D970E0D003A04000F6GB0050E9B8CF0G010000B005E4800300909E36B100F02D075A000400000000E30170A090236E00C400276E001B5G349F8054002E3FD006G000ADFC000609B70E148003D479A10E52600C8G300596721A00F31A0B00G0E57C4606E010GC30AF2800D07260FE0840B035',
+        '0B0G017A00C080390390D240700BC0G01420000CGA900F5000CF0GB00328040ADA30000000G7000FB1560AF08D009740G040CD07250F00A079F0G004301A00D8C074B0D86E300190060BA0C900D150000010F70G00A548B6008A0E01B042000C80A1E06000B00DC702000C005000G084476520GD0C03F001FCBD48A31G0E2965',
+        '193580400A6DF200AEG010B0040758D68B0DF50C129G4007020706005008100AF60C7D2A050304E0750B010F4009030800025C90670E0D0F409AEB800C0F200568002A7000F1350GD009B3F000C46080B3040G00A002E00DG001000836D007C05C700010D9008A0E0006A802000C0F549AF80EC700500G0000DG30560E8A00B1',
+        '8076FGE9500DB1C0B2A934100000807004050B0C900A0306F000006A38BG940537600C82DB04G009090B0000760C0003D08049300E02670C0G04E6700980DA02000008G000091F5790070001BF650D48100000B0200700A00A0E00FD83C12B9G08900FC0G00B0260A650GD080700EC0B41B06200C50EA8DFC0DFBA53004800G1',
+        'C4D0FB83050200060G105000D009A70B89050070310600CDE000D0028CA0300001C2070D089A6400400000A90D0EB0006009004B00F38C000D0E6F0804C19G2A0800CD07900B56020F000805001D003C060DB020CE05F8099000E3GA4680D07105A4700F100C0EB8FC080254E0D7136G070G81BEA3040F95000B309C0G580DA0',
+        '0000AE000B0264080960000070C01A00B3080074E1A0050D10F0DG006895B0E70G0BC13D2F007605000086EB045AC2008C0300451009FD0E00E5F9A70C0301000B4920C600000G73F60000095738000AE8GD070002460C907030E41809GC0B6F0F709360400BE0D004B0700E0A00300G000G1C020360A00B310C0A0F9GED0006',
+        '0G0BC01D72000405000860320GD0BE7C0100G500AF00093D407D0EF830C01200F0070009GD010080004A80E0900F2D510200D0B0003ACG0F1C00020G8600009A001638705E0DG0B0083FE00C6B9G5A10A00G0B00F002360427B50DG6000C980E0A049F0E01G30500700E006A00004BC380920G00B0F0D1E6CD00730B040EA0G9',
+        '95AE0008GF7D000B80005200B4EC0FA323C00FD090600408B004GE1C30280D00600020CE008795F0020C0050003F00D0F0E70B0020153A400A9304076CDB80020008E104079A560G3B0FD0028146EC00A149000650BED83FE050B80F00000214C0F2000510A00G0E403A002G00000007G06089E07BF000C07E1B0003C0G20900',
+        'B1C047820003056900000903060B0E0C08030065D29C1GA79000FACD18G00043870CA5160E304FBGG20F74300A00C800A03BD0F864C090020001200B7G80A03000G000B05002000D09BD630A01E07080000050700CD6GA0B00E090D083B0005F0D4000A00000F0GE2F003D57G90EB000E0700000ADF139003GA68F9EC0205701',
+        'G0DA30004010085004080000305000E0002085E060A94BG00390704B8000A26DEB0900F003006A08A8F50B040090D301207DC090000850BE063C005D1GE09F720500B20AE14D398619034EC0062A000BD00EG300B07F010507A6D00F003CE00G7064000002B0000A0C1264AEF08G05070G00003750000C0F900F2G0C7ED08004',
+        '00700300A0DC000F960C85EF04G230A03A0109D200070000800GC00A0F530ED00036700190F00040008F040G75EA9D36000E0B39002D7C00270008F043C051BE00000000F000C0250D03027C0005B010C002010BE064807G14005080C2BG00E0F0C00000D00E6791794A0DB5001F00C0E2001F6750A000G36310EC940G78F05B',
+        '0F0B206D000085CA0170B00000280G00G80400EF05007000A60000G907D0130E40F0GC000D000E600C080BF6G04100720B0009000006040G63G07D40020A0005C0409287D0E00B13300654D0B002E0G0090560007384D0FC00B0FE10A90C5847000900002A3FG0088G0DC1240B0E0006FE3205900C1DA7B475613F0A080G20ED',
+        '4C1A0F090GBE3D570GD35000C08A000F0020CEBA041D8906E900000D05F0C00A0E897GF40200000024C009A800D50EF01BF0035097000602D0060B000E000894060430G0F0095A0080GE4A90DC00BF01000000E048A6237052010076B00004DC0100B00F5A0360E0700290DGE00000A500EF07612DGB40000D6B05030F741G00',
+        'G60B9F0DEC0A4508F10040200000DA004AC2580B0G000F900E0000C35F00020BE2B306A000975400000000004083EC2A1F003500006E000754002E00DA00B1366000009FBE02AD040DG06705C0000900380ED14A907G2BC0AB090200801D0050B5600C37F2001009D340B0G160E000020701E456A039F00000EAFD0000CB6305'
+      ],
+      'nightmare': [
+        '0000AE000B0264080960000070C01A00B3080074E1A0050D10F0DG006895B0E70G0BC13D2F007605000086EB045AC2008C0300451009FD0E00E5F9A70C0301000B4920C600000G73F60000095738000AE8GD070002460C907030E41809GC0B6F0F709360400BE0D004B0700E0A00300G000G1C020360A00B310C0A0F9GED0006',
+        '0G0BC01D72000405000860320GD0BE7C0100G500AF00093D407D0EF830C01200F0070009GD010080004A80E0900F2D510200D0B0003ACG0F1C00020G8600009A001638705E0DG0B0083FE00C6B9G5A10A00G0B00F002360427B50DG6000C980E0A049F0E01G30500700E006A00004BC380920G00B0F0D1E6CD00730B040EA0G9',
+        '95AE0008GF7D000B80005200B4EC0FA323C00FD090600408B004GE1C30280D00600020CE008795F0020C0050003F00D0F0E70B0020153A400A9304076CDB80020008E104079A560G3B0FD0028146EC00A149000650BED83FE050B80F00000214C0F2000510A00G0E403A002G00000007G06089E07BF000C07E1B0003C0G20900',
+        'B1C047820003056900000903060B0E0C08030065D29C1GA79000FACD18G00043870CA5160E304FBGG20F74300A00C800A03BD0F864C090020001200B7G80A03000G000B05002000D09BD630A01E07080000050700CD6GA0B00E090D083B0005F0D4000A00000F0GE2F003D57G90EB000E0700000ADF139003GA68F9EC0205701',
+        'G0DA30004010085004080000305000E0002085E060A94BG00390704B8000A26DEB0900F003006A08A8F50B040090D301207DC090000850BE063C005D1GE09F720500B20AE14D398619034EC0062A000BD00EG300B07F010507A6D00F003CE00G7064000002B0000A0C1264AEF08G05070G00003750000C0F900F2G0C7ED08004',
+        '00700300A0DC000F960C85EF04G230A03A0109D200070000800GC00A0F530ED00036700190F00040008F040G75EA9D36000E0B39002D7C00270008F043C051BE00000000F000C0250D03027C0005B010C002010BE064807G14005080C2BG00E0F0C00000D00E6791794A0DB5001F00C0E2001F6750A000G36310EC940G78F05B',
+        '0F0B206D000085CA0170B00000280G00G80400EF05007000A60000G907D0130E40F0GC000D000E600C080BF6G04100720B0009000006040G63G07D40020A0005C0409287D0E00B13300654D0B002E0G0090560007384D0FC00B0FE10A90C5847000900002A3FG0088G0DC1240B0E0006FE3205900C1DA7B475613F0A080G20ED',
+        '4C1A0F090GBE3D570GD35000C08A000F0020CEBA041D8906E900000D05F0C00A0E897GF40200000024C009A800D50EF01BF0035097000602D0060B000E000894060430G0F0095A0080GE4A90DC00BF01000000E048A6237052010076B00004DC0100B00F5A0360E0700290DGE00000A500EF07612DGB40000D6B05030F741G00',
+        'G60B9F0DEC0A4508F10040200000DA004AC2580B0G000F900E0000C35F00020BE2B306A000975400000000004083EC2A1F003500006E000754002E00DA00B1366000009FBE02AD040DG06705C0000900380ED14A907G2BC0AB090200801D0050B5600C37F2001009D340B0G160E000020701E456A039F00000EAFD0000CB6305',
+        'GB824DAC100300500400B2938050000E0951G700A20D000C00300F150G90B0020019000070600F30CFA7004BD00000003605A02E0008007040G0F6075000010D25EB046030090A0F0CF6E5B0GD80042790007AG0E00F6D01D07A03800B2000E000040C70B80EG610012G0EF003AC790B000D0B3109G5A2C08A0C00062071DEF3',
+        'G003C00820015006F00C5D04G09010A712003A70050C8BD90A0021E000B003FG30000007A00F008E05B0F02090863G7098A0B05310D00020D60E80003400F15BBD0906A0583EG000AG30004000000508000098004F0D07B20F00D0G00A100C9300F0E000B24A00CD70500201DG00083FE08D40B0FC702AG1000A070F806304E0',
+        '00ABFE5006009081FD7496G3000805206C528710F000D4E3918EAC0200306F0700DGB3600501F00C09B00F0100G00700163000000B0AG050450700C00080B60080C0E500091F00A0G01AC0300E6B400FDF0502B0G3A0C0987069G00F004C1B05040100000000000D000071284G0E500907004B0502D08100BA000D9010567200',
+        '70200805ECG1490D840G0920070D00000F51E00DB00602370D00C7A00040B5GE03A00D680B2E105990DE503081AF00CBB0F000000470E8D0008500000D000G0AE26FD07C000090B0000403E6A009C7200B70800006F030E5G093A0127000008409G841C06E0A53F02EB7F053081G040000C0GB000F020000F0002AD000000B10',
+        '00900DEF6210005A60CAB9420D5700E0000E0007000C40080075AC000F9E60001E80FG60040A507090GB20000300140CF0070AC498B1G020C020010DF0E0806020AF0E96B70G30D0760410D50AF9E000E300C0FG000800000G1C7B3000D290800900E000G53B0007B0000351700400000030G708200DC940G000D0A0108F2B0E',
+        '0105D082GCA0E007000063CB70D04008DEGC7F0430B850090700000509000F0D007810F02E0AG0CB00CDA70008G01062F00600BECD50800090008G000400DE0000000801E000000006B10CGD90800A040CD00003A671B09000FE9060B0CD71530002E03F87940CG100E00A0800FC90D00093000G5A00F076000F4597D0000200',
+        '2000DG601B097000F7000AB100050090DCE84925G30A000005007CF86E0D0G30G000135D0008C94F000620809FG000500A8F04000000E1670009060701E302AG0053C74G80960DF0000A0109005008000G028500BC0E9070B89D00A60030540C000008G00010F60006BE00040980G00082F0A000740010D040010F7CE5023A00',
+        '89506B0000700E0F00E003G861CF5DB00C1B570E4820G36A06G00001005A09470035BE00000G700CE8670G35B0000094B2D00AF000E5600G0004000276A000E00GA000860E4000730B00A00F0037400603080D002060905E5D46E070F90B00080481009G0700CB35000904B0C000E001250071E00004A08D000D00000208F000',
+        'ED0CG300820B01AF0000000065040C3003G1F0400D90607E608BA7C0FE0G0200A040000508F7006B3B0600F20409GDC0C52FD07000GA000801E00CBG53000700D8043600000F9G01003750G000A2E00C09C20080006E3A00G060000A3100740000000D082G060097000E40309F81A0G0970G2060D000800446100000E700D003',
+        '000003G0F040E56010E60A0D7800F00400009F0C010E003A0DF07000C60A08120E0C00BG5A0160496000000000008A000934500062EB1000G21BA0E60F9D7000D0G000030B02A170B0A008250G06040D00C0D10704F50EG05800B00000D00000F00A1002D3604B07030700DB1EA00086EB00G57F09C003A081000E00B0270D0C',
+        '1G05B289ADE0000608070500000300A003E04D00620G0B8062C0G0A3589B400000000E0G00450000047000003A6C0G00AD8003CB000204000C037000DB89A5008769DGB0C3A1FE05D02GF007800000C100140030B90D7AG8003C80250070B09000G008000CB00F00719004E62500G8DA0F0800G0760000000000105000D8C069'
+      ]
+    }
+  };
+}
diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart
new file mode 100644
index 0000000000000000000000000000000000000000..2f9ae753d2cd5ef02f1feafda79db96a84a9315b
--- /dev/null
+++ b/lib/config/default_game_settings.dart
@@ -0,0 +1,49 @@
+import 'package:sudoku/utils/tools.dart';
+
+class DefaultGameSettings {
+  static const String parameterCodeLevel = 'level';
+  static const String parameterCodeSize = 'size';
+
+  static const List<String> availableParameters = [
+    parameterCodeLevel,
+    parameterCodeSize,
+  ];
+
+  static const String levelValueEasy = 'easy';
+  static const String levelValueMedium = 'medium';
+  static const String levelValueHard = 'hard';
+  static const String levelValueNightmare = 'nightmare';
+
+  static const String defaultLevelValue = levelValueMedium;
+  static const List<String> allowedLevelValues = [
+    levelValueEasy,
+    levelValueMedium,
+    levelValueHard,
+    levelValueNightmare,
+  ];
+
+  static const String sizeValueTiny = '2x2';
+  static const String sizeValueSmall = '3x2';
+  static const String sizeValueStandard = '3x3';
+  static const String sizeValueLarge = '4x4';
+
+  static const String defaultSizeValue = sizeValueStandard;
+  static const List<String> allowedSizeValues = [
+    sizeValueTiny,
+    sizeValueSmall,
+    sizeValueStandard,
+    sizeValueLarge,
+  ];
+
+  static List<String> getAvailableValues(String parameterCode) {
+    switch (parameterCode) {
+      case parameterCodeLevel:
+        return DefaultGameSettings.allowedLevelValues;
+      case parameterCodeSize:
+        return DefaultGameSettings.allowedSizeValues;
+    }
+
+    printlog('Did not find any available value for game parameter "$parameterCode".');
+    return [];
+  }
+}
diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart
new file mode 100644
index 0000000000000000000000000000000000000000..401e3efd5333c35a48da480c6c4443ebb23d3277
--- /dev/null
+++ b/lib/config/default_global_settings.dart
@@ -0,0 +1,39 @@
+import 'package:sudoku/utils/tools.dart';
+
+class DefaultGlobalSettings {
+  static const String parameterCodeSkin = 'skin';
+
+  static const List<String> availableParameters = [
+    parameterCodeSkin,
+  ];
+
+  static const String skinValueDigits = 'digits';
+  static const String skinValueFood = 'food';
+  static const String skinValueNature = 'nature';
+  static const String skinValueMonsters = 'monsters';
+
+  static const String defaultSkinValue = skinValueDigits;
+  static const List<String> allowedSkinValues = [
+    skinValueDigits,
+    skinValueFood,
+    skinValueNature,
+    skinValueMonsters,
+  ];
+  static const List<String> shufflableSkins = [
+    skinValueFood,
+    skinValueNature,
+    skinValueMonsters,
+  ];
+
+  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 [];
+  }
+
+  static const int defaultTipCountDownValueInSeconds = 20;
+}
diff --git a/lib/config/theme.dart b/lib/config/theme.dart
new file mode 100644
index 0000000000000000000000000000000000000000..1732b790ab4f411f084a987bc5e50be2ab408c24
--- /dev/null
+++ b/lib/config/theme.dart
@@ -0,0 +1,196 @@
+import 'package:flutter/material.dart';
+
+/// Colors from Tailwind CSS (v3.0) - June 2022
+///
+/// https://tailwindcss.com/docs/customizing-colors
+
+const int _primaryColor = 0xFF6366F1;
+const MaterialColor primarySwatch = MaterialColor(_primaryColor, <int, Color>{
+  50: Color(0xFFEEF2FF), // indigo-50
+  100: Color(0xFFE0E7FF), // indigo-100
+  200: Color(0xFFC7D2FE), // indigo-200
+  300: Color(0xFFA5B4FC), // indigo-300
+  400: Color(0xFF818CF8), // indigo-400
+  500: Color(_primaryColor), // indigo-500
+  600: Color(0xFF4F46E5), // indigo-600
+  700: Color(0xFF4338CA), // indigo-700
+  800: Color(0xFF3730A3), // indigo-800
+  900: Color(0xFF312E81), // indigo-900
+});
+
+const int _textColor = 0xFF64748B;
+const MaterialColor textSwatch = MaterialColor(_textColor, <int, Color>{
+  50: Color(0xFFF8FAFC), // slate-50
+  100: Color(0xFFF1F5F9), // slate-100
+  200: Color(0xFFE2E8F0), // slate-200
+  300: Color(0xFFCBD5E1), // slate-300
+  400: Color(0xFF94A3B8), // slate-400
+  500: Color(_textColor), // slate-500
+  600: Color(0xFF475569), // slate-600
+  700: Color(0xFF334155), // slate-700
+  800: Color(0xFF1E293B), // slate-800
+  900: Color(0xFF0F172A), // slate-900
+});
+
+const Color errorColor = Color(0xFFDC2626); // red-600
+
+final ColorScheme lightColorScheme = ColorScheme.light(
+  primary: primarySwatch.shade500,
+  secondary: primarySwatch.shade500,
+  onSecondary: Colors.white,
+  error: errorColor,
+  background: textSwatch.shade200,
+  onBackground: textSwatch.shade500,
+  onSurface: textSwatch.shade500,
+  surface: textSwatch.shade50,
+  surfaceVariant: Colors.white,
+  shadow: textSwatch.shade900.withOpacity(.1),
+);
+
+final ColorScheme darkColorScheme = ColorScheme.dark(
+  primary: primarySwatch.shade500,
+  secondary: primarySwatch.shade500,
+  onSecondary: Colors.white,
+  error: errorColor,
+  background: const Color(0xFF171724),
+  onBackground: textSwatch.shade400,
+  onSurface: textSwatch.shade300,
+  surface: const Color(0xFF262630),
+  surfaceVariant: const Color(0xFF282832),
+  shadow: textSwatch.shade900.withOpacity(.2),
+);
+
+final ThemeData lightTheme = ThemeData(
+  colorScheme: lightColorScheme,
+  fontFamily: 'Nunito',
+  textTheme: TextTheme(
+    displayLarge: TextStyle(
+      color: textSwatch.shade700,
+      fontFamily: 'Nunito',
+    ),
+    displayMedium: TextStyle(
+      color: textSwatch.shade600,
+      fontFamily: 'Nunito',
+    ),
+    displaySmall: TextStyle(
+      color: textSwatch.shade500,
+      fontFamily: 'Nunito',
+    ),
+    headlineLarge: TextStyle(
+      color: textSwatch.shade700,
+      fontFamily: 'Nunito',
+    ),
+    headlineMedium: TextStyle(
+      color: textSwatch.shade600,
+      fontFamily: 'Nunito',
+    ),
+    headlineSmall: TextStyle(
+      color: textSwatch.shade500,
+      fontFamily: 'Nunito',
+    ),
+    titleLarge: TextStyle(
+      color: textSwatch.shade700,
+      fontFamily: 'Nunito',
+    ),
+    titleMedium: TextStyle(
+      color: textSwatch.shade600,
+      fontFamily: 'Nunito',
+    ),
+    titleSmall: TextStyle(
+      color: textSwatch.shade500,
+      fontFamily: 'Nunito',
+    ),
+    bodyLarge: TextStyle(
+      color: textSwatch.shade700,
+      fontFamily: 'Nunito',
+    ),
+    bodyMedium: TextStyle(
+      color: textSwatch.shade600,
+      fontFamily: 'Nunito',
+    ),
+    bodySmall: TextStyle(
+      color: textSwatch.shade500,
+      fontFamily: 'Nunito',
+    ),
+    labelLarge: TextStyle(
+      color: textSwatch.shade700,
+      fontFamily: 'Nunito',
+    ),
+    labelMedium: TextStyle(
+      color: textSwatch.shade600,
+      fontFamily: 'Nunito',
+    ),
+    labelSmall: TextStyle(
+      color: textSwatch.shade500,
+      fontFamily: 'Nunito',
+    ),
+  ),
+);
+
+final ThemeData darkTheme = lightTheme.copyWith(
+  colorScheme: darkColorScheme,
+  textTheme: TextTheme(
+    displayLarge: TextStyle(
+      color: textSwatch.shade200,
+      fontFamily: 'Nunito',
+    ),
+    displayMedium: TextStyle(
+      color: textSwatch.shade300,
+      fontFamily: 'Nunito',
+    ),
+    displaySmall: TextStyle(
+      color: textSwatch.shade400,
+      fontFamily: 'Nunito',
+    ),
+    headlineLarge: TextStyle(
+      color: textSwatch.shade200,
+      fontFamily: 'Nunito',
+    ),
+    headlineMedium: TextStyle(
+      color: textSwatch.shade300,
+      fontFamily: 'Nunito',
+    ),
+    headlineSmall: TextStyle(
+      color: textSwatch.shade400,
+      fontFamily: 'Nunito',
+    ),
+    titleLarge: TextStyle(
+      color: textSwatch.shade200,
+      fontFamily: 'Nunito',
+    ),
+    titleMedium: TextStyle(
+      color: textSwatch.shade300,
+      fontFamily: 'Nunito',
+    ),
+    titleSmall: TextStyle(
+      color: textSwatch.shade400,
+      fontFamily: 'Nunito',
+    ),
+    bodyLarge: TextStyle(
+      color: textSwatch.shade200,
+      fontFamily: 'Nunito',
+    ),
+    bodyMedium: TextStyle(
+      color: textSwatch.shade300,
+      fontFamily: 'Nunito',
+    ),
+    bodySmall: TextStyle(
+      color: textSwatch.shade400,
+      fontFamily: 'Nunito',
+    ),
+    labelLarge: TextStyle(
+      color: textSwatch.shade200,
+      fontFamily: 'Nunito',
+    ),
+    labelMedium: TextStyle(
+      color: textSwatch.shade300,
+      fontFamily: 'Nunito',
+    ),
+    labelSmall: TextStyle(
+      color: textSwatch.shade400,
+      fontFamily: 'Nunito',
+    ),
+  ),
+);
+
+final ThemeData appTheme = lightTheme;
diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart
new file mode 100644
index 0000000000000000000000000000000000000000..3f29efd13c70c8999233870ff0f126f53f242461
--- /dev/null
+++ b/lib/cubit/game_cubit.dart
@@ -0,0 +1,166 @@
+import 'dart:async';
+import 'dart:math';
+
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+import 'package:sudoku/config/default_global_settings.dart';
+import 'package:sudoku/models/cell.dart';
+
+import 'package:sudoku/models/cell_location.dart';
+import 'package:sudoku/models/game.dart';
+import 'package:sudoku/models/settings_game.dart';
+import 'package:sudoku/models/settings_global.dart';
+import 'package:sudoku/utils/board_animate.dart';
+
+part 'game_state.dart';
+
+class GameCubit extends HydratedCubit<GameState> {
+  GameCubit()
+      : super(GameState(
+          game: Game.createNull(),
+        ));
+
+  void updateState(Game game) {
+    emit(GameState(
+      game: game,
+    ));
+  }
+
+  void refresh() {
+    updateState(Game(
+      gameSettings: state.game.gameSettings,
+      globalSettings: state.game.globalSettings,
+      board: state.game.board,
+      solvedBoard: state.game.solvedBoard,
+      isRunning: state.game.isRunning,
+      isFinished: state.game.isFinished,
+      blockSizeHorizontal: state.game.blockSizeHorizontal,
+      blockSizeVertical: state.game.blockSizeVertical,
+      boardSize: state.game.boardSize,
+      shuffledCellValues: state.game.shuffledCellValues,
+      boardConflicts: state.game.boardConflicts,
+      selectedCell: state.game.selectedCell,
+      showConflicts: state.game.showConflicts,
+      givenTipsCount: state.game.givenTipsCount,
+      buttonTipsCountdown: state.game.buttonTipsCountdown,
+      animationInProgress: state.game.animationInProgress,
+      boardAnimated: state.game.boardAnimated,
+    ));
+  }
+
+  void startNewGame({
+    required GameSettings gameSettings,
+    required GlobalSettings globalSettings,
+  }) {
+    final Game newGame = Game.createNew(
+      gameSettings: gameSettings,
+      globalSettings: globalSettings,
+    );
+
+    newGame.dump();
+
+    updateState(newGame);
+    refresh();
+
+    BoardAnimate.startAnimation(this, 'start');
+  }
+
+  void selectCell(CellLocation location) {
+    state.game.selectedCell = state.game.board.get(location);
+    refresh();
+  }
+
+  void unselectCell() {
+    state.game.selectedCell = null;
+    refresh();
+  }
+
+  void updateCellValue(CellLocation location, int value) {
+    if (state.game.board.get(location).isFixed == false) {
+      state.game.board.set(
+        location,
+        Cell(
+          location: location,
+          value: value,
+          isFixed: false,
+        ),
+      );
+      refresh();
+    }
+
+    if (state.game.checkBoardIsSolved()) {
+      BoardAnimate.startAnimation(this, 'win');
+      state.game.isFinished = true;
+      refresh();
+    }
+  }
+
+  void toggleShowConflicts() {
+    state.game.showConflicts = !state.game.showConflicts;
+    refresh();
+  }
+
+  void increaseGivenTipsCount() {
+    state.game.givenTipsCount++;
+    state.game.buttonTipsCountdown = DefaultGlobalSettings.defaultTipCountDownValueInSeconds;
+    refresh();
+
+    const Duration interval = Duration(milliseconds: 500);
+    Timer.periodic(
+      interval,
+      (Timer timer) {
+        if (state.game.buttonTipsCountdown == 0) {
+          timer.cancel();
+        } else {
+          state.game.buttonTipsCountdown = max(state.game.buttonTipsCountdown - 1, 0);
+        }
+        refresh();
+      },
+    );
+  }
+
+  void quitGame() {
+    state.game.isRunning = false;
+    refresh();
+  }
+
+  void updateAnimationInProgress(bool animationInProgress) {
+    state.game.animationInProgress = animationInProgress;
+    refresh();
+  }
+
+  void setAnimatedBackground(List animatedCellsPattern) {
+    for (int row = 0; row < state.game.boardSize; row++) {
+      for (int col = 0; col < state.game.boardSize; col++) {
+        state.game.boardAnimated[row][col] = animatedCellsPattern[row][col];
+      }
+    }
+    refresh();
+  }
+
+  void resetAnimatedBackground() {
+    for (int row = 0; row < state.game.boardSize; row++) {
+      for (int col = 0; col < state.game.boardSize; col++) {
+        state.game.boardAnimated[row][col] = false;
+      }
+    }
+    refresh();
+  }
+
+  @override
+  GameState? fromJson(Map<String, dynamic> json) {
+    Game game = json['game'] as Game;
+
+    return GameState(
+      game: game,
+    );
+  }
+
+  @override
+  Map<String, dynamic>? toJson(GameState state) {
+    return <String, dynamic>{
+      'game': state.game.toJson(),
+    };
+  }
+}
diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart
new file mode 100644
index 0000000000000000000000000000000000000000..8d8f70fc138bd99263f889070f3c929c6fb04e31
--- /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.game,
+  });
+
+  final Game game;
+
+  @override
+  List<dynamic> get props => <dynamic>[
+        game,
+      ];
+
+  Map<String, dynamic> get values => <String, dynamic>{
+        'game': game,
+      };
+}
diff --git a/lib/cubit/settings_game_cubit.dart b/lib/cubit/settings_game_cubit.dart
new file mode 100644
index 0000000000000000000000000000000000000000..fc2840d804f35ae11e58873e7d2194a4747aab3f
--- /dev/null
+++ b/lib/cubit/settings_game_cubit.dart
@@ -0,0 +1,71 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+import 'package:sudoku/config/default_game_settings.dart';
+
+import 'package:sudoku/models/settings_game.dart';
+
+part 'settings_game_state.dart';
+
+class GameSettingsCubit extends HydratedCubit<GameSettingsState> {
+  GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault()));
+
+  void setValues({
+    String? level,
+    String? size,
+  }) {
+    emit(
+      GameSettingsState(
+        settings: GameSettings(
+          level: level ?? state.settings.level,
+          size: size ?? state.settings.size,
+        ),
+      ),
+    );
+  }
+
+  String getParameterValue(String code) {
+    switch (code) {
+      case DefaultGameSettings.parameterCodeLevel:
+        return GameSettings.getLevelValueFromUnsafe(state.settings.level);
+      case DefaultGameSettings.parameterCodeSize:
+        return GameSettings.getSizeValueFromUnsafe(state.settings.size);
+    }
+    return '';
+  }
+
+  void setParameterValue(String code, String value) {
+    final String level = (code == DefaultGameSettings.parameterCodeLevel)
+        ? value
+        : getParameterValue(DefaultGameSettings.parameterCodeLevel);
+    final String size = (code == DefaultGameSettings.parameterCodeSize)
+        ? value
+        : getParameterValue(DefaultGameSettings.parameterCodeSize);
+
+    setValues(
+      level: level,
+      size: size,
+    );
+  }
+
+  @override
+  GameSettingsState? fromJson(Map<String, dynamic> json) {
+    final String level = json[DefaultGameSettings.parameterCodeLevel] as String;
+    final String size = json[DefaultGameSettings.parameterCodeSize] as String;
+
+    return GameSettingsState(
+      settings: GameSettings(
+        level: level,
+        size: size,
+      ),
+    );
+  }
+
+  @override
+  Map<String, dynamic>? toJson(GameSettingsState state) {
+    return <String, dynamic>{
+      DefaultGameSettings.parameterCodeLevel: state.settings.level,
+      DefaultGameSettings.parameterCodeSize: state.settings.size,
+    };
+  }
+}
diff --git a/lib/cubit/settings_game_state.dart b/lib/cubit/settings_game_state.dart
new file mode 100644
index 0000000000000000000000000000000000000000..b773dc69be12673b158e880e2d7e6e7bec465506
--- /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 0000000000000000000000000000000000000000..9dceaa067477ac8ccf58b86afee7f7c4b7b0977f
--- /dev/null
+++ b/lib/cubit/settings_global_cubit.dart
@@ -0,0 +1,60 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+import 'package:sudoku/config/default_global_settings.dart';
+
+import 'package:sudoku/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 0000000000000000000000000000000000000000..4e4fbdf707b4e805f2092d0ca6a68a2de1c957c6
--- /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 8767c741b7b3edf0380b48e12a9bbe2b1becf009..0000000000000000000000000000000000000000
--- a/lib/entities/cell.dart
+++ /dev/null
@@ -1,222 +0,0 @@
-import 'package:flutter/material.dart';
-
-import 'package:sudoku/provider/data.dart';
-import 'package:sudoku/utils/board_animate.dart';
-import 'package:sudoku/utils/board_utils.dart';
-
-class Cell {
-  const Cell({
-    required this.row,
-    required this.col,
-    required this.value,
-    required this.isFixed,
-  });
-
-  final int row;
-  final int col;
-  final int value;
-  final bool isFixed;
-
-  static const Cell none = Cell(row: 0, col: 0, value: 0, isFixed: true);
-
-  /*
-  * Build widget for board cell, with interactions
-  */
-  Widget widget(Data myProvider) {
-    final String imageAsset = getImageAssetName(myProvider);
-
-    return Container(
-      decoration: BoxDecoration(
-        color: getBackgroundColor(myProvider),
-        border: getCellBorders(myProvider),
-      ),
-      child: GestureDetector(
-        child: AnimatedSwitcher(
-          duration: const Duration(milliseconds: 100),
-          transitionBuilder: (Widget child, Animation<double> animation) {
-            return ScaleTransition(scale: animation, child: child);
-          },
-          child: Image(
-            image: AssetImage(imageAsset),
-            fit: BoxFit.fill,
-            key: ValueKey<int>(imageAsset.hashCode),
-          ),
-        ),
-        onTap: () {
-          if (col != myProvider.selectedCellCol || row != myProvider.selectedCellRow) {
-            myProvider.selectCell(col, row);
-          } else {
-            myProvider.selectCell(null, null);
-          }
-        },
-      ),
-    );
-  }
-
-  /*
-  * Build widget for select/update value cell, with interactions
-  */
-  Container widgetUpdateValue(Data myProvider) {
-    if (value < 0) {
-      return Container();
-    }
-
-    final String imageAsset = getImageAssetName(myProvider);
-    Color backgroundColor = Colors.grey.shade200;
-
-    if (myProvider.showConflicts &&
-        myProvider.selectedCellCol != null &&
-        myProvider.selectedCellRow != null) {
-      final Board cells = myProvider.board;
-      final int blockSizeHorizontal = myProvider.blockSizeHorizontal;
-      final int blockSizeVertical = myProvider.blockSizeVertical;
-
-      if (!BoardUtils.isValueAllowed(cells, blockSizeHorizontal, blockSizeVertical,
-          myProvider.selectedCellCol, myProvider.selectedCellRow, value)) {
-        backgroundColor = Colors.pink.shade100;
-      }
-    }
-
-    return Container(
-        decoration: BoxDecoration(
-          color: backgroundColor,
-          border: Border.all(
-            color: Colors.black,
-            width: 2,
-          ),
-        ),
-        child: GestureDetector(
-          child: Image(image: AssetImage(imageAsset), fit: BoxFit.fill),
-          onTap: () {
-            if (myProvider.selectedCellCol != null && myProvider.selectedCellRow != null) {
-              myProvider.updateCellValue(
-                  myProvider.selectedCellCol, myProvider.selectedCellRow, value);
-            }
-            myProvider.selectCell(null, null);
-            if (BoardUtils.checkBoardIsSolved(myProvider)) {
-              BoardAnimate.startAnimation(myProvider, 'win');
-            }
-          },
-        ));
-  }
-
-  /*
-  * Compute image asset name, from skin and cell value/state
-  */
-  String getImageAssetName(Data myProvider) {
-    if (value > 0) {
-      int cellValue = myProvider.getTranslatedValueForDisplay(value);
-      return 'assets/skins/${myProvider.parameterSkin}_$cellValue.png';
-    }
-
-    return 'assets/icons/cell_empty.png';
-  }
-
-  // Compute cell background color, from cell state
-  Color getBackgroundColor(Data myProvider) {
-    final Color editableCellColor = Colors.grey.shade100;
-    final Color editableCellColorConflict = Colors.pink.shade100;
-    final Color fixedCellColor = Colors.grey.shade300;
-    final Color fixedCellColorConflict = Colors.pink.shade200;
-    final Color editableSelectedValueColor = Colors.green.shade100;
-    final Color fixedSelectedValueColor = Colors.green.shade300;
-    final Color editableAnimated = Colors.green.shade200;
-    final Color fixedAnimated = Colors.green.shade300;
-
-    Color backgroundColor = editableCellColor;
-
-    if (isFixed) {
-      backgroundColor = fixedCellColor;
-    }
-
-    final int conflictsCount = myProvider.boardConflicts[row][col];
-
-    if (myProvider.showConflicts && (conflictsCount != 0)) {
-      if (isFixed) {
-        backgroundColor = fixedCellColorConflict;
-      } else {
-        backgroundColor = editableCellColorConflict;
-      }
-    }
-
-    if (myProvider.showConflicts && (value == myProvider.selectedCellValue)) {
-      if (isFixed) {
-        backgroundColor = fixedSelectedValueColor;
-      } else {
-        backgroundColor = editableSelectedValueColor;
-      }
-    }
-
-    final bool isAnimated = myProvider.boardAnimated[row][col];
-
-    if (isAnimated) {
-      if (isFixed) {
-        backgroundColor = fixedAnimated;
-      } else {
-        backgroundColor = editableAnimated;
-      }
-    }
-
-    return backgroundColor;
-  }
-
-  // Compute cell borders, from board size and cell state
-  Border getCellBorders(Data myProvider) {
-    // final int row = , int col;
-
-    final int blockSizeHorizontal = myProvider.blockSizeHorizontal;
-    final int blockSizeVertical = myProvider.blockSizeVertical;
-
-    const Color cellBorderDarkColor = Colors.black;
-    const Color cellBorderLightColor = Colors.grey;
-    const Color cellBorderSelectedColor = Colors.red;
-
-    Color cellBorderColor = cellBorderSelectedColor;
-    double cellBorderWidth = 4;
-
-    // Reduce cell border width on big boards
-    final int boardSize = blockSizeVertical * blockSizeHorizontal;
-    if (boardSize > 8) {
-      cellBorderWidth = 2;
-      if (boardSize > 10) {
-        cellBorderWidth = 1;
-      }
-    }
-
-    if (!myProvider.gameIsRunning) {
-      cellBorderColor = Colors.green.shade700;
-    }
-
-    Border borders = Border.all(
-      color: cellBorderColor,
-      width: cellBorderWidth,
-    );
-
-    // Update cell borders if not currently selected cell
-    if (col != myProvider.selectedCellCol || row != myProvider.selectedCellRow) {
-      borders = Border(
-        top: BorderSide(
-            width: cellBorderWidth,
-            color:
-                ((row % blockSizeVertical) == 0) ? cellBorderDarkColor : cellBorderLightColor),
-        left: BorderSide(
-            width: cellBorderWidth,
-            color: ((col % blockSizeHorizontal) == 0)
-                ? cellBorderDarkColor
-                : cellBorderLightColor),
-        right: BorderSide(
-            width: cellBorderWidth,
-            color: (((col + 1) % blockSizeHorizontal) == 0)
-                ? cellBorderDarkColor
-                : cellBorderLightColor),
-        bottom: BorderSide(
-            width: cellBorderWidth,
-            color: (((row + 1) % blockSizeVertical) == 0)
-                ? cellBorderDarkColor
-                : cellBorderLightColor),
-      );
-    }
-
-    return borders;
-  }
-}
diff --git a/lib/main.dart b/lib/main.dart
index a1fc31fe9c887e17017e8450e8740e2dd2b7c8b7..a5c7e892c19d4286c9379750b21d9c14c2305ff7 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,15 +1,42 @@
+import 'dart:io';
+
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:provider/provider.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:hive/hive.dart';
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+import 'package:path_provider/path_provider.dart';
 import 'package:overlay_support/overlay_support.dart';
 
-import 'package:sudoku/provider/data.dart';
-import 'package:sudoku/ui/screens/home.dart';
+import 'package:sudoku/config/default_global_settings.dart';
+import 'package:sudoku/config/theme.dart';
+import 'package:sudoku/cubit/game_cubit.dart';
+import 'package:sudoku/cubit/settings_game_cubit.dart';
+import 'package:sudoku/cubit/settings_global_cubit.dart';
+import 'package:sudoku/ui/skeleton.dart';
 
-void main() {
+void main() async {
+  // Initialize packages
   WidgetsFlutterBinding.ensureInitialized();
-  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
-      .then((value) => runApp(const MyApp()));
+  await EasyLocalization.ensureInitialized();
+  final Directory tmpDir = await getTemporaryDirectory();
+  Hive.init(tmpDir.toString());
+  HydratedBloc.storage = await HydratedStorage.build(
+    storageDirectory: tmpDir,
+  );
+
+  runApp(
+    EasyLocalization(
+      path: 'assets/translations',
+      supportedLocales: const <Locale>[
+        Locale('en'),
+        Locale('fr'),
+      ],
+      fallbackLocale: const Locale('en'),
+      useFallbackTranslations: true,
+      child: const MyApp(),
+    ),
+  );
 }
 
 class MyApp extends StatelessWidget {
@@ -17,19 +44,62 @@ class MyApp extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return ChangeNotifierProvider(
-      create: (BuildContext context) => Data(),
-      child: Consumer<Data>(builder: (context, data, child) {
-        return OverlaySupport(
-          child: MaterialApp(
-            debugShowCheckedModeBanner: false,
-            theme: ThemeData(
-              visualDensity: VisualDensity.adaptivePlatformDensity,
-            ),
-            home: const Home(),
-          ),
-        );
-      }),
+    final List<String> assets = getImagesAssets();
+    for (String asset in assets) {
+      precacheImage(AssetImage(asset), context);
+    }
+
+    return MultiBlocProvider(
+      providers: [
+        BlocProvider<GameCubit>(create: (context) => GameCubit()),
+        BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()),
+        BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()),
+      ],
+      child: OverlaySupport(
+        child: MaterialApp(
+          title: 'Sudoku',
+          theme: appTheme,
+          home: const SkeletonScreen(),
+
+          // Localization stuff
+          localizationsDelegates: context.localizationDelegates,
+          supportedLocales: context.supportedLocales,
+          locale: context.locale,
+          debugShowCheckedModeBanner: false,
+        ),
+      ),
     );
   }
+
+  List<String> getImagesAssets() {
+    final List<String> assets = [];
+
+    final List<String> gameImages = [
+      'button_back',
+      'button_help',
+      'button_show_conflicts',
+      'button_start',
+      'game_win',
+      'placeholder',
+      'cell_empty'
+    ];
+
+    for (String image in gameImages) {
+      assets.add('${'assets/icons/$image'}.png');
+    }
+
+    List<String> skinImages = [];
+    for (int value = 1; value <= 16; value++) {
+      skinImages.add(value.toString());
+    }
+
+    for (String skin in DefaultGlobalSettings.allowedSkinValues) {
+      assets.add('assets/icons/skin_$skin.png');
+      for (String image in skinImages) {
+        assets.add('${'${'assets/skins/$skin'}_$image'}.png');
+      }
+    }
+
+    return assets;
+  }
 }
diff --git a/lib/models/board.dart b/lib/models/board.dart
new file mode 100644
index 0000000000000000000000000000000000000000..c83989ed1f70bec670d6fe6a565279ff294fdab6
--- /dev/null
+++ b/lib/models/board.dart
@@ -0,0 +1,206 @@
+import 'dart:math';
+
+import 'package:sudoku/models/cell.dart';
+import 'package:sudoku/models/cell_location.dart';
+import 'package:sudoku/models/types.dart';
+import 'package:sudoku/utils/tools.dart';
+
+class Board {
+  Board({
+    required this.cells,
+  });
+
+  BoardCells cells = const [];
+
+  factory Board.createEmpty() {
+    return Board(
+      cells: [],
+    );
+  }
+
+  factory Board.createNew({
+    required BoardCells cells,
+  }) {
+    return Board(
+      cells: cells,
+    );
+  }
+
+  Cell get(CellLocation location) {
+    if (location.row < cells.length) {
+      if (location.col < cells[location.row].length) {
+        return cells[location.row][location.col];
+      }
+    }
+
+    return Cell.none;
+  }
+
+  void set(CellLocation location, Cell cell) {
+    cells[location.row][location.col] = cell;
+  }
+
+  BoardCells copyCells() {
+    final BoardCells copiedGrid = [];
+    for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) {
+      final List<Cell> row = [];
+      for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) {
+        row.add(Cell(
+          location: CellLocation.go(rowIndex, colIndex),
+          value: cells[rowIndex][colIndex].value,
+          isFixed: false,
+        ));
+      }
+      copiedGrid.add(row);
+    }
+
+    return copiedGrid;
+  }
+
+  BoardCells getSolvedGrid() {
+    final Board tmpBoard = Board(cells: copyCells());
+
+    do {
+      final List<List<int>> cellsWithUniqueAvailableValue =
+          tmpBoard.getEmptyCellsWithUniqueAvailableValue();
+      if (cellsWithUniqueAvailableValue.isEmpty) {
+        break;
+      }
+
+      for (int i = 0; i < cellsWithUniqueAvailableValue.length; i++) {
+        final int row = cellsWithUniqueAvailableValue[i][0];
+        final int col = cellsWithUniqueAvailableValue[i][1];
+        final int value = cellsWithUniqueAvailableValue[i][2];
+
+        tmpBoard.cells[row][col] = Cell(
+          location: CellLocation.go(row, col),
+          value: value,
+          isFixed: tmpBoard.cells[row][col].isFixed,
+        );
+      }
+    } while (true);
+
+    return tmpBoard.cells;
+  }
+
+  List<List<int>> getEmptyCellsWithUniqueAvailableValue() {
+    List<List<int>> candidateCells = [];
+
+    final int boardSize = cells.length;
+
+    for (int row = 0; row < boardSize; row++) {
+      for (int col = 0; col < boardSize; col++) {
+        if (cells[row][col].value == 0) {
+          int allowedValuesCount = 0;
+          int candidateValue = 0;
+          for (int value = 1; value <= boardSize; value++) {
+            if (isValueAllowed(CellLocation.go(row, col), value)) {
+              candidateValue = value;
+              allowedValuesCount++;
+            }
+          }
+          if (allowedValuesCount == 1) {
+            candidateCells.add([row, col, candidateValue]);
+          }
+        }
+      }
+    }
+
+    return candidateCells;
+  }
+
+  bool isValueAllowed(CellLocation? candidateLocation, int candidateValue) {
+    if ((candidateLocation == null) || (candidateValue == 0)) {
+      return true;
+    }
+
+    final int boardSize = cells.length;
+
+    // check lines does not contains a value twice
+    for (int row = 0; row < boardSize; row++) {
+      final List<int> values = [];
+      for (int col = 0; col < boardSize; col++) {
+        int value = cells[row][col].value;
+        if (row == candidateLocation.row && col == candidateLocation.col) {
+          value = candidateValue;
+        }
+        if (value != 0) {
+          values.add(value);
+        }
+      }
+      final List<int> distinctValues = values.toSet().toList();
+      if (values.length != distinctValues.length) {
+        return false;
+      }
+    }
+
+    // check columns does not contains a value twice
+    for (int col = 0; col < boardSize; col++) {
+      final List<int> values = [];
+      for (int row = 0; row < boardSize; row++) {
+        int value = cells[row][col].value;
+        if (row == candidateLocation.row && col == candidateLocation.col) {
+          value = candidateValue;
+        }
+        if (value != 0) {
+          values.add(value);
+        }
+      }
+      final List<int> distinctValues = values.toSet().toList();
+      if (values.length != distinctValues.length) {
+        return false;
+      }
+    }
+
+    // check blocks does not contains a value twice
+    final int blockSizeVertical = sqrt(cells.length).toInt();
+    final int blockSizeHorizontal = cells.length ~/ blockSizeVertical;
+
+    final int horizontalBlocksCount = blockSizeVertical;
+    final int verticalBlocksCount = blockSizeHorizontal;
+    for (int blockRow = 0; blockRow < verticalBlocksCount; blockRow++) {
+      for (int blockCol = 0; blockCol < horizontalBlocksCount; blockCol++) {
+        final List<int> values = [];
+
+        for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) {
+          for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) {
+            final int row = (blockRow * blockSizeVertical) + rowInBlock;
+            final int col = (blockCol * blockSizeHorizontal) + colInBlock;
+            int value = cells[row][col].value;
+            if (row == candidateLocation.row && col == candidateLocation.col) {
+              value = candidateValue;
+            }
+            if (value != 0) {
+              values.add(value);
+            }
+          }
+        }
+
+        final List<int> distinctValues = values.toSet().toList();
+        if (values.length != distinctValues.length) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  void dump() {
+    printlog('');
+    printlog('$Board:');
+    printlog('  cells: $cells');
+    printlog('');
+  }
+
+  @override
+  String toString() {
+    return '$Board(${toJson()})';
+  }
+
+  Map<String, dynamic>? toJson() {
+    return <String, dynamic>{
+      'cells': cells,
+    };
+  }
+}
diff --git a/lib/models/cell.dart b/lib/models/cell.dart
new file mode 100644
index 0000000000000000000000000000000000000000..c8e07172493e6857ef70558dce37c4071950f88d
--- /dev/null
+++ b/lib/models/cell.dart
@@ -0,0 +1,41 @@
+import 'package:sudoku/models/cell_location.dart';
+import 'package:sudoku/utils/tools.dart';
+
+class Cell {
+  const Cell({
+    required this.location,
+    required this.value,
+    required this.isFixed,
+  });
+
+  final CellLocation location;
+  final int value;
+  final bool isFixed;
+
+  static Cell none = Cell(
+    location: CellLocation.go(0, 0),
+    value: 0,
+    isFixed: true,
+  );
+
+  void dump() {
+    printlog('$Cell:');
+    printlog('  location: $location');
+    printlog('  value: $value');
+    printlog('  isFixed: $isFixed');
+    printlog('');
+  }
+
+  @override
+  String toString() {
+    return '$Cell(${toJson()})';
+  }
+
+  Map<String, dynamic>? toJson() {
+    return <String, dynamic>{
+      'location': location.toJson(),
+      'value': value,
+      'isFixed': isFixed,
+    };
+  }
+}
diff --git a/lib/models/cell_location.dart b/lib/models/cell_location.dart
new file mode 100644
index 0000000000000000000000000000000000000000..e3c14a8c27cc1368afb9fdd30d4683206c9df987
--- /dev/null
+++ b/lib/models/cell_location.dart
@@ -0,0 +1,34 @@
+import 'package:sudoku/utils/tools.dart';
+
+class CellLocation {
+  final int col;
+  final int row;
+
+  CellLocation({
+    required this.col,
+    required this.row,
+  });
+
+  factory CellLocation.go(int row, int col) {
+    return CellLocation(col: col, row: row);
+  }
+
+  void dump() {
+    printlog('$CellLocation:');
+    printlog('  row: $row');
+    printlog('  col: $col');
+    printlog('');
+  }
+
+  @override
+  String toString() {
+    return '$CellLocation(${toJson()})';
+  }
+
+  Map<String, dynamic>? toJson() {
+    return <String, dynamic>{
+      'row': row,
+      'col': col,
+    };
+  }
+}
diff --git a/lib/models/game.dart b/lib/models/game.dart
new file mode 100644
index 0000000000000000000000000000000000000000..3ae9cc9a96192c9e9d4b5d9444c7df3c434c5f5c
--- /dev/null
+++ b/lib/models/game.dart
@@ -0,0 +1,424 @@
+import 'dart:math';
+
+import 'package:sudoku/assets/grids.dart';
+import 'package:sudoku/config/default_global_settings.dart';
+import 'package:sudoku/cubit/game_cubit.dart';
+import 'package:sudoku/models/board.dart';
+import 'package:sudoku/models/cell.dart';
+import 'package:sudoku/models/cell_location.dart';
+import 'package:sudoku/models/settings_game.dart';
+import 'package:sudoku/models/settings_global.dart';
+import 'package:sudoku/models/types.dart';
+import 'package:sudoku/utils/board_utils.dart';
+import 'package:sudoku/utils/tools.dart';
+
+class Game {
+  Game({
+    required this.gameSettings,
+    required this.globalSettings,
+    required this.board,
+    required this.solvedBoard,
+    required this.isRunning,
+    required this.isFinished,
+    this.shuffledCellValues = const [],
+    this.boardConflicts = const [],
+    this.selectedCell,
+    this.showConflicts = false,
+    this.givenTipsCount = 0,
+    this.buttonTipsCountdown = 0,
+    this.animationInProgress = false,
+    this.boardAnimated = const [],
+    required this.blockSizeHorizontal,
+    required this.blockSizeVertical,
+    required this.boardSize,
+  });
+
+  final GameSettings gameSettings;
+  final GlobalSettings globalSettings;
+
+  bool isRunning = false;
+  bool isFinished = false;
+
+  int blockSizeVertical = 0;
+  int blockSizeHorizontal = 0;
+  int boardSize = 0;
+
+  Board board;
+  Board solvedBoard;
+
+  List<int> shuffledCellValues = [];
+  Cell? selectedCell;
+
+  int givenTipsCount = 0;
+  int buttonTipsCountdown = 0;
+  bool showConflicts = false;
+  ConflictsCount boardConflicts = [];
+
+  bool animationInProgress = false;
+  AnimatedBoard boardAnimated = [];
+
+  factory Game.createNull() {
+    return Game(
+      gameSettings: GameSettings.createDefault(),
+      globalSettings: GlobalSettings.createDefault(),
+      board: Board.createEmpty(),
+      solvedBoard: Board.createEmpty(),
+      shuffledCellValues: [],
+      isRunning: false,
+      isFinished: false,
+      givenTipsCount: 0,
+      blockSizeHorizontal: 0,
+      blockSizeVertical: 0,
+      boardSize: 0,
+    );
+  }
+
+  factory Game.createNew({
+    GameSettings? gameSettings,
+    GlobalSettings? globalSettings,
+  }) {
+    final GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault();
+    final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault();
+
+    final int blockSizeHorizontal = int.parse(newGameSettings.size.split('x')[0]);
+    final int blockSizeVertical = int.parse(newGameSettings.size.split('x')[1]);
+    final int boardSize = blockSizeHorizontal * blockSizeVertical;
+
+    const int maxCellValue = 16;
+    final List<int> shuffledCellValues = List<int>.generate(maxCellValue, (i) => i + 1);
+
+    if (DefaultGlobalSettings.shufflableSkins.contains(newGlobalSettings.skin)) {
+      shuffledCellValues.shuffle();
+      printlog('Shuffled tiles values: $shuffledCellValues');
+    }
+
+    ConflictsCount nonConflictedBoard = [];
+
+    for (int row = 0; row < boardSize; row++) {
+      List<int> line = [];
+      for (int col = 0; col < boardSize; col++) {
+        line.add(0);
+      }
+      nonConflictedBoard.add(line);
+    }
+
+    final List<String> templates =
+        SudokuGrids.templates[newGameSettings.size]?[newGameSettings.level] ?? [];
+    final String template = templates.elementAt(Random().nextInt(templates.length)).toString();
+
+    if (template.length != pow(blockSizeHorizontal * blockSizeVertical, 2)) {
+      printlog('Failed to get grid template...');
+      return Game.createNull();
+    }
+
+    final Board board = BoardUtils.createBoardFromTemplate(
+      template: template,
+      isSymetric: (blockSizeHorizontal == blockSizeVertical),
+    );
+    final Board solvedBoard = Board.createNew(cells: board.getSolvedGrid());
+
+    // Animated background
+    AnimatedBoard notAnimatedBoard = [];
+    for (int row = 0; row < boardSize; row++) {
+      List<bool> line = [];
+      for (int col = 0; col < boardSize; col++) {
+        line.add(false);
+      }
+      notAnimatedBoard.add(line);
+    }
+
+    return Game(
+      gameSettings: newGameSettings,
+      globalSettings: newGlobalSettings,
+      board: board,
+      solvedBoard: solvedBoard,
+      isRunning: true,
+      isFinished: false,
+      boardConflicts: nonConflictedBoard,
+      shuffledCellValues: shuffledCellValues,
+      selectedCell: null,
+      blockSizeHorizontal: blockSizeHorizontal,
+      blockSizeVertical: blockSizeVertical,
+      boardSize: boardSize,
+      boardAnimated: notAnimatedBoard,
+    );
+  }
+
+  bool canGiveTip() {
+    return (buttonTipsCountdown == 0);
+  }
+
+  int getTranslatedValueForDisplay(int originalValue) {
+    return shuffledCellValues[originalValue - 1];
+  }
+
+  bool checkBoardIsSolved() {
+    // (re)compute conflicts
+    boardConflicts = computeConflictsInBoard();
+
+    // check grid is fully completed and does not contain conflict
+    for (int row = 0; row < boardSize; row++) {
+      for (int col = 0; col < boardSize; col++) {
+        if (board.cells[row][col].value == 0 || boardConflicts[row][col] != 0) {
+          return false;
+        }
+      }
+    }
+
+    printlog('-> ok sudoku solved!');
+
+    return true;
+  }
+
+  ConflictsCount computeConflictsInBoard() {
+    final BoardCells cells = board.cells;
+    final ConflictsCount conflicts = boardConflicts;
+
+    // reset conflict states
+    for (int row = 0; row < boardSize; row++) {
+      for (int col = 0; col < boardSize; col++) {
+        conflicts[row][col] = 0;
+      }
+    }
+
+    // check lines does not contains a value twice
+    for (int row = 0; row < boardSize; row++) {
+      final List<int> values = [];
+      for (int col = 0; col < boardSize; col++) {
+        int value = cells[row][col].value;
+        if (value != 0) {
+          values.add(value);
+        }
+      }
+      final List<int> distinctValues = values.toSet().toList();
+      if (values.length != distinctValues.length) {
+        printlog('line $row contains duplicates');
+        // Add line to cells in conflict
+        for (int col = 0; col < boardSize; col++) {
+          conflicts[row][col]++;
+        }
+      }
+    }
+
+    // check columns does not contains a value twice
+    for (int col = 0; col < boardSize; col++) {
+      final List<int> values = [];
+      for (int row = 0; row < boardSize; row++) {
+        int value = cells[row][col].value;
+        if (value != 0) {
+          values.add(value);
+        }
+      }
+      final List<int> distinctValues = values.toSet().toList();
+      if (values.length != distinctValues.length) {
+        printlog('column $col contains duplicates');
+        // Add column to cells in conflict
+        for (int row = 0; row < boardSize; row++) {
+          conflicts[row][col]++;
+        }
+      }
+    }
+
+    // check blocks does not contains a value twice
+    final int horizontalBlocksCount = blockSizeVertical;
+    final int verticalBlocksCount = blockSizeHorizontal;
+    for (int blockRow = 0; blockRow < verticalBlocksCount; blockRow++) {
+      for (int blockCol = 0; blockCol < horizontalBlocksCount; blockCol++) {
+        List<int> values = [];
+
+        for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) {
+          for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) {
+            int row = (blockRow * blockSizeVertical) + rowInBlock;
+            int col = (blockCol * blockSizeHorizontal) + colInBlock;
+            int value = cells[row][col].value;
+            if (value != 0) {
+              values.add(value);
+            }
+          }
+        }
+
+        List<int> distinctValues = values.toSet().toList();
+        if (values.length != distinctValues.length) {
+          printlog('block [$blockCol,$blockRow] contains duplicates');
+          // Add blocks to cells in conflict
+          for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) {
+            for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) {
+              int row = (blockRow * blockSizeVertical) + rowInBlock;
+              int col = (blockCol * blockSizeHorizontal) + colInBlock;
+              conflicts[row][col]++;
+            }
+          }
+        }
+      }
+    }
+
+    return conflicts;
+  }
+
+  void showTip(GameCubit gameCubit) {
+    if (selectedCell == null) {
+      // no selected cell -> pick one
+      helpSelectCell(gameCubit);
+    } else {
+      // currently selected cell -> set value
+      helpFillCell(gameCubit);
+    }
+    gameCubit.increaseGivenTipsCount();
+  }
+
+  void helpSelectCell(GameCubit gameCubit) {
+    // pick one of wrong value cells, if found
+    final List<List<int>> wrongValueCells = getCellsWithWrongValue();
+    if (wrongValueCells.isNotEmpty) {
+      printlog('will pick from wrongValueCells');
+      pickRandomFromList(gameCubit, wrongValueCells);
+      return;
+    }
+
+    // pick one of conflicting cells, if found
+    final List<List<int>> conflictingCells = getCellsWithConflicts();
+    if (conflictingCells.isNotEmpty) {
+      printlog('will pick from conflictingCells');
+      pickRandomFromList(gameCubit, conflictingCells);
+      return;
+    }
+
+    //  pick one form cells with unique non-conflicting candidate value
+    final List<List<int>> candidateCells = board.getEmptyCellsWithUniqueAvailableValue();
+    if (candidateCells.isNotEmpty) {
+      printlog('will pick from candidateCells');
+      pickRandomFromList(gameCubit, candidateCells);
+      return;
+    }
+  }
+
+  List<List<int>> getCellsWithWrongValue() {
+    final BoardCells cells = board.cells;
+    final BoardCells cellsSolved = solvedBoard.cells;
+
+    List<List<int>> cellsWithWrongValue = [];
+
+    for (int row = 0; row < boardSize; row++) {
+      for (int col = 0; col < boardSize; col++) {
+        if (cells[row][col].value != 0 &&
+            cells[row][col].value != cellsSolved[row][col].value) {
+          cellsWithWrongValue.add([row, col]);
+        }
+      }
+    }
+
+    return cellsWithWrongValue;
+  }
+
+  List<List<int>> getCellsWithConflicts() {
+    List<List<int>> cellsWithConflict = [];
+
+    for (int row = 0; row < boardSize; row++) {
+      for (int col = 0; col < boardSize; col++) {
+        if (boardConflicts[row][col] != 0) {
+          cellsWithConflict.add([row, col]);
+        }
+      }
+    }
+
+    return cellsWithConflict;
+  }
+
+  void pickRandomFromList(GameCubit gameCubit, List<List<int>> cellsCoordinates) {
+    if (cellsCoordinates.isNotEmpty) {
+      cellsCoordinates.shuffle();
+      final List<int> cell = cellsCoordinates[0];
+      gameCubit.selectCell(CellLocation.go(cell[0], cell[1]));
+    }
+  }
+
+  void helpFillCell(GameCubit gameCubit) {
+    // Will clean cell if no eligible value found
+    int eligibleValue = 0;
+
+    // Ensure there is only one eligible value for this cell
+    int allowedValuesCount = 0;
+    for (int value = 1; value <= boardSize; value++) {
+      if (board.isValueAllowed(selectedCell?.location, value)) {
+        allowedValuesCount++;
+        eligibleValue = value;
+      }
+    }
+
+    gameCubit.updateCellValue(
+        selectedCell!.location, allowedValuesCount == 1 ? eligibleValue : 0);
+    gameCubit.unselectCell();
+  }
+
+  printGrid() {
+    final BoardCells cells = board.cells;
+    final BoardCells solvedCells = solvedBoard.cells;
+
+    const String stringValues = '0123456789ABCDEFG';
+    printlog('');
+    printlog('-------');
+    for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) {
+      String row = '';
+      String rowSolved = '';
+      for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) {
+        row += stringValues[cells[rowIndex][colIndex].value];
+        rowSolved += stringValues[solvedCells[rowIndex][colIndex].value];
+      }
+      printlog('$row | $rowSolved');
+    }
+    printlog('-------');
+    printlog('');
+  }
+
+  void dump() {
+    printlog('');
+    printlog('## Current game dump:');
+    printlog('');
+    gameSettings.dump();
+    globalSettings.dump();
+    printlog('');
+    printlog('$Game:');
+    printlog('  blockSizeHorizontal: $blockSizeHorizontal');
+    printlog('  blockSizeVertical: $blockSizeVertical');
+    printlog('  boardSize: $boardSize');
+    printlog('  shuffledCellValues: $shuffledCellValues');
+    printlog('');
+    printlog('  isRunning: $isRunning');
+    printlog('  isFinished: $isFinished');
+    printlog('  selectedCell: ${selectedCell?.toString() ?? ''}');
+    printlog('  showConflicts: $showConflicts');
+    printlog('  givenTipsCount: $givenTipsCount');
+    printlog('  givenTipsCountEnableCountdown: $buttonTipsCountdown');
+    printlog('  animationInProgress: $animationInProgress');
+    printlog('');
+
+    printGrid();
+    printlog('');
+  }
+
+  @override
+  String toString() {
+    return '$Game(${toJson()})';
+  }
+
+  Map<String, dynamic>? toJson() {
+    return <String, dynamic>{
+      'gameSettings': gameSettings.toJson(),
+      'globalSettings': globalSettings.toJson(),
+      'isRunning': isRunning,
+      'isFinished': isFinished,
+      'board': board.toJson(),
+      'solvedBoard': solvedBoard.toJson(),
+      'shuffledCellValues': shuffledCellValues,
+      'boardConflicts': boardConflicts,
+      'selectedCell': selectedCell?.toJson(),
+      'showConflicts': showConflicts,
+      'givenTipsCount': givenTipsCount,
+      'givenTipsCountEnableCountdown': buttonTipsCountdown,
+      'animationInProgress': animationInProgress,
+      'boardAnimated': boardAnimated,
+      'blockSizeHorizontal': blockSizeHorizontal,
+      'blockSizeVertical': blockSizeVertical,
+    };
+  }
+}
diff --git a/lib/models/settings_game.dart b/lib/models/settings_game.dart
new file mode 100644
index 0000000000000000000000000000000000000000..8786fc9279dc6f3c1893ff606105999b21d501c8
--- /dev/null
+++ b/lib/models/settings_game.dart
@@ -0,0 +1,54 @@
+import 'package:sudoku/config/default_game_settings.dart';
+import 'package:sudoku/utils/tools.dart';
+
+class GameSettings {
+  String level;
+  String size;
+
+  GameSettings({
+    required this.level,
+    required this.size,
+  });
+
+  static String getLevelValueFromUnsafe(String level) {
+    if (DefaultGameSettings.allowedLevelValues.contains(level)) {
+      return level;
+    }
+
+    return DefaultGameSettings.defaultLevelValue;
+  }
+
+  static String getSizeValueFromUnsafe(String size) {
+    if (DefaultGameSettings.allowedSizeValues.contains(size)) {
+      return size;
+    }
+
+    return DefaultGameSettings.defaultSizeValue;
+  }
+
+  factory GameSettings.createDefault() {
+    return GameSettings(
+      level: DefaultGameSettings.defaultLevelValue,
+      size: DefaultGameSettings.defaultSizeValue,
+    );
+  }
+
+  void dump() {
+    printlog('$GameSettings:');
+    printlog('  ${DefaultGameSettings.parameterCodeLevel}: $level');
+    printlog('  ${DefaultGameSettings.parameterCodeSize}: $size');
+    printlog('');
+  }
+
+  @override
+  String toString() {
+    return '$GameSettings(${toJson()})';
+  }
+
+  Map<String, dynamic>? toJson() {
+    return <String, dynamic>{
+      DefaultGameSettings.parameterCodeLevel: level,
+      DefaultGameSettings.parameterCodeSize: size,
+    };
+  }
+}
diff --git a/lib/models/settings_global.dart b/lib/models/settings_global.dart
new file mode 100644
index 0000000000000000000000000000000000000000..b96912020563dd4ace4494be96626d80f8da65d6
--- /dev/null
+++ b/lib/models/settings_global.dart
@@ -0,0 +1,41 @@
+import 'package:sudoku/config/default_global_settings.dart';
+import 'package:sudoku/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 0000000000000000000000000000000000000000..9f75508834cf3590dbc3ed2f1401b6d04bff3d6b
--- /dev/null
+++ b/lib/models/types.dart
@@ -0,0 +1,6 @@
+import 'package:sudoku/models/cell.dart';
+
+typedef BoardCells = List<List<Cell>>;
+typedef ConflictsCount = List<List<int>>;
+typedef AnimatedBoard = List<List<bool>>;
+typedef AnimatedBoardSequence = List<AnimatedBoard>;
diff --git a/lib/provider/data.dart b/lib/provider/data.dart
deleted file mode 100644
index 0ad2253bac79978469fa31c7f717283cfddf0f89..0000000000000000000000000000000000000000
--- a/lib/provider/data.dart
+++ /dev/null
@@ -1,382 +0,0 @@
-import 'dart:async';
-import 'dart:convert';
-
-import 'package:flutter/foundation.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-
-import 'package:sudoku/entities/cell.dart';
-import 'package:sudoku/utils/tools.dart';
-
-typedef Board = List<List<Cell>>;
-typedef ConflictsCount = List<List<int>>;
-typedef AnimatedBoard = List<List<bool>>;
-typedef AnimatedBoardSequence = List<AnimatedBoard>;
-
-class Data extends ChangeNotifier {
-  // Configuration available values
-  final List<String> _availableParameters = ['level', 'size', 'skin'];
-
-  final List<String> _availableLevelValues = ['easy', 'medium', 'hard', 'nightmare'];
-  final List<String> _availableSizeValues = ['2x2', '3x2', '3x3', '4x4'];
-  final List<String> _availableSkinValues = ['default', 'food', 'nature', 'monsters'];
-  final List<String> _shufflableSkins = ['food', 'nature', 'monsters'];
-
-  List<String> get availableParameters => _availableParameters;
-  List<String> get availableLevelValues => _availableLevelValues;
-  List<String> get availableSizeValues => _availableSizeValues;
-  List<String> get availableSkinValues => _availableSkinValues;
-
-  // Application default configuration
-  String _parameterLevel = '';
-  final String _parameterLevelDefault = 'medium';
-  String _parameterSize = '';
-  final String _parameterSizeDefault = '3x3';
-  String _parameterSkin = '';
-  final String _parameterSkinDefault = 'default';
-
-  // Application current configuration
-  String get parameterLevel => _parameterLevel;
-  String get parameterSize => _parameterSize;
-  String get parameterSkin => _parameterSkin;
-
-  // Game data
-  bool _assetsPreloaded = false;
-  bool _gameIsRunning = false;
-  bool _animationInProgress = false;
-  int _blockSizeVertical = 0;
-  int _blockSizeHorizontal = 0;
-  Board _board = [];
-  Board _boardSolved = [];
-  ConflictsCount _boardConflicts = [];
-  AnimatedBoard _boardAnimated = [];
-  List<int> _shuffledCellValues = [];
-  int? _selectedCellCol;
-  int? _selectedCellRow;
-  int? _selectedCellValue;
-  bool _showConflicts = false;
-  int _givenTipsCount = 0;
-  int _givenTipsCountEnableCountdown = 0;
-  String _currentSavedState = '';
-
-  void updateParameterLevel(String parameterLevel) {
-    _parameterLevel = parameterLevel;
-    notifyListeners();
-  }
-
-  int get blockSizeVertical => _blockSizeVertical;
-  int get blockSizeHorizontal => _blockSizeHorizontal;
-  void updateParameterSize(String parameterSize) {
-    _parameterSize = parameterSize;
-    _blockSizeHorizontal = int.parse(_parameterSize.split('x')[0]);
-    _blockSizeVertical = int.parse(_parameterSize.split('x')[1]);
-    notifyListeners();
-  }
-
-  void updateParameterSkin(String parameterSkin) {
-    _parameterSkin = parameterSkin;
-    notifyListeners();
-  }
-
-  String getParameterValue(String parameterCode) {
-    switch (parameterCode) {
-      case 'level':
-        return _parameterLevel;
-      case 'size':
-        return _parameterSize;
-      case 'skin':
-        return _parameterSkin;
-    }
-    return '';
-  }
-
-  List<String> getParameterAvailableValues(String parameterCode) {
-    switch (parameterCode) {
-      case 'level':
-        return _availableLevelValues;
-      case 'size':
-        return _availableSizeValues;
-      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 '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('skin', prefs.getString('skin') ?? _parameterSkinDefault);
-  }
-
-  String get currentSavedState => _currentSavedState;
-
-  String computeCurrentGameState() {
-    String cellsValues = '';
-    const String stringValues = '0123456789ABCDEFG';
-    for (int rowIndex = 0; rowIndex < _board.length; rowIndex++) {
-      for (int colIndex = 0; colIndex < _board[rowIndex].length; colIndex++) {
-        cellsValues += stringValues[_board[rowIndex][colIndex].value];
-        cellsValues += _board[rowIndex][colIndex].isFixed ? 'x' : ' ';
-      }
-    }
-
-    return json.encode({
-      'level': _parameterLevel,
-      'size': _parameterSize,
-      'skin': _parameterSkin,
-      'tipsCount': _givenTipsCount,
-      'showConflicts': _showConflicts,
-      'boardValues': cellsValues,
-      'shuffledCellValues': _shuffledCellValues,
-    });
-  }
-
-  void saveCurrentGameState() async {
-    if (_gameIsRunning) {
-      _currentSavedState = computeCurrentGameState();
-
-      final prefs = await SharedPreferences.getInstance();
-      prefs.setString('savedState', _currentSavedState);
-    } else {
-      resetCurrentSavedState();
-    }
-  }
-
-  void resetCurrentSavedState() async {
-    _currentSavedState = '';
-
-    final prefs = await SharedPreferences.getInstance();
-    prefs.setString('savedState', _currentSavedState);
-    notifyListeners();
-  }
-
-  void loadCurrentSavedState() async {
-    final prefs = await SharedPreferences.getInstance();
-    _currentSavedState = prefs.getString('savedState') ?? '';
-  }
-
-  bool hasCurrentSavedState() {
-    return (_currentSavedState != '');
-  }
-
-  Map<String, dynamic> getCurrentSavedState() {
-    if (_currentSavedState != '') {
-      Map<String, dynamic> savedState = json.decode(_currentSavedState);
-      if (savedState.isNotEmpty) {
-        return savedState;
-      }
-    }
-    return {};
-  }
-
-  bool get gameIsRunning => _gameIsRunning;
-  void updateGameIsRunning(bool gameIsRunning) {
-    _gameIsRunning = gameIsRunning;
-    notifyListeners();
-  }
-
-  bool get assetsPreloaded => _assetsPreloaded;
-  void updateAssetsPreloaded(bool assetsPreloaded) {
-    _assetsPreloaded = assetsPreloaded;
-  }
-
-  Board get board => _board;
-  void updateCells(Board board) {
-    _board = board;
-    notifyListeners();
-  }
-
-  Board get boardSolved => _boardSolved;
-  void updateCellsSolved(Board boardSolved) {
-    _boardSolved = boardSolved;
-  }
-
-  AnimatedBoard get boardAnimated => _boardAnimated;
-  ConflictsCount get boardConflicts => _boardConflicts;
-
-  void shuffleCellValues() {
-    const int maxCellValue = 16;
-    final List<int> values = List<int>.generate(maxCellValue, (i) => i + 1);
-
-    if (_shufflableSkins.contains(_parameterSkin)) {
-      values.shuffle();
-      printlog('Shuffled tiles values: $values');
-    }
-
-    _shuffledCellValues = values;
-  }
-
-  void setShuffleCellValues(List<int> values) {
-    _shuffledCellValues = values;
-  }
-
-  int getTranslatedValueForDisplay(int originalValue) {
-    return _shuffledCellValues[originalValue - 1];
-  }
-
-  int? get selectedCellCol => _selectedCellCol;
-  set updateCelectedCellCol(int? selectedCellCol) {
-    _selectedCellCol = selectedCellCol;
-    notifyListeners();
-  }
-
-  int? get selectedCellRow => _selectedCellRow;
-  set updateSelectedCellRow(int? selectedCellRow) {
-    _selectedCellRow = selectedCellRow;
-    notifyListeners();
-  }
-
-  int? get selectedCellValue => _selectedCellValue;
-  set updateSelectedCellValue(int? selectedCellValue) {
-    _selectedCellValue = selectedCellValue;
-    notifyListeners();
-  }
-
-  int get givenTipsCountEnableCountdown => _givenTipsCountEnableCountdown;
-
-  int get givenTipsCount => _givenTipsCount;
-  void increaseGivenTipsCount() {
-    _givenTipsCount = _givenTipsCount + 1;
-    _givenTipsCountEnableCountdown = 60;
-    const Duration interval = Duration(milliseconds: 500);
-    Timer.periodic(
-      interval,
-      (Timer timer) {
-        if (_givenTipsCountEnableCountdown == 0) {
-          timer.cancel();
-          notifyListeners();
-        } else {
-          _givenTipsCountEnableCountdown--;
-          notifyListeners();
-        }
-      },
-    );
-
-    saveCurrentGameState();
-    notifyListeners();
-  }
-
-  void setGivenTipsCount(int value) {
-    _givenTipsCount = value;
-    _givenTipsCountEnableCountdown = 0;
-    notifyListeners();
-  }
-
-  bool canGiveTip() {
-    return (_givenTipsCountEnableCountdown == 0);
-  }
-
-  void resetGivenTipsCount() {
-    setGivenTipsCount(0);
-  }
-
-  void selectCell(int? col, int? row) {
-    _selectedCellCol = col;
-    _selectedCellRow = row;
-    _selectedCellValue = null;
-    if (row != null && col != null) {
-      if (_board[row][col].value != 0) {
-        _selectedCellValue = _board[row][col].value;
-      }
-    }
-    notifyListeners();
-  }
-
-  void initConflictsBoard() {
-    ConflictsCount nonConflictedBoard = [];
-    final int boardSideLength = _blockSizeHorizontal * _blockSizeVertical;
-    for (int row = 0; row < boardSideLength; row++) {
-      List<int> line = [];
-      for (int col = 0; col < boardSideLength; col++) {
-        line.add(0);
-      }
-      nonConflictedBoard.add(line);
-    }
-    _boardConflicts = nonConflictedBoard;
-  }
-
-  void updateConflicts(ConflictsCount conflictsCount) {
-    _boardConflicts = conflictsCount;
-  }
-
-  void updateCellValue(int? col, int? row, int value) {
-    if ((col != null) && (row != null)) {
-      if (!_board[row][col].isFixed) {
-        _board[row][col] = Cell(
-          row: row,
-          col: col,
-          value: value,
-          isFixed: false,
-        );
-
-        saveCurrentGameState();
-        notifyListeners();
-      }
-    }
-  }
-
-  bool get showConflicts => _showConflicts;
-  void updateShowConflicts(bool showConflicts) {
-    _showConflicts = showConflicts;
-    notifyListeners();
-  }
-
-  void toggleShowConflicts() {
-    updateShowConflicts(!showConflicts);
-    saveCurrentGameState();
-  }
-
-  bool get animationInProgress => _animationInProgress;
-  void updateAnimationInProgress(bool animationInProgress) {
-    _animationInProgress = animationInProgress;
-    notifyListeners();
-  }
-
-  void initAnimatedBackground() {
-    AnimatedBoard staticBoard = [];
-    final int boardSideLength = _blockSizeHorizontal * _blockSizeVertical;
-    for (int row = 0; row < boardSideLength; row++) {
-      List<bool> line = [];
-      for (int col = 0; col < boardSideLength; col++) {
-        line.add(false);
-      }
-      staticBoard.add(line);
-    }
-    _boardAnimated = staticBoard;
-  }
-
-  void setAnimatedBackground(List animatedCellsPattern) {
-    final int boardSideLength = _blockSizeHorizontal * _blockSizeVertical;
-    for (int row = 0; row < boardSideLength; row++) {
-      for (int col = 0; col < boardSideLength; col++) {
-        _boardAnimated[row][col] = animatedCellsPattern[row][col];
-      }
-    }
-    notifyListeners();
-  }
-
-  void resetAnimatedBackground() {
-    final int boardSideLength = _blockSizeHorizontal * _blockSizeVertical;
-    for (int row = 0; row < boardSideLength; row++) {
-      for (int col = 0; col < boardSideLength; col++) {
-        _boardAnimated[row][col] = false;
-      }
-    }
-  }
-}
diff --git a/lib/ui/layout/board.dart b/lib/ui/layout/board.dart
index 069557ef249bea8deb463c029133df17f038fd13..3d44197e0c38ad2dea02d8ef0c93fe79f285b52a 100644
--- a/lib/ui/layout/board.dart
+++ b/lib/ui/layout/board.dart
@@ -1,48 +1,55 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
-import 'package:sudoku/provider/data.dart';
+import 'package:sudoku/cubit/game_cubit.dart';
+import 'package:sudoku/models/cell_location.dart';
+import 'package:sudoku/models/game.dart';
+import 'package:sudoku/ui/widgets/cell_widget.dart';
 
 class BoardLayout extends StatelessWidget {
-  const BoardLayout({super.key, required this.myProvider});
-
-  final Data myProvider;
+  const BoardLayout({super.key});
 
   @override
-  Container build(BuildContext context) {
-    const Color borderColor = Colors.black;
-    final int boardSize = myProvider.blockSizeHorizontal * myProvider.blockSizeVertical;
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game game = gameState.game;
+
+        const Color borderColor = Colors.black;
 
-    final Widget gameTileset = Table(
-      defaultColumnWidth: const IntrinsicColumnWidth(),
-      children: [
-        for (int row = 0; row < boardSize; row++)
-          TableRow(
+        return Container(
+          margin: const EdgeInsets.all(2),
+          padding: const EdgeInsets.all(2),
+          decoration: BoxDecoration(
+            color: borderColor,
+            borderRadius: BorderRadius.circular(2),
+            border: Border.all(
+              color: borderColor,
+              width: 2,
+            ),
+          ),
+          child: Column(
             children: [
-              for (int col = 0; col < boardSize; col++)
-                Column(
-                  children: [myProvider.board[row][col].widget(myProvider)],
-                ),
+              Table(
+                defaultColumnWidth: const IntrinsicColumnWidth(),
+                children: [
+                  for (int row = 0; row < game.boardSize; row++)
+                    TableRow(
+                      children: [
+                        for (int col = 0; col < game.boardSize; col++)
+                          Column(
+                            children: [
+                              CellWidget(cell: game.board.get(CellLocation.go(row, col)))
+                            ],
+                          ),
+                      ],
+                    ),
+                ],
+              ),
             ],
           ),
-      ],
-    );
-
-    return Container(
-      margin: const EdgeInsets.all(2),
-      padding: const EdgeInsets.all(2),
-      decoration: BoxDecoration(
-        color: borderColor,
-        borderRadius: BorderRadius.circular(2),
-        border: Border.all(
-          color: borderColor,
-          width: 2,
-        ),
-      ),
-      child: Column(
-        children: [
-          gameTileset,
-        ],
-      ),
+        );
+      },
     );
   }
 }
diff --git a/lib/ui/layout/game.dart b/lib/ui/layout/game.dart
deleted file mode 100644
index e6cc4ee582875b63e60d5ed2b8951580277a9af0..0000000000000000000000000000000000000000
--- a/lib/ui/layout/game.dart
+++ /dev/null
@@ -1,30 +0,0 @@
-import 'package:flutter/material.dart';
-
-import 'package:sudoku/ui/layout/board.dart';
-import 'package:sudoku/ui/widgets/message_game_end.dart';
-import 'package:sudoku/ui/widgets/bar_select_cell_value.dart';
-import 'package:sudoku/provider/data.dart';
-import 'package:sudoku/utils/board_utils.dart';
-
-class Game extends StatelessWidget {
-  const Game({super.key, required this.myProvider});
-
-  final Data myProvider;
-
-  @override
-  Widget build(BuildContext context) {
-    final bool gameIsFinished = BoardUtils.checkBoardIsSolved(myProvider);
-
-    return Column(
-      mainAxisAlignment: MainAxisAlignment.start,
-      crossAxisAlignment: CrossAxisAlignment.center,
-      children: [
-        BoardLayout(myProvider: myProvider),
-        const SizedBox(height: 2),
-        gameIsFinished
-            ? EndGameMessage(myProvider: myProvider)
-            : SelectCellValueBar(myProvider: myProvider),
-      ],
-    );
-  }
-}
diff --git a/lib/ui/layout/parameters.dart b/lib/ui/layout/parameters.dart
deleted file mode 100644
index abd8b9c25df646b7e7c4e75bb53aeacc7585d3de..0000000000000000000000000000000000000000
--- a/lib/ui/layout/parameters.dart
+++ /dev/null
@@ -1,129 +0,0 @@
-import 'package:flutter/material.dart';
-
-import 'package:sudoku/provider/data.dart';
-import 'package:sudoku/ui/widgets/button_game_resume.dart';
-import 'package:sudoku/ui/widgets/button_game_start_new.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 = [];
-
-    List<String> parameters = myProvider.availableParameters;
-    for (int index = 0; index < parameters.length; index++) {
-      lines.add(buildParameterSelector(myProvider, parameters[index]));
-      lines.add(const SizedBox(height: separatorHeight));
-    }
-
-    myProvider.loadCurrentSavedState();
-    Widget buttonsBlock = myProvider.hasCurrentSavedState()
-        ? ResumeGameButton(myProvider: myProvider)
-        : StartNewGameButton(myProvider: myProvider);
-
-    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),
-        Container(
-          child: buttonsBlock,
-        ),
-      ],
-    );
-  }
-
-  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('placeholder'),
-          onPressed: () {},
-        ),
-      ],
-    );
-  }
-
-  Widget buildParameterSelector(Data myProvider, String parameterCode) {
-    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(myProvider, parameterCode, availableValues[index])
-                ],
-              ),
-          ],
-        ),
-      ],
-    );
-  }
-
-  Widget _buildParameterButton(Data myProvider, String parameterCode, String parameterValue) {
-    String currentValue = myProvider.getParameterValue(parameterCode).toString();
-
-    bool isActive = (parameterValue == currentValue);
-    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/painters/parameter_painter.dart b/lib/ui/painters/parameter_painter.dart
new file mode 100644
index 0000000000000000000000000000000000000000..a409d1173f4b93fe5de5b2474d6ea2c7825a012c
--- /dev/null
+++ b/lib/ui/painters/parameter_painter.dart
@@ -0,0 +1,233 @@
+import 'dart:math';
+
+import 'package:flutter/material.dart';
+
+import 'package:sudoku/config/default_game_settings.dart';
+import 'package:sudoku/models/settings_game.dart';
+import 'package:sudoku/models/settings_global.dart';
+import 'package:sudoku/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.parameterCodeLevel:
+        paintLevelParameterItem(value, canvas, canvasSize);
+        break;
+      case DefaultGameSettings.parameterCodeSize:
+        paintSizeParameterItem(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 paintLevelParameterItem(
+    final String value,
+    final Canvas canvas,
+    final double size,
+  ) {
+    Color backgroundColor = Colors.grey;
+
+    final List<dynamic> stars = [];
+
+    switch (value) {
+      case DefaultGameSettings.levelValueEasy:
+        backgroundColor = Colors.green;
+        stars.add([0.5, 0.5]);
+        break;
+      case DefaultGameSettings.levelValueMedium:
+        backgroundColor = Colors.orange;
+        stars.add([0.3, 0.5]);
+        stars.add([0.7, 0.5]);
+        break;
+      case DefaultGameSettings.levelValueHard:
+        backgroundColor = Colors.red;
+        stars.add([0.3, 0.3]);
+        stars.add([0.7, 0.3]);
+        stars.add([0.5, 0.7]);
+        break;
+      case DefaultGameSettings.levelValueNightmare:
+        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 paintSizeParameterItem(
+    final String value,
+    final Canvas canvas,
+    final double size,
+  ) {
+    Color backgroundColor = Colors.grey;
+    int gridWidth = 1;
+    int gridHeight = 1;
+
+    switch (value) {
+      case DefaultGameSettings.sizeValueTiny:
+        backgroundColor = Colors.green;
+        gridWidth = 2;
+        gridHeight = 2;
+        break;
+      case DefaultGameSettings.sizeValueSmall:
+        backgroundColor = Colors.orange;
+        gridWidth = 3;
+        gridHeight = 2;
+        break;
+      case DefaultGameSettings.sizeValueStandard:
+        backgroundColor = Colors.red;
+        gridWidth = 3;
+        gridHeight = 3;
+        break;
+      case DefaultGameSettings.sizeValueLarge:
+        backgroundColor = Colors.purple;
+        gridWidth = 4;
+        gridHeight = 4;
+        break;
+      default:
+        printlog('Wrong value for size parameter value: $value');
+    }
+
+    final paint = Paint();
+    paint.strokeJoin = StrokeJoin.round;
+    paint.strokeWidth = 5 / 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 / 6;
+    final double originX = (size - gridWidth * cellSize) / 2;
+    final double originY = (size - gridHeight * cellSize) / 2;
+
+    for (int row = 0; row < gridHeight; row++) {
+      for (int col = 0; col < gridWidth; col++) {
+        final Offset topLeft = Offset(originX + col * cellSize, originY + row * cellSize);
+        final Offset bottomRight = topLeft + Offset(cellSize, cellSize);
+
+        paint.color = Colors.white;
+        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/home.dart b/lib/ui/screens/home.dart
deleted file mode 100644
index 3b86e93a33d36f4f319655a41c9eeac189abeba1..0000000000000000000000000000000000000000
--- a/lib/ui/screens/home.dart
+++ /dev/null
@@ -1,165 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:provider/provider.dart';
-import 'package:badges/badges.dart' as badges;
-import 'package:overlay_support/overlay_support.dart';
-
-import 'package:sudoku/ui/layout/game.dart';
-import 'package:sudoku/ui/layout/parameters.dart';
-import 'package:sudoku/provider/data.dart';
-import 'package:sudoku/utils/game_utils.dart';
-
-class Home extends StatefulWidget {
-  const Home({super.key});
-
-  @override
-  HomeState createState() => HomeState();
-}
-
-class HomeState extends State<Home> {
-  @override
-  void initState() {
-    super.initState();
-
-    Data myProvider = Provider.of<Data>(context, listen: false);
-    myProvider.initParametersValues();
-    myProvider.loadCurrentSavedState();
-  }
-
-  List<String> getImagesAssets(Data myProvider) {
-    final List<String> assets = [];
-
-    final List<String> gameImages = [
-      'button_back',
-      'button_help',
-      'button_show_conflicts',
-      'button_start',
-      'game_win',
-      'placeholder',
-      'cell_empty'
-    ];
-    for (String level in myProvider.availableLevelValues) {
-      gameImages.add('level_$level');
-    }
-    for (String size in myProvider.availableSizeValues) {
-      gameImages.add('size_$size');
-    }
-
-    for (String image in gameImages) {
-      assets.add('${'assets/icons/$image'}.png');
-    }
-
-    List<String> skinImages = [];
-    for (int value = 1; value <= 16; value++) {
-      skinImages.add(value.toString());
-    }
-
-    for (String skin in myProvider.availableSkinValues) {
-      for (String image in skinImages) {
-        assets.add('${'${'assets/skins/$skin'}_$image'}.png');
-      }
-    }
-
-    return assets;
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final Data myProvider = Provider.of<Data>(context);
-
-    if (!myProvider.assetsPreloaded) {
-      final List<String> assets = getImagesAssets(myProvider);
-      for (String asset in assets) {
-        precacheImage(AssetImage(asset), context);
-      }
-      myProvider.updateAssetsPreloaded(true);
-    }
-
-    final List<Widget> menuActions = [];
-
-    if (myProvider.gameIsRunning) {
-      menuActions.add(TextButton(
-        child: Container(
-          decoration: BoxDecoration(
-            borderRadius: BorderRadius.circular(4),
-            border: Border.all(
-              color: Colors.white,
-              width: 3,
-            ),
-          ),
-          child: const Image(
-            image: AssetImage('assets/icons/button_back.png'),
-            fit: BoxFit.fill,
-          ),
-        ),
-        onPressed: () => toast('Long press to quit game...'),
-        onLongPress: () => GameUtils.quitGame(myProvider),
-      ));
-      menuActions.add(const Spacer(flex: 6));
-      menuActions.add(TextButton(
-        child: Container(
-          decoration: BoxDecoration(
-            borderRadius: BorderRadius.circular(4),
-            border: Border.all(
-              color: Colors.white,
-              width: 3,
-            ),
-          ),
-          child: badges.Badge(
-            showBadge: myProvider.givenTipsCount == 0 ? false : true,
-            badgeStyle: badges.BadgeStyle(
-              badgeColor: myProvider.givenTipsCount < 10
-                  ? Colors.green
-                  : myProvider.givenTipsCount < 20
-                      ? Colors.orange
-                      : Colors.red,
-            ),
-            badgeContent: Text(
-              myProvider.givenTipsCount == 0 ? '' : myProvider.givenTipsCount.toString(),
-              style: const TextStyle(color: Colors.white),
-            ),
-            child: Container(
-              padding: EdgeInsets.all(myProvider.givenTipsCountEnableCountdown / 4),
-              child: const Image(
-                image: AssetImage('assets/icons/button_help.png'),
-                fit: BoxFit.fill,
-              ),
-            ),
-          ),
-        ),
-        onPressed: () => myProvider.canGiveTip() ? GameUtils.showTip(myProvider) : null,
-      ));
-      menuActions.add(const Spacer());
-      menuActions.add(TextButton(
-        child: Container(
-          decoration: BoxDecoration(
-            borderRadius: BorderRadius.circular(4),
-            border: Border.all(
-              color: myProvider.showConflicts ? Colors.blue : Colors.white,
-              width: 3,
-            ),
-          ),
-          child: const Image(
-            image: AssetImage('assets/icons/button_show_conflicts.png'),
-            fit: BoxFit.fill,
-          ),
-        ),
-        onPressed: () {
-          myProvider.toggleShowConflicts();
-        },
-      ));
-    }
-
-    return Scaffold(
-      appBar: AppBar(
-        actions: menuActions,
-      ),
-      body: SafeArea(
-        child: Center(
-          child: myProvider.gameIsRunning
-              ? Game(myProvider: myProvider)
-              : Parameters(myProvider: myProvider),
-        ),
-      ),
-    );
-  }
-}
diff --git a/lib/ui/screens/screen_game.dart b/lib/ui/screens/screen_game.dart
new file mode 100644
index 0000000000000000000000000000000000000000..0992122a5e2a5e1ddf71f476b9cc3f508ea0771d
--- /dev/null
+++ b/lib/ui/screens/screen_game.dart
@@ -0,0 +1,34 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:sudoku/cubit/game_cubit.dart';
+import 'package:sudoku/models/game.dart';
+import 'package:sudoku/ui/layout/board.dart';
+import 'package:sudoku/ui/widgets/message_game_end.dart';
+import 'package:sudoku/ui/widgets/bar_select_cell_value.dart';
+
+class ScreenGame extends StatelessWidget {
+  const ScreenGame({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game game = gameState.game;
+
+        return Column(
+          mainAxisAlignment: MainAxisAlignment.start,
+          crossAxisAlignment: CrossAxisAlignment.center,
+          children: [
+            const SizedBox(height: 8),
+            const BoardLayout(),
+            const SizedBox(height: 8),
+            game.isFinished ? const SizedBox.shrink() : const SelectCellValueBar(),
+            const Expanded(child: SizedBox.shrink()),
+            game.isFinished ? const EndGameMessage() : const SizedBox.shrink(),
+          ],
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/screens/screen_parameters.dart b/lib/ui/screens/screen_parameters.dart
new file mode 100644
index 0000000000000000000000000000000000000000..fe20130eefec4ef76849c171ac5f18420c208826
--- /dev/null
+++ b/lib/ui/screens/screen_parameters.dart
@@ -0,0 +1,127 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:sudoku/config/default_game_settings.dart';
+import 'package:sudoku/config/default_global_settings.dart';
+import 'package:sudoku/cubit/settings_game_cubit.dart';
+import 'package:sudoku/cubit/settings_global_cubit.dart';
+import 'package:sudoku/ui/painters/parameter_painter.dart';
+import 'package:sudoku/ui/widgets/button_game_start_new.dart';
+import 'package:sudoku/ui/widgets/parameter_image.dart';
+
+class ScreenParameters extends StatelessWidget {
+  const ScreenParameters({super.key});
+
+  final double separatorHeight = 8.0;
+
+  @override
+  Widget build(BuildContext context) {
+    final List<Widget> lines = [];
+
+    // 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);
+
+    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 - 25;
+
+              final bool isPainterOrAsset = (code != DefaultGlobalSettings.parameterCodeSkin);
+
+              return TextButton(
+                child: Container(
+                  child: isPainterOrAsset
+                      ? CustomPaint(
+                          size: Size(itemWidth, itemWidth),
+                          willChange: false,
+                          painter: ParameterPainter(
+                            code: code,
+                            value: value,
+                            isSelected: isActive,
+                            gameSettings: gameSettingsState.settings,
+                            globalSettings: globalSettingsState.settings,
+                          ),
+                          isComplex: true,
+                        )
+                      : SizedBox.square(
+                          dimension: itemWidth,
+                          child: ParameterImage(
+                            code: code,
+                            value: value,
+                            isSelected: isActive,
+                          ),
+                        ),
+                ),
+                onPressed: () => isGlobal
+                    ? globalSettingsCubit.setParameterValue(code, value)
+                    : gameSettingsCubit.setParameterValue(code, value),
+              );
+            },
+          );
+        },
+      );
+
+      parameterButtons.add(parameterButton);
+    }
+
+    return parameterButtons;
+  }
+}
diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart
new file mode 100644
index 0000000000000000000000000000000000000000..2b450cac224517da2565ca260b95b66744106e37
--- /dev/null
+++ b/lib/ui/skeleton.dart
@@ -0,0 +1,33 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:sudoku/cubit/game_cubit.dart';
+import 'package:sudoku/ui/screens/screen_game.dart';
+import 'package:sudoku/ui/screens/screen_parameters.dart';
+import 'package:sudoku/ui/widgets/global_app_bar.dart';
+
+class SkeletonScreen extends StatelessWidget {
+  const SkeletonScreen({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: const GlobalAppBar(),
+      extendBodyBehindAppBar: false,
+      body: Material(
+        color: Theme.of(context).colorScheme.background,
+        child: Container(
+          margin: const EdgeInsets.only(
+            top: 8.0,
+          ),
+          child: BlocBuilder<GameCubit, GameState>(
+            builder: (BuildContext context, GameState gameState) {
+              return gameState.game.isRunning ? const ScreenGame() : const ScreenParameters();
+            },
+          ),
+        ),
+      ),
+      backgroundColor: Theme.of(context).colorScheme.background,
+    );
+  }
+}
diff --git a/lib/ui/widgets/bar_select_cell_value.dart b/lib/ui/widgets/bar_select_cell_value.dart
index 581b36491edb1d2ce4d8f9be313ccdfd2df51e08..7ace4cb54c741ca357ac147567333fe755b6998a 100644
--- a/lib/ui/widgets/bar_select_cell_value.dart
+++ b/lib/ui/widgets/bar_select_cell_value.dart
@@ -1,56 +1,62 @@
 import 'dart:math';
 
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
-import 'package:sudoku/entities/cell.dart';
-import 'package:sudoku/provider/data.dart';
+import 'package:sudoku/cubit/game_cubit.dart';
+import 'package:sudoku/models/cell.dart';
+import 'package:sudoku/models/cell_location.dart';
+import 'package:sudoku/models/game.dart';
+import 'package:sudoku/ui/widgets/cell_widget_update.dart';
 
 class SelectCellValueBar extends StatelessWidget {
-  const SelectCellValueBar({super.key, required this.myProvider});
-
-  final Data myProvider;
+  const SelectCellValueBar({super.key});
 
   @override
   Widget build(BuildContext context) {
-    final Board cells = myProvider.board;
-
-    final bool isCellSelected =
-        (myProvider.selectedCellCol != null && myProvider.selectedCellRow != null);
-    final bool isUpdatableCellSelected = isCellSelected
-        ? !cells[myProvider.selectedCellRow ?? 0][myProvider.selectedCellCol ?? 0].isFixed
-        : false;
-    final int maxValue = myProvider.blockSizeHorizontal * myProvider.blockSizeVertical;
-
-    const int maxItemsPerLine = 10;
-    final int linesCount = (maxValue / maxItemsPerLine).floor() + 1;
-    final int itemsCountPerLine = min(maxItemsPerLine, maxValue + 1);
-
-    return Container(
-      margin: const EdgeInsets.all(2),
-      padding: const EdgeInsets.all(2),
-      child: Table(
-        defaultColumnWidth: const IntrinsicColumnWidth(),
-        children: [
-          for (int lineIndex = 0; lineIndex < linesCount; lineIndex++)
-            TableRow(
-              children: [
-                for (int value = lineIndex * itemsCountPerLine;
-                    value < (lineIndex + 1) * itemsCountPerLine;
-                    value++)
-                  Column(
-                    children: [
-                      Cell(
-                        row: 1,
-                        col: value,
-                        value: isUpdatableCellSelected ? (value <= maxValue ? value : -1) : -1,
-                        isFixed: false,
-                      ).widgetUpdateValue(myProvider)
-                    ],
-                  ),
-              ],
-            ),
-        ],
-      ),
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game game = gameState.game;
+
+        final bool isUpdatableCellSelected =
+            (game.selectedCell != null) ? !game.selectedCell!.isFixed : false;
+        final int maxValue = game.boardSize;
+
+        const int maxItemsPerLine = 10;
+        final int linesCount = (maxValue / maxItemsPerLine).floor() + 1;
+        final int itemsCountPerLine = min(maxItemsPerLine, maxValue + 1);
+
+        return Container(
+          margin: const EdgeInsets.all(2),
+          padding: const EdgeInsets.all(2),
+          child: Table(
+            defaultColumnWidth: const IntrinsicColumnWidth(),
+            children: [
+              for (int lineIndex = 0; lineIndex < linesCount; lineIndex++)
+                TableRow(
+                  children: [
+                    for (int value = lineIndex * itemsCountPerLine;
+                        value < (lineIndex + 1) * itemsCountPerLine;
+                        value++)
+                      Column(
+                        children: [
+                          CellWidgetUpdate(
+                            cell: Cell(
+                              location: CellLocation.go(1, value),
+                              value: isUpdatableCellSelected
+                                  ? (value <= maxValue ? value : -1)
+                                  : -1,
+                              isFixed: false,
+                            ),
+                          ),
+                        ],
+                      ),
+                  ],
+                ),
+            ],
+          ),
+        );
+      },
     );
   }
 }
diff --git a/lib/ui/widgets/button_game_restart.dart b/lib/ui/widgets/button_game_restart.dart
index a2e8b670a6cb3ebe8753baf5a7a331a53b3a5286..1da7f3934aa4b1e8184e925cbdf207d719f0d9e5 100644
--- a/lib/ui/widgets/button_game_restart.dart
+++ b/lib/ui/widgets/button_game_restart.dart
@@ -1,21 +1,27 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
-import 'package:sudoku/provider/data.dart';
-import 'package:sudoku/utils/game_utils.dart';
+import 'package:sudoku/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) {
-    return TextButton(
-      child: const Image(
-        image: AssetImage('assets/icons/button_back.png'),
-        fit: BoxFit.fill,
-      ),
-      onPressed: () => GameUtils.quitGame(myProvider),
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
+
+        return TextButton(
+          child: const Image(
+            image: AssetImage('assets/icons/button_back.png'),
+            fit: BoxFit.fill,
+          ),
+          onPressed: () {
+            gameCubit.quitGame();
+          },
+        );
+      },
     );
   }
 }
diff --git a/lib/ui/widgets/button_game_resume.dart b/lib/ui/widgets/button_game_resume.dart
deleted file mode 100644
index 41cb5688656aecce2faa0238717b9bb5d653eb9d..0000000000000000000000000000000000000000
--- a/lib/ui/widgets/button_game_resume.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-import 'package:flutter/material.dart';
-
-import 'package:sudoku/ui/layout/parameters.dart';
-import 'package:sudoku/utils/game_utils.dart';
-
-class ResumeGameButton extends Parameters {
-  const ResumeGameButton({super.key, required super.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: [
-              Column(
-                children: [
-                  TextButton(
-                    child: Parameters.buildImageContainerWidget('button_delete_saved_game'),
-                    onPressed: () => GameUtils.deleteSavedGame(myProvider),
-                  ),
-                ],
-              ),
-              Column(
-                children: [
-                  TextButton(
-                    child: Parameters.buildImageContainerWidget('button_resume_game'),
-                    onPressed: () => GameUtils.resumeSavedGame(myProvider),
-                  ),
-                ],
-              ),
-              Parameters.buildDecorationImageWidget(),
-            ],
-          ),
-        ],
-      ),
-    );
-  }
-}
diff --git a/lib/ui/widgets/button_game_start_new.dart b/lib/ui/widgets/button_game_start_new.dart
index 88e004d9a4bd18f317c22f774762916b635ac568..6cd2d639331d1a8da0f8a77806faa0d02de97379 100644
--- a/lib/ui/widgets/button_game_start_new.dart
+++ b/lib/ui/widgets/button_game_start_new.dart
@@ -1,38 +1,34 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
-import 'package:sudoku/provider/data.dart';
-import 'package:sudoku/ui/layout/parameters.dart';
-import 'package:sudoku/utils/game_utils.dart';
+import 'package:sudoku/cubit/game_cubit.dart';
+import 'package:sudoku/cubit/settings_game_cubit.dart';
+import 'package:sudoku/cubit/settings_global_cubit.dart';
 
 class StartNewGameButton extends StatelessWidget {
-  const StartNewGameButton({super.key, required this.myProvider});
-
-  final Data myProvider;
+  const StartNewGameButton({super.key});
 
   @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),
-                  ),
-                ],
+    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,
               ),
-              Parameters.buildDecorationImageWidget(),
-            ],
-          ),
-        ],
-      ),
+            );
+          },
+        );
+      },
     );
   }
 }
diff --git a/lib/ui/widgets/cell_widget.dart b/lib/ui/widgets/cell_widget.dart
new file mode 100644
index 0000000000000000000000000000000000000000..eb697a0fc709e1b5c777b7e7bbda2bb314c91805
--- /dev/null
+++ b/lib/ui/widgets/cell_widget.dart
@@ -0,0 +1,171 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:sudoku/cubit/game_cubit.dart';
+import 'package:sudoku/models/cell.dart';
+import 'package:sudoku/models/game.dart';
+
+class CellWidget extends StatelessWidget {
+  const CellWidget({super.key, required this.cell});
+
+  final Cell cell;
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game game = gameState.game;
+
+        final String imageAsset = getImageAssetName(game);
+
+        return Container(
+          decoration: BoxDecoration(
+            color: getBackgroundColor(game),
+            border: getCellBorders(game),
+          ),
+          child: GestureDetector(
+            child: AnimatedSwitcher(
+              duration: const Duration(milliseconds: 100),
+              transitionBuilder: (Widget child, Animation<double> animation) {
+                return ScaleTransition(scale: animation, child: child);
+              },
+              child: Image(
+                image: AssetImage(imageAsset),
+                fit: BoxFit.fill,
+                key: ValueKey<int>(imageAsset.hashCode),
+              ),
+            ),
+            onTap: () {
+              final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
+
+              if (cell.location.col != game.selectedCell?.location.col ||
+                  cell.location.row != game.selectedCell?.location.row) {
+                gameCubit.selectCell(cell.location);
+              } else {
+                gameCubit.unselectCell();
+              }
+            },
+          ),
+        );
+      },
+    );
+  }
+
+  /*
+  * Compute image asset name, from skin and cell value/state
+  */
+  String getImageAssetName(Game game) {
+    if ((cell.value) > 0) {
+      final int cellValue = game.getTranslatedValueForDisplay(cell.value);
+      return 'assets/skins/${game.globalSettings.skin}_$cellValue.png';
+    }
+
+    return 'assets/icons/cell_empty.png';
+  }
+
+  // Compute cell background color, from cell state
+  Color getBackgroundColor(Game game) {
+    final Color editableCellColor = Colors.grey.shade100;
+    final Color editableCellColorConflict = Colors.pink.shade100;
+    final Color fixedCellColor = Colors.grey.shade300;
+    final Color fixedCellColorConflict = Colors.pink.shade200;
+    final Color editableSelectedValueColor = Colors.green.shade100;
+    final Color fixedSelectedValueColor = Colors.green.shade300;
+    final Color editableAnimated = Colors.green.shade200;
+    final Color fixedAnimated = Colors.green.shade300;
+
+    Color backgroundColor = editableCellColor;
+
+    if (cell.isFixed == true) {
+      backgroundColor = fixedCellColor;
+    }
+
+    final int conflictsCount = game.boardConflicts[cell.location.row][cell.location.col];
+
+    if (game.showConflicts == true) {
+      if (conflictsCount != 0) {
+        if (cell.isFixed == true) {
+          backgroundColor = fixedCellColorConflict;
+        } else {
+          backgroundColor = editableCellColorConflict;
+        }
+      }
+
+      if ((cell.value != 0) && (cell.value == game.selectedCell?.value)) {
+        if (cell.isFixed == true) {
+          backgroundColor = fixedSelectedValueColor;
+        } else {
+          backgroundColor = editableSelectedValueColor;
+        }
+      }
+    }
+
+    final bool isAnimated = game.boardAnimated[cell.location.row][cell.location.col];
+
+    if (isAnimated) {
+      if (cell.isFixed == true) {
+        backgroundColor = fixedAnimated;
+      } else {
+        backgroundColor = editableAnimated;
+      }
+    }
+
+    return backgroundColor;
+  }
+
+  // Compute cell borders, from board size and cell state
+  Border getCellBorders(Game game) {
+    const Color cellBorderDarkColor = Colors.black;
+    const Color cellBorderLightColor = Colors.grey;
+    const Color cellBorderSelectedColor = Colors.red;
+
+    Color cellBorderColor = cellBorderSelectedColor;
+    double cellBorderWidth = 4;
+
+    // Reduce cell border width on big boards
+    if (game.boardSize > 8) {
+      cellBorderWidth = 2;
+      if (game.boardSize > 10) {
+        cellBorderWidth = 1;
+      }
+    }
+
+    if (!game.isRunning) {
+      cellBorderColor = Colors.green.shade700;
+    }
+
+    Border borders = Border.all(
+      color: cellBorderColor,
+      width: cellBorderWidth,
+    );
+
+    // Update cell borders if not currently selected cell
+    if (cell.location.col != game.selectedCell?.location.col ||
+        cell.location.row != game.selectedCell?.location.row) {
+      borders = Border(
+        top: BorderSide(
+            width: cellBorderWidth,
+            color: (((cell.location.row) % game.blockSizeVertical) == 0)
+                ? cellBorderDarkColor
+                : cellBorderLightColor),
+        left: BorderSide(
+            width: cellBorderWidth,
+            color: (((cell.location.col) % game.blockSizeHorizontal) == 0)
+                ? cellBorderDarkColor
+                : cellBorderLightColor),
+        right: BorderSide(
+            width: cellBorderWidth,
+            color: ((((cell.location.col) + 1) % game.blockSizeHorizontal) == 0)
+                ? cellBorderDarkColor
+                : cellBorderLightColor),
+        bottom: BorderSide(
+            width: cellBorderWidth,
+            color: ((((cell.location.row) + 1) % game.blockSizeVertical) == 0)
+                ? cellBorderDarkColor
+                : cellBorderLightColor),
+      );
+    }
+
+    return borders;
+  }
+}
diff --git a/lib/ui/widgets/cell_widget_update.dart b/lib/ui/widgets/cell_widget_update.dart
new file mode 100644
index 0000000000000000000000000000000000000000..a8bb005faf9b6d684a23af474cb90ab85c93c5c6
--- /dev/null
+++ b/lib/ui/widgets/cell_widget_update.dart
@@ -0,0 +1,71 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:sudoku/cubit/game_cubit.dart';
+import 'package:sudoku/models/cell.dart';
+import 'package:sudoku/models/game.dart';
+
+class CellWidgetUpdate extends StatelessWidget {
+  const CellWidgetUpdate({super.key, this.cell});
+
+  final Cell? cell;
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game game = gameState.game;
+
+        if ((cell?.value ?? 0) < 0) {
+          return Container();
+        }
+
+        final String imageAsset = getImageAssetName(game);
+
+        Color backgroundColor = Colors.grey.shade200;
+
+        if (game.showConflicts == true &&
+            game.selectedCell?.location.col != null &&
+            game.selectedCell?.location.row != null) {
+          if (!game.board.isValueAllowed(game.selectedCell?.location, cell?.value ?? 0)) {
+            backgroundColor = Colors.pink.shade100;
+          }
+        }
+
+        return Container(
+          decoration: BoxDecoration(
+            color: backgroundColor,
+            border: Border.all(
+              color: Colors.black,
+              width: 2,
+            ),
+          ),
+          child: GestureDetector(
+            child: Image(image: AssetImage(imageAsset), fit: BoxFit.fill),
+            onTap: () {
+              final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
+
+              if (game.selectedCell != null) {
+                gameCubit.updateCellValue(game.selectedCell!.location, cell?.value ?? 0);
+              }
+
+              gameCubit.unselectCell();
+            },
+          ),
+        );
+      },
+    );
+  }
+
+  /*
+  * Compute image asset name, from skin and cell value/state
+  */
+  String getImageAssetName(Game game) {
+    if ((cell?.value ?? 0) > 0) {
+      final int cellValue = game.getTranslatedValueForDisplay(cell?.value ?? 0);
+      return 'assets/skins/${game.globalSettings.skin}_$cellValue.png';
+    }
+
+    return 'assets/icons/cell_empty.png';
+  }
+}
diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart
new file mode 100644
index 0000000000000000000000000000000000000000..558340540c5791c5123732dd8a46dda008bac102
--- /dev/null
+++ b/lib/ui/widgets/global_app_bar.dart
@@ -0,0 +1,113 @@
+import 'package:badges/badges.dart' as badges;
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:overlay_support/overlay_support.dart';
+
+import 'package:sudoku/config/default_global_settings.dart';
+import 'package:sudoku/cubit/game_cubit.dart';
+import 'package:sudoku/models/game.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) {
+        final Game game = gameState.game;
+
+        final List<Widget> menuActions = [];
+
+        if (game.isRunning && !game.isFinished) {
+          final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
+
+          menuActions.add(TextButton(
+            child: Container(
+              decoration: BoxDecoration(
+                borderRadius: BorderRadius.circular(4),
+                border: Border.all(
+                  color: Colors.white,
+                  width: 3,
+                ),
+              ),
+              child: const Image(
+                image: AssetImage('assets/icons/button_back.png'),
+                fit: BoxFit.fill,
+              ),
+            ),
+            onPressed: () => toast('Long press to quit game...'),
+            onLongPress: () {
+              gameCubit.quitGame();
+            },
+          ));
+          menuActions.add(const Spacer(flex: 6));
+          menuActions.add(TextButton(
+            child: Container(
+              decoration: BoxDecoration(
+                borderRadius: BorderRadius.circular(4),
+                border: Border.all(
+                  color: Colors.white,
+                  width: 3,
+                ),
+              ),
+              child: badges.Badge(
+                showBadge: game.givenTipsCount == 0 ? false : true,
+                badgeStyle: badges.BadgeStyle(
+                  badgeColor: game.givenTipsCount < 10
+                      ? Colors.green
+                      : game.givenTipsCount < 20
+                          ? Colors.orange
+                          : Colors.red,
+                ),
+                badgeContent: Text(
+                  game.givenTipsCount == 0 ? '' : game.givenTipsCount.toString(),
+                  style: const TextStyle(color: Colors.white),
+                ),
+                child: Container(
+                  padding: EdgeInsets.all(15 *
+                      game.buttonTipsCountdown /
+                      DefaultGlobalSettings.defaultTipCountDownValueInSeconds),
+                  child: const Image(
+                    image: AssetImage('assets/icons/button_help.png'),
+                    fit: BoxFit.fill,
+                  ),
+                ),
+              ),
+            ),
+            onPressed: () {
+              final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
+              game.canGiveTip() ? game.showTip(gameCubit) : null;
+            },
+          ));
+          menuActions.add(const Spacer());
+          menuActions.add(TextButton(
+            child: Container(
+              decoration: BoxDecoration(
+                borderRadius: BorderRadius.circular(4),
+                border: Border.all(
+                  color: game.showConflicts == true ? Colors.blue : Colors.white,
+                  width: 3,
+                ),
+              ),
+              child: const Image(
+                image: AssetImage('assets/icons/button_show_conflicts.png'),
+                fit: BoxFit.fill,
+              ),
+            ),
+            onPressed: () {
+              gameCubit.toggleShowConflicts();
+            },
+          ));
+        }
+
+        return AppBar(
+          title: const SizedBox.shrink(),
+          actions: menuActions,
+        );
+      },
+    );
+  }
+
+  @override
+  Size get preferredSize => const Size.fromHeight(50);
+}
diff --git a/lib/ui/widgets/message_game_end.dart b/lib/ui/widgets/message_game_end.dart
index 168e055e50074f24ad953566e5c52d9df8633e5d..8716d31d8ea27bd364cfe71b8c504e6484525e7a 100644
--- a/lib/ui/widgets/message_game_end.dart
+++ b/lib/ui/widgets/message_game_end.dart
@@ -1,45 +1,51 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
+import 'package:sudoku/cubit/game_cubit.dart';
+import 'package:sudoku/models/game.dart';
 import 'package:sudoku/ui/widgets/button_game_restart.dart';
-import 'package:sudoku/provider/data.dart';
 
 class EndGameMessage extends StatelessWidget {
-  const EndGameMessage({super.key, required this.myProvider});
-
-  final Data myProvider;
+  const EndGameMessage({super.key});
 
   @override
   Widget build(BuildContext context) {
-    const Image decorationImage = Image(
-      image: AssetImage('assets/icons/game_win.png'),
-      fit: BoxFit.fill,
-    );
+    return BlocBuilder<GameCubit, GameState>(
+      builder: (BuildContext context, GameState gameState) {
+        final Game game = gameState.game;
+
+        const Image decorationImage = Image(
+          image: AssetImage('assets/icons/game_win.png'),
+          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: [
-              const Column(
-                children: [decorationImage],
-              ),
-              Column(
+              TableRow(
                 children: [
-                  myProvider.animationInProgress
-                      ? decorationImage
-                      : RestartGameButton(myProvider: myProvider)
+                  const Column(
+                    children: [decorationImage],
+                  ),
+                  Column(
+                    children: [
+                      game.animationInProgress == true
+                          ? decorationImage
+                          : const RestartGameButton()
+                    ],
+                  ),
+                  const Column(
+                    children: [decorationImage],
+                  ),
                 ],
               ),
-              const Column(
-                children: [decorationImage],
-              ),
             ],
           ),
-        ],
-      ),
+        );
+      },
     );
   }
 }
diff --git a/lib/ui/widgets/parameter_image.dart b/lib/ui/widgets/parameter_image.dart
new file mode 100644
index 0000000000000000000000000000000000000000..54787e2c0a15c8f84c218181e7f06b9624a5d88d
--- /dev/null
+++ b/lib/ui/widgets/parameter_image.dart
@@ -0,0 +1,38 @@
+import 'package:flutter/material.dart';
+
+class ParameterImage extends StatelessWidget {
+  const ParameterImage({
+    super.key,
+    required this.code,
+    required this.value,
+    required this.isSelected,
+  });
+
+  final String code;
+  final String value;
+  final bool isSelected;
+
+  static const Color buttonBackgroundColor = Colors.white;
+  static const Color buttonBorderColorActive = Colors.blue;
+  static const Color buttonBorderColorInactive = Colors.white;
+  static const double buttonBorderWidth = 8.0;
+  static const double buttonBorderRadius = 8.0;
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      decoration: BoxDecoration(
+        color: buttonBackgroundColor,
+        borderRadius: BorderRadius.circular(buttonBorderRadius),
+        border: Border.all(
+          color: isSelected ? buttonBorderColorActive : buttonBorderColorInactive,
+          width: buttonBorderWidth,
+        ),
+      ),
+      child: Image(
+        image: AssetImage('assets/icons/${code}_$value.png'),
+        fit: BoxFit.fill,
+      ),
+    );
+  }
+}
diff --git a/lib/utils/board_animate.dart b/lib/utils/board_animate.dart
index dd28298a282b09ec9ce4b7d1ce82903616b46dee..40adc186efb3ecb677b26ea1986d628aad06bb3b 100644
--- a/lib/utils/board_animate.dart
+++ b/lib/utils/board_animate.dart
@@ -1,20 +1,21 @@
 import 'dart:async';
 
-import 'package:sudoku/provider/data.dart';
+import 'package:sudoku/cubit/game_cubit.dart';
+import 'package:sudoku/models/game.dart';
+import 'package:sudoku/models/types.dart';
 
 class BoardAnimate {
   // Start game animation: blinking tiles
-  static AnimatedBoardSequence createStartGameAnimationPatterns(Data myProvider) {
+  static AnimatedBoardSequence createStartGameAnimationPatterns(Game game) {
     AnimatedBoardSequence patterns = [];
 
     int patternsCount = 3;
-    int boardSideLength = myProvider.blockSizeHorizontal * myProvider.blockSizeVertical;
 
     for (int patternIndex = 0; patternIndex < patternsCount; patternIndex++) {
       AnimatedBoard pattern = [];
-      for (int row = 0; row < boardSideLength; row++) {
+      for (int row = 0; row < game.boardSize; row++) {
         List<bool> patternRow = [];
-        for (int col = 0; col < boardSideLength; col++) {
+        for (int col = 0; col < game.boardSize; col++) {
           patternRow.add(((patternIndex + row + col) % 2 == 0));
         }
         pattern.add(patternRow);
@@ -26,17 +27,16 @@ class BoardAnimate {
   }
 
   // Win game animation: fill board with colored rows, from bottom to top
-  static AnimatedBoardSequence createWinGameAnimationPatterns(Data myProvider) {
+  static AnimatedBoardSequence createWinGameAnimationPatterns(Game game) {
     AnimatedBoardSequence patterns = [];
 
-    int boardSideLength = myProvider.blockSizeHorizontal * myProvider.blockSizeVertical;
-    int patternsCount = boardSideLength + 6;
+    int patternsCount = game.boardSize + 6;
 
     for (int patternIndex = 0; patternIndex < patternsCount; patternIndex++) {
       AnimatedBoard pattern = [];
-      for (int row = 0; row < boardSideLength; row++) {
+      for (int row = 0; row < game.boardSize; row++) {
         List<bool> patternRow = [];
-        for (int col = 0; col < boardSideLength; col++) {
+        for (int col = 0; col < game.boardSize; col++) {
           patternRow.add(row > (patternIndex - 4));
         }
         pattern.add(patternRow);
@@ -48,17 +48,17 @@ class BoardAnimate {
   }
 
   // Default multi-purpose animation: sliding stripes, from top left to right bottom
-  static AnimatedBoardSequence createDefaultAnimationPatterns(Data myProvider) {
+  static AnimatedBoardSequence createDefaultAnimationPatterns(Game game) {
     AnimatedBoardSequence patterns = [];
 
-    int boardSideLength = myProvider.blockSizeHorizontal * myProvider.blockSizeVertical;
+    int boardSideLength = game.boardSize;
     int patternsCount = boardSideLength;
 
     for (int patternIndex = 0; patternIndex < patternsCount; patternIndex++) {
       AnimatedBoard pattern = [];
-      for (int row = 0; row < boardSideLength; row++) {
+      for (int row = 0; row < game.boardSize; row++) {
         List<bool> patternRow = [];
-        for (int col = 0; col < boardSideLength; col++) {
+        for (int col = 0; col < game.boardSize; col++) {
           patternRow.add(((patternIndex + row + col) % 4 == 0));
         }
         pattern.add(patternRow);
@@ -69,23 +69,25 @@ class BoardAnimate {
     return patterns;
   }
 
-  static void startAnimation(Data myProvider, String animationType) {
+  static void startAnimation(GameCubit gameCubit, String animationType) {
+    final Game game = gameCubit.state.game;
+
     AnimatedBoardSequence patterns = [];
 
     switch (animationType) {
       case 'start':
-        patterns = createStartGameAnimationPatterns(myProvider);
+        patterns = createStartGameAnimationPatterns(game);
         break;
       case 'win':
-        patterns = createWinGameAnimationPatterns(myProvider);
+        patterns = createWinGameAnimationPatterns(game);
         break;
       default:
-        patterns = createDefaultAnimationPatterns(myProvider);
+        patterns = createDefaultAnimationPatterns(game);
     }
 
     int patternIndex = patterns.length;
 
-    myProvider.updateAnimationInProgress(true);
+    gameCubit.updateAnimationInProgress(true);
 
     const interval = Duration(milliseconds: 200);
     Timer.periodic(
@@ -93,11 +95,11 @@ class BoardAnimate {
       (Timer timer) {
         if (patternIndex == 0) {
           timer.cancel();
-          myProvider.resetAnimatedBackground();
-          myProvider.updateAnimationInProgress(false);
+          gameCubit.resetAnimatedBackground();
+          gameCubit.updateAnimationInProgress(false);
         } else {
           patternIndex--;
-          myProvider.setAnimatedBackground(patterns[patternIndex]);
+          gameCubit.setAnimatedBackground(patterns[patternIndex]);
         }
       },
     );
diff --git a/lib/utils/board_utils.dart b/lib/utils/board_utils.dart
index 8f0a835da2297b5fafebf68e5ba164ba718a8bee..491c3d1a07e3677aad8aad21b3998ee60ba86690 100644
--- a/lib/utils/board_utils.dart
+++ b/lib/utils/board_utils.dart
@@ -1,59 +1,19 @@
 import 'dart:math';
 
-import 'package:sudoku/entities/cell.dart';
-import 'package:sudoku/provider/data.dart';
-import 'package:sudoku/utils/random_pick_grid.dart';
+import 'package:sudoku/models/board.dart';
+import 'package:sudoku/models/cell.dart';
+import 'package:sudoku/models/cell_location.dart';
+import 'package:sudoku/models/types.dart';
 import 'package:sudoku/utils/tools.dart';
 
 class BoardUtils {
-  static printGrid(Board cells, Board solvedCells) {
-    String stringValues = '0123456789ABCDEFG';
-    printlog('');
-    printlog('-------');
-    for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) {
-      String row = '';
-      String rowSolved = '';
-      for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) {
-        row += stringValues[cells[rowIndex][colIndex].value];
-        rowSolved += stringValues[solvedCells[rowIndex][colIndex].value];
-      }
-      printlog('$row | $rowSolved');
-    }
-    printlog('-------');
-    printlog('');
-  }
-
-  static Future<void> pickGrid(Data myProvider) async {
-    String grid = '';
-    RandomPickGrid randomPickGrid;
-
-    randomPickGrid = RandomPickGrid();
-    await randomPickGrid.init(myProvider.parameterLevel, myProvider.parameterSize);
-
-    grid = randomPickGrid.grid;
-
-    final int blockSizeHorizontal = myProvider.blockSizeHorizontal;
-    final int blockSizeVertical = myProvider.blockSizeVertical;
-
-    if (grid.length == pow(blockSizeHorizontal * blockSizeVertical, 2)) {
-      printlog('Picked grid from template: $grid');
-      bool isSymetric = (blockSizeHorizontal == blockSizeVertical);
-      myProvider.updateCells(BoardUtils.createBoardFromTemplate(grid, isSymetric));
-      myProvider.updateCellsSolved(BoardUtils.getSolvedGrid(myProvider));
-      myProvider.selectCell(null, null);
-
-      printGrid(myProvider.board, myProvider.boardSolved);
-    }
-  }
-
-  static Board createEmptyBoard(final int boardSize) {
-    final Board cells = [];
+  static BoardCells createEmptyBoard(final int boardSize) {
+    final BoardCells cells = [];
     for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) {
       final List<Cell> row = [];
       for (int colIndex = 0; colIndex < boardSize; colIndex++) {
         row.add(Cell(
-          row: rowIndex,
-          col: colIndex,
+          location: CellLocation.go(rowIndex, colIndex),
           value: 0,
           isFixed: false,
         ));
@@ -64,56 +24,12 @@ class BoardUtils {
     return cells;
   }
 
-  static Board createBoardFromSavedState(Data myProvider, String savedBoard) {
-    final Board cells = [];
-    final int boardSize = int.parse(pow((savedBoard.length / 2), 1 / 2).toStringAsFixed(0));
-
-    const String stringValues = '0123456789ABCDEFG';
-
-    int index = 0;
-    for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) {
-      final List<Cell> row = [];
-      for (int colIndex = 0; colIndex < boardSize; colIndex++) {
-        final String stringValue = savedBoard[index++];
-        final int value = stringValues.indexOf(stringValue);
-
-        final String isFixedString = savedBoard[index++];
-        final bool isFixed = (isFixedString != ' ');
-
-        row.add(Cell(
-          row: rowIndex,
-          col: colIndex,
-          value: value,
-          isFixed: isFixed,
-        ));
-      }
-      cells.add(row);
-    }
-
-    return cells;
-  }
-
-  static Board copyBoard(List cells) {
-    final Board copiedGrid = [];
-    for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) {
-      final List<Cell> row = [];
-      for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) {
-        row.add(Cell(
-          row: rowIndex,
-          col: colIndex,
-          value: cells[rowIndex][colIndex].value,
-          isFixed: false,
-        ));
-      }
-      copiedGrid.add(row);
-    }
-
-    return copiedGrid;
-  }
-
-  static Board createBoardFromTemplate(String grid, bool isSymetric) {
-    Board cells = [];
-    final int boardSize = int.parse(pow(grid.length, 1 / 2).toStringAsFixed(0));
+  static Board createBoardFromTemplate({
+    required String template,
+    required bool isSymetric,
+  }) {
+    BoardCells cells = [];
+    final int boardSize = int.parse(pow(template.length, 1 / 2).toStringAsFixed(0));
 
     const String stringValues = '0123456789ABCDEFG';
 
@@ -121,11 +37,10 @@ class BoardUtils {
     for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) {
       final List<Cell> row = [];
       for (int colIndex = 0; colIndex < boardSize; colIndex++) {
-        final String stringValue = grid[index++];
+        final String stringValue = template[index++];
         final int value = stringValues.indexOf(stringValue);
         row.add(Cell(
-          row: rowIndex,
-          col: colIndex,
+          location: CellLocation.go(rowIndex, colIndex),
           value: value,
           isFixed: (value != 0),
         ));
@@ -151,12 +66,11 @@ class BoardUtils {
     switch (flip) {
       case 'horizontal':
         {
-          final Board transformedBoard = createEmptyBoard(boardSize);
+          final BoardCells transformedBoard = createEmptyBoard(boardSize);
           for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) {
             for (int colIndex = 0; colIndex < boardSize; colIndex++) {
               transformedBoard[rowIndex][colIndex] = Cell(
-                row: rowIndex,
-                col: colIndex,
+                location: CellLocation.go(rowIndex, colIndex),
                 value: cells[boardSize - rowIndex - 1][colIndex].value,
                 isFixed: false,
               );
@@ -167,12 +81,11 @@ class BoardUtils {
         break;
       case 'vertical':
         {
-          final Board transformedBoard = createEmptyBoard(boardSize);
+          final BoardCells transformedBoard = createEmptyBoard(boardSize);
           for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) {
             for (int colIndex = 0; colIndex < boardSize; colIndex++) {
               transformedBoard[rowIndex][colIndex] = Cell(
-                row: rowIndex,
-                col: colIndex,
+                location: CellLocation.go(rowIndex, colIndex),
                 value: cells[rowIndex][boardSize - colIndex - 1].value,
                 isFixed: false,
               );
@@ -186,12 +99,11 @@ class BoardUtils {
     switch (rotate) {
       case 'left':
         {
-          final Board transformedBoard = createEmptyBoard(boardSize);
+          final BoardCells transformedBoard = createEmptyBoard(boardSize);
           for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) {
             for (int colIndex = 0; colIndex < boardSize; colIndex++) {
               transformedBoard[rowIndex][colIndex] = Cell(
-                row: rowIndex,
-                col: colIndex,
+                location: CellLocation.go(rowIndex, colIndex),
                 value: cells[colIndex][boardSize - rowIndex - 1].value,
                 isFixed: false,
               );
@@ -202,12 +114,11 @@ class BoardUtils {
         break;
       case 'right':
         {
-          final Board transformedBoard = createEmptyBoard(boardSize);
+          final BoardCells transformedBoard = createEmptyBoard(boardSize);
           for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) {
             for (int colIndex = 0; colIndex < boardSize; colIndex++) {
               transformedBoard[rowIndex][colIndex] = Cell(
-                row: rowIndex,
-                col: colIndex,
+                location: CellLocation.go(rowIndex, colIndex),
                 value: cells[boardSize - colIndex - 1][rowIndex].value,
                 isFixed: false,
               );
@@ -222,293 +133,15 @@ class BoardUtils {
     for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) {
       for (int colIndex = 0; colIndex < boardSize; colIndex++) {
         cells[rowIndex][colIndex] = Cell(
-          row: rowIndex,
-          col: colIndex,
+          location: CellLocation.go(rowIndex, colIndex),
           value: cells[rowIndex][colIndex].value,
           isFixed: (cells[rowIndex][colIndex].value != 0) ? true : false,
         );
       }
     }
 
-    return cells;
-  }
-
-  static bool checkBoardIsSolved(Data myProvider) {
-    final Board cells = myProvider.board;
-    final ConflictsCount conflicts = myProvider.boardConflicts;
-
-    final int blockSizeHorizontal = myProvider.blockSizeHorizontal;
-    final int blockSizeVertical = myProvider.blockSizeVertical;
-
-    final int boardSize = blockSizeHorizontal * blockSizeVertical;
-
-    // (re)compute conflicts
-    BoardUtils.computeConflictsInBoard(myProvider);
-
-    // check grid is fully completed and does not contain conflict
-    for (int row = 0; row < boardSize; row++) {
-      for (int col = 0; col < boardSize; col++) {
-        if (cells[row][col].value == 0 || conflicts[row][col] != 0) {
-          return false;
-        }
-      }
-    }
-
-    printlog('-> ok sudoku solved!');
-
-    return true;
-  }
-
-  static bool isValueAllowed(
-    Board cells,
-    int blockSizeHorizontal,
-    int blockSizeVertical,
-    int? candidateCol,
-    int? candidateRow,
-    int candidateValue,
-  ) {
-    if ((candidateCol == null) || (candidateRow == null) || (candidateValue == 0)) {
-      return true;
-    }
-
-    final int boardSize = blockSizeHorizontal * blockSizeVertical;
-
-    // check lines does not contains a value twice
-    for (int row = 0; row < boardSize; row++) {
-      final List<int> values = [];
-      for (int col = 0; col < boardSize; col++) {
-        int value = cells[row][col].value;
-        if (row == candidateRow && col == candidateCol) {
-          value = candidateValue;
-        }
-        if (value != 0) {
-          values.add(value);
-        }
-      }
-      final List<int> distinctValues = values.toSet().toList();
-      if (values.length != distinctValues.length) {
-        return false;
-      }
-    }
-
-    // check columns does not contains a value twice
-    for (int col = 0; col < boardSize; col++) {
-      final List<int> values = [];
-      for (int row = 0; row < boardSize; row++) {
-        int value = cells[row][col].value;
-        if (row == candidateRow && col == candidateCol) {
-          value = candidateValue;
-        }
-        if (value != 0) {
-          values.add(value);
-        }
-      }
-      final List<int> distinctValues = values.toSet().toList();
-      if (values.length != distinctValues.length) {
-        return false;
-      }
-    }
-
-    // check blocks does not contains a value twice
-    final int horizontalBlocksCount = blockSizeVertical;
-    final int verticalBlocksCount = blockSizeHorizontal;
-    for (int blockRow = 0; blockRow < verticalBlocksCount; blockRow++) {
-      for (int blockCol = 0; blockCol < horizontalBlocksCount; blockCol++) {
-        final List<int> values = [];
-
-        for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) {
-          for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) {
-            final int row = (blockRow * blockSizeVertical) + rowInBlock;
-            final int col = (blockCol * blockSizeHorizontal) + colInBlock;
-            int value = cells[row][col].value;
-            if (row == candidateRow && col == candidateCol) {
-              value = candidateValue;
-            }
-            if (value != 0) {
-              values.add(value);
-            }
-          }
-        }
-
-        final List<int> distinctValues = values.toSet().toList();
-        if (values.length != distinctValues.length) {
-          return false;
-        }
-      }
-    }
-
-    return true;
-  }
-
-  static void computeConflictsInBoard(Data myProvider) {
-    final Board cells = myProvider.board;
-    final ConflictsCount conflicts = myProvider.boardConflicts;
-
-    final int blockSizeHorizontal = myProvider.blockSizeHorizontal;
-    final int blockSizeVertical = myProvider.blockSizeVertical;
-
-    final int boardSize = blockSizeHorizontal * blockSizeVertical;
-
-    // reset conflict states
-    for (int row = 0; row < boardSize; row++) {
-      for (int col = 0; col < boardSize; col++) {
-        conflicts[row][col] = 0;
-      }
-    }
-
-    // check lines does not contains a value twice
-    for (int row = 0; row < boardSize; row++) {
-      final List<int> values = [];
-      for (int col = 0; col < boardSize; col++) {
-        int value = cells[row][col].value;
-        if (value != 0) {
-          values.add(value);
-        }
-      }
-      final List<int> distinctValues = values.toSet().toList();
-      if (values.length != distinctValues.length) {
-        printlog('line $row contains duplicates');
-        // Add line to cells in conflict
-        for (int col = 0; col < boardSize; col++) {
-          conflicts[row][col]++;
-        }
-      }
-    }
-
-    // check columns does not contains a value twice
-    for (int col = 0; col < boardSize; col++) {
-      final List<int> values = [];
-      for (int row = 0; row < boardSize; row++) {
-        int value = cells[row][col].value;
-        if (value != 0) {
-          values.add(value);
-        }
-      }
-      final List<int> distinctValues = values.toSet().toList();
-      if (values.length != distinctValues.length) {
-        printlog('column $col contains duplicates');
-        // Add column to cells in conflict
-        for (int row = 0; row < boardSize; row++) {
-          conflicts[row][col]++;
-        }
-      }
-    }
-
-    // check blocks does not contains a value twice
-    final int horizontalBlocksCount = blockSizeVertical;
-    final int verticalBlocksCount = blockSizeHorizontal;
-    for (int blockRow = 0; blockRow < verticalBlocksCount; blockRow++) {
-      for (int blockCol = 0; blockCol < horizontalBlocksCount; blockCol++) {
-        List<int> values = [];
-
-        for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) {
-          for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) {
-            int row = (blockRow * blockSizeVertical) + rowInBlock;
-            int col = (blockCol * blockSizeHorizontal) + colInBlock;
-            int value = cells[row][col].value;
-            if (value != 0) {
-              values.add(value);
-            }
-          }
-        }
-
-        List<int> distinctValues = values.toSet().toList();
-        if (values.length != distinctValues.length) {
-          printlog('block [$blockCol,$blockRow] contains duplicates');
-          // Add blocks to cells in conflict
-          for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) {
-            for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) {
-              int row = (blockRow * blockSizeVertical) + rowInBlock;
-              int col = (blockCol * blockSizeHorizontal) + colInBlock;
-              conflicts[row][col]++;
-            }
-          }
-        }
-      }
-    }
-
-    myProvider.updateConflicts(conflicts);
-  }
-
-  static List<List<int>> getCellsWithWrongValue(
-    final Board cells,
-    final Board cellsSolved,
-    final int blockSizeHorizontal,
-    final int blockSizeVertical,
-  ) {
-    final List<List<int>> cellsWithWrongValue = [];
-    final int boardSize = blockSizeHorizontal * blockSizeVertical;
-
-    for (int row = 0; row < boardSize; row++) {
-      for (int col = 0; col < boardSize; col++) {
-        if (cells[row][col].value != 0 &&
-            cells[row][col].value != cellsSolved[row][col].value) {
-          cellsWithWrongValue.add([col, row]);
-        }
-      }
-    }
-
-    return cellsWithWrongValue;
-  }
-
-  static List<List<int>> getCellsWithUniqueAvailableValue(
-    Board cells,
-    final int blockSizeHorizontal,
-    final int blockSizeVertical,
-  ) {
-    final List<List<int>> candidateCells = [];
-    final int boardSize = blockSizeHorizontal * blockSizeVertical;
-
-    for (int row = 0; row < boardSize; row++) {
-      for (int col = 0; col < boardSize; col++) {
-        if (cells[row][col].value == 0) {
-          int allowedValuesCount = 0;
-          int candidateValue = 0;
-          for (int value = 1; value <= boardSize; value++) {
-            if (BoardUtils.isValueAllowed(
-                cells, blockSizeHorizontal, blockSizeVertical, col, row, value)) {
-              candidateValue = value;
-              allowedValuesCount++;
-            }
-          }
-          if (allowedValuesCount == 1) {
-            candidateCells.add([col, row, candidateValue]);
-          }
-        }
-      }
-    }
-
-    return candidateCells;
-  }
-
-  static Board getSolvedGrid(Data myProvider) {
-    final Board cells = copyBoard(myProvider.board);
-    final int blockSizeHorizontal = myProvider.blockSizeHorizontal;
-    int blockSizeVertical = myProvider.blockSizeVertical;
-
-    do {
-      List<List<int>> cellsWithUniqueAvailableValue =
-          BoardUtils.getCellsWithUniqueAvailableValue(
-        cells,
-        blockSizeHorizontal,
-        blockSizeVertical,
-      );
-      if (cellsWithUniqueAvailableValue.isEmpty) {
-        break;
-      }
-      for (int i = 0; i < cellsWithUniqueAvailableValue.length; i++) {
-        int col = cellsWithUniqueAvailableValue[i][0];
-        int row = cellsWithUniqueAvailableValue[i][1];
-        int value = cellsWithUniqueAvailableValue[i][2];
-        cells[row][col] = Cell(
-          row: row,
-          col: col,
-          value: value,
-          isFixed: cells[row][col].isFixed,
-        );
-      }
-    } while (true);
-
-    return cells;
+    return Board.createNew(
+      cells: cells,
+    );
   }
 }
diff --git a/lib/utils/game_utils.dart b/lib/utils/game_utils.dart
deleted file mode 100644
index 4de2454cd2d382bc54cbe327f308ed9a7546a278..0000000000000000000000000000000000000000
--- a/lib/utils/game_utils.dart
+++ /dev/null
@@ -1,147 +0,0 @@
-import 'package:sudoku/provider/data.dart';
-import 'package:sudoku/utils/board_animate.dart';
-import 'package:sudoku/utils/board_utils.dart';
-import 'package:sudoku/utils/tools.dart';
-
-class GameUtils {
-  static Future<void> quitGame(Data myProvider) async {
-    myProvider.updateGameIsRunning(false);
-    if (BoardUtils.checkBoardIsSolved(myProvider)) {
-      myProvider.resetCurrentSavedState();
-    }
-  }
-
-  static Future<void> startNewGame(Data myProvider) async {
-    myProvider.updateParameterSize(myProvider.parameterSize);
-    myProvider.updateGameIsRunning(true);
-    myProvider.resetGivenTipsCount();
-    myProvider.shuffleCellValues();
-    myProvider.updateCells(BoardUtils.createEmptyBoard(
-        myProvider.blockSizeHorizontal * myProvider.blockSizeVertical));
-    myProvider.initAnimatedBackground();
-    myProvider.initConflictsBoard();
-    BoardUtils.pickGrid(myProvider);
-    BoardAnimate.startAnimation(myProvider, 'start');
-  }
-
-  static void deleteSavedGame(Data myProvider) {
-    myProvider.resetCurrentSavedState();
-  }
-
-  static void resumeSavedGame(Data myProvider) {
-    final Map<String, dynamic> savedState = myProvider.getCurrentSavedState();
-    if (savedState.isNotEmpty) {
-      try {
-        myProvider.setParameterValue('level', savedState['level']);
-        myProvider.setParameterValue('size', savedState['size']);
-        myProvider.setParameterValue('skin', savedState['skin']);
-
-        myProvider.setGivenTipsCount(savedState['tipsCount']);
-        myProvider.updateShowConflicts(savedState['showConflicts']);
-
-        myProvider.setShuffleCellValues(savedState['shuffledCellValues']);
-
-        myProvider.updateCells(
-            BoardUtils.createBoardFromSavedState(myProvider, savedState['boardValues']));
-        myProvider.updateGameIsRunning(true);
-      } catch (e) {
-        printlog('Failed to resume game. Will start new one instead.');
-        myProvider.resetCurrentSavedState();
-        myProvider.initParametersValues();
-        startNewGame(myProvider);
-      }
-    } else {
-      myProvider.resetCurrentSavedState();
-      myProvider.initParametersValues();
-      startNewGame(myProvider);
-    }
-  }
-
-  static void showTip(Data myProvider) {
-    if (myProvider.selectedCellCol == null || myProvider.selectedCellRow == null) {
-      // no selected cell -> pick one
-      GameUtils.helpSelectCell(myProvider);
-    } else {
-      // currently selected cell -> set value
-      GameUtils.helpFillCell(myProvider);
-    }
-    myProvider.increaseGivenTipsCount();
-  }
-
-  static void helpSelectCell(Data myProvider) {
-    final Board cells = myProvider.board;
-    final int blockSizeHorizontal = myProvider.blockSizeHorizontal;
-    final int blockSizeVertical = myProvider.blockSizeVertical;
-
-    // pick one of wrong value cells, if found
-    final List<List<int>> wrongValueCells = BoardUtils.getCellsWithWrongValue(
-      cells,
-      myProvider.boardSolved,
-      blockSizeHorizontal,
-      blockSizeVertical,
-    );
-    if (wrongValueCells.isNotEmpty) {
-      GameUtils.pickRandomFromList(myProvider, wrongValueCells);
-      return;
-    }
-
-    // pick one of conflicting cells, if found
-    final List<List<int>> conflictingCells = BoardUtils.getCellsWithUniqueAvailableValue(
-      cells,
-      blockSizeHorizontal,
-      blockSizeVertical,
-    );
-    if (conflictingCells.isNotEmpty) {
-      GameUtils.pickRandomFromList(myProvider, conflictingCells);
-      return;
-    }
-
-    //  pick one form cells with unique non-conflicting candidate value
-    final List<List<int>> candidateCells = BoardUtils.getCellsWithUniqueAvailableValue(
-      cells,
-      blockSizeHorizontal,
-      blockSizeVertical,
-    );
-    if (candidateCells.isNotEmpty) {
-      GameUtils.pickRandomFromList(myProvider, candidateCells);
-      return;
-    }
-  }
-
-  static void pickRandomFromList(Data myProvider, List<List<int>> cellsCoordinates) {
-    if (cellsCoordinates.isNotEmpty) {
-      cellsCoordinates.shuffle();
-      List<int> cell = cellsCoordinates[0];
-      myProvider.selectCell(cell[0], cell[1]);
-    }
-  }
-
-  static void helpFillCell(Data myProvider) {
-    final Board cells = myProvider.board;
-    final int blockSizeHorizontal = myProvider.blockSizeHorizontal;
-    final int blockSizeVertical = myProvider.blockSizeVertical;
-
-    final int boardSize = blockSizeHorizontal * blockSizeVertical;
-
-    // Will clean cell if no eligible value found
-    int eligibleValue = 0;
-
-    // ensure there is only one eligible value for this cell
-    int allowedValuesCount = 0;
-    for (int value = 1; value <= boardSize; value++) {
-      if (BoardUtils.isValueAllowed(cells, blockSizeHorizontal, blockSizeVertical,
-          myProvider.selectedCellCol, myProvider.selectedCellRow, value)) {
-        allowedValuesCount++;
-        eligibleValue = value;
-      }
-    }
-
-    myProvider.updateCellValue(myProvider.selectedCellCol, myProvider.selectedCellRow,
-        allowedValuesCount == 1 ? eligibleValue : 0);
-    myProvider.selectCell(null, null);
-    if (BoardUtils.checkBoardIsSolved(myProvider)) {
-      myProvider.resetCurrentSavedState();
-      BoardAnimate.startAnimation(myProvider, 'win');
-    }
-  }
-}
diff --git a/lib/utils/random_pick_grid.dart b/lib/utils/random_pick_grid.dart
deleted file mode 100644
index 795988460a9bcfbeb2f489a79d1e876f91c8726a..0000000000000000000000000000000000000000
--- a/lib/utils/random_pick_grid.dart
+++ /dev/null
@@ -1,41 +0,0 @@
-import 'dart:convert';
-
-import 'package:flutter/services.dart';
-
-import 'package:sudoku/utils/tools.dart';
-
-class RandomPickGrid {
-  RandomPickGrid();
-
-  String _grid = '';
-
-  init(String level, String size) async {
-    _grid = '';
-    await gridFromLocalFile(level, size);
-  }
-
-  Future<void> gridFromLocalFile(String level, String size) async {
-    // Get global grids list
-    List grids = [];
-    try {
-      String jsonString = await rootBundle.loadString('assets/files/templates.json');
-      final jsonResponse = await json.decode(jsonString);
-      grids = jsonResponse['templates'][size][level];
-    } catch (e) {
-      printlog("$e");
-    }
-
-    // Check we have enough grids
-    if (grids.isEmpty) {
-      printlog('Not enough grids [$size, $level] in templates.');
-    }
-
-    // Randomize grids list
-    grids.shuffle();
-
-    // Pick first grids from shuffled list
-    _grid = grids.take(1).toList()[0];
-  }
-
-  String get grid => _grid;
-}
diff --git a/pubspec.lock b/pubspec.lock
index cc569cd7a5e93d27e7349cd166dfaf1efd0d63dd..4991f4dceb056cec471bfc8547563946a71e98ea 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1,6 +1,14 @@
 # Generated by pub
 # See https://dart.dev/tools/pub/glossary#lockfile
 packages:
+  args:
+    dependency: transitive
+    description:
+      name: args
+      sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.5.0"
   async:
     dependency: transitive
     description:
@@ -17,6 +25,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "3.1.2"
+  bloc:
+    dependency: transitive
+    description:
+      name: bloc
+      sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "8.1.4"
   characters:
     dependency: transitive
     description:
@@ -25,6 +41,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.3.0"
+  clock:
+    dependency: transitive
+    description:
+      name: clock
+      sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.1"
   collection:
     dependency: transitive
     description:
@@ -33,6 +57,38 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.18.0"
+  crypto:
+    dependency: transitive
+    description:
+      name: crypto
+      sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.3"
+  easy_localization:
+    dependency: "direct main"
+    description:
+      name: easy_localization
+      sha256: "432698c31a488dd64c56d4759f20d04844baba5e9e4f2cb1abb9676257918b17"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.6"
+  easy_logger:
+    dependency: transitive
+    description:
+      name: easy_logger
+      sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.0.2"
+  equatable:
+    dependency: "direct main"
+    description:
+      name: equatable
+      sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.0.5"
   ffi:
     dependency: transitive
     description:
@@ -54,19 +110,56 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_bloc:
+    dependency: "direct main"
+    description:
+      name: flutter_bloc
+      sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2
+      url: "https://pub.dev"
+    source: hosted
+    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_web_plugins:
     dependency: transitive
     description: flutter
     source: sdk
     version: "0.0.0"
+  hive:
+    dependency: "direct main"
+    description:
+      name: hive
+      sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.3"
+  hydrated_bloc:
+    dependency: "direct main"
+    description:
+      name: hydrated_bloc
+      sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c
+      url: "https://pub.dev"
+    source: hosted
+    version: "9.1.5"
+  intl:
+    dependency: transitive
+    description:
+      name: intl
+      sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.18.1"
   lints:
     dependency: transitive
     description:
@@ -115,6 +208,30 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.9.0"
+  path_provider:
+    dependency: "direct main"
+    description:
+      name: path_provider
+      sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.3"
+  path_provider_android:
+    dependency: transitive
+    description:
+      name: path_provider_android
+      sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.4"
+  path_provider_foundation:
+    dependency: transitive
+    description:
+      name: path_provider_foundation
+      sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.2"
   path_provider_linux:
     dependency: transitive
     description:
@@ -156,7 +273,7 @@ packages:
     source: hosted
     version: "2.1.8"
   provider:
-    dependency: "direct main"
+    dependency: transitive
     description:
       name: provider
       sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
@@ -164,21 +281,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:
@@ -224,6 +341,22 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.99"
+  synchronized:
+    dependency: transitive
+    description:
+      name: synchronized
+      sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.0+1"
+  typed_data:
+    dependency: transitive
+    description:
+      name: typed_data
+      sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.3.2"
   vector_math:
     dependency: transitive
     description:
@@ -236,18 +369,18 @@ packages:
     dependency: transitive
     description:
       name: web
-      sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad"
+      sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
       url: "https://pub.dev"
     source: hosted
-    version: "0.5.0"
+    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:
diff --git a/pubspec.yaml b/pubspec.yaml
index 9c7e81f30e99d0d95ee2f2e8439789911cf4e51a..6c79d6b72d4e8694622522f0345e41bab3e78cca 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,9 @@
 name: sudoku
 description: A sudoku game application.
+
 publish_to: 'none'
-version: 0.1.18+67
+
+version: 0.1.19+68
 
 environment:
   sdk: '^3.0.0'
@@ -9,10 +11,14 @@ environment:
 dependencies:
   flutter:
     sdk: flutter
-  provider: ^6.0.5
   badges: ^3.1.2
-  shared_preferences: ^2.2.1
+  easy_localization: ^3.0.1
+  equatable: ^2.0.5
+  flutter_bloc: ^8.1.1
+  hive: ^2.2.3
+  hydrated_bloc: ^9.0.0
   overlay_support: ^2.1.0
+  path_provider: ^2.0.11
 
 dev_dependencies:
   flutter_lints: ^3.0.1
@@ -20,6 +26,18 @@ dev_dependencies:
 flutter:
   uses-material-design: true
   assets:
-    - assets/files/
     - assets/icons/
     - assets/skins/
+    - assets/translations/
+
+  fonts:
+    - family: Nunito
+      fonts:
+        - asset: assets/fonts/Nunito-Bold.ttf
+          weight: 700
+        - asset: assets/fonts/Nunito-Medium.ttf
+          weight: 500
+        - asset: assets/fonts/Nunito-Regular.ttf
+          weight: 400
+        - asset: assets/fonts/Nunito-Light.ttf
+          weight: 300