diff --git a/assets/json/levels.json b/assets/json/levels.json new file mode 100644 index 0000000..da6a459 --- /dev/null +++ b/assets/json/levels.json @@ -0,0 +1,1181 @@ +{ + "result": [ + { + "id": 34, + "order": 1, + "title": "Tooth Brushing Etiquette", + "questions": [ + { + "id": 301, + "title": "Hadi wants her teeth to be clean. Which of her actions do you think is correct?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_UXndIM5.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 1, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1192, + "title": "We carefully clean our teeth with a toothbrush", + "image_id": "af38813ccc", + "image_info": { + "filename": "hadihoda_user_iNZMIEt.png", + "size": 111332, + "extension": ".png" + }, + "order": 1, + "is_active": true + }, + { + "id": 1193, + "title": "We walk in the yard with a glass of juice", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer1_82OrRAN.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1194, + "title": "We eat candy and watch TV", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "hadihoda_user_l7cRKyg.png", + "size": 111332, + "extension": ".png" + }, + "order": 3, + "is_active": true + }, + { + "id": 1195, + "title": "Take it and throw it away without saying anything", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "answer1_w0EyoOQ.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 4, + "is_active": true + } + ] + }, + { + "id": 302, + "title": "What is the best time to brush teeth?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_HXTTczp.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 2, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1196, + "title": "Morning after waking up and night before sleeping", + "image_id": "af38813ccc", + "image_info": { + "filename": "answer2_F42cGzw.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 1, + "is_active": true + }, + { + "id": 1197, + "title": "Only when we have toothache", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer1_rzFgkLQ.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1198, + "title": "Only on weekends", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer2_4bPWCnw.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1199, + "title": "When mom says", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "hadihoda_user_0fLITwd.png", + "size": 111332, + "extension": ".png" + }, + "order": 4, + "is_active": true + } + ] + }, + { + "id": 303, + "title": "How many times a day should Hadi brush her teeth?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_tELeghD.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 3, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1200, + "title": "Twice a day", + "image_id": "af38813ccc", + "image_info": { + "filename": "answer1_KETj6i3.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 1, + "is_active": true + }, + { + "id": 1201, + "title": "Once a day", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer1_9VovGd0.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1202, + "title": "Three times a day", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer2_Dt5fAsU.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1203, + "title": "Only when she remembers", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "answer1_o9la4sD.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 4, + "is_active": true + } + ] + } + ] + }, + { + "id": 35, + "order": 2, + "title": "Eating Etiquette", + "questions": [ + { + "id": 311, + "title": "Hadi wants to eat. Which of her actions is correct?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_Xtv5lSc.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 1, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1232, + "title": "She washes her hands before eating", + "image_id": "af38813ccc", + "image_info": { + "filename": "answer1_C2dAmzG.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 1, + "is_active": true + }, + { + "id": 1233, + "title": "She eats with dirty hands", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer2_4oYaj60.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1234, + "title": "She spills food on the ground", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer1_MV4vliK.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1235, + "title": "She eats with her mouth open", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "answer1_Bh6JUWv.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 4, + "is_active": true + } + ] + }, + { + "id": 312, + "title": "What should Hadi say when eating?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_KqNhyXy.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 2, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1236, + "title": "Bismillah", + "image_id": "af38813ccc", + "image_info": { + "filename": "hadihoda_user_WnLLPk0.png", + "size": 111332, + "extension": ".png" + }, + "order": 1, + "is_active": true + }, + { + "id": 1237, + "title": "Hello", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer1_BfJLxDD.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1238, + "title": "Goodbye", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "hadihoda_user_NFZvYz4.png", + "size": 111332, + "extension": ".png" + }, + "order": 3, + "is_active": true + }, + { + "id": 1239, + "title": "Nothing", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "hadihoda_user_4pnLURZ.png", + "size": 111332, + "extension": ".png" + }, + "order": 4, + "is_active": true + } + ] + }, + { + "id": 313, + "title": "How should Hadi eat?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_yskYwgR.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 3, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1240, + "title": "Slowly and calmly", + "image_id": "af38813ccc", + "image_info": { + "filename": "answer1_gCLGHBG.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 1, + "is_active": true + }, + { + "id": 1241, + "title": "Very quickly", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer2_9A6rnMB.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1242, + "title": "Loudly", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer2_19p9V2Z.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1243, + "title": "While running", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "answer1_JzbIvAs.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 4, + "is_active": true + } + ] + } + ] + }, + { + "id": 36, + "order": 3, + "title": "Sleeping Etiquette", + "questions": [ + { + "id": 321, + "title": "Hadi wants to sleep. Which of her actions is correct?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_j9BlKTe.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 1, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1272, + "title": "She washes her hands before sleeping", + "image_id": "af38813ccc", + "image_info": { + "filename": "answer2_nwvb7rC.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 1, + "is_active": true + }, + { + "id": 1273, + "title": "She sleeps in dirty clothes", + "image_id": "c290649fdb", + "image_info": { + "filename": "hadihoda_user_bvNH1AC.png", + "size": 111332, + "extension": ".png" + }, + "order": 2, + "is_active": true + }, + { + "id": 1274, + "title": "She sleeps with shoes on", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer2_JVVfxGq.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1275, + "title": "She sleeps on the ground", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "hadihoda_user_rAKoU3L.png", + "size": 111332, + "extension": ".png" + }, + "order": 4, + "is_active": true + } + ] + }, + { + "id": 322, + "title": "What should Hadi say before sleeping?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_TEW4rrP.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 2, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1276, + "title": "Bismillah and bedtime prayer", + "image_id": "af38813ccc", + "image_info": { + "filename": "hadihoda_user_kxxcuBs.png", + "size": 111332, + "extension": ".png" + }, + "order": 1, + "is_active": true + }, + { + "id": 1277, + "title": "Hello", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer2_dA1hKmO.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1278, + "title": "Goodbye", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer2_mOYRhA8.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1279, + "title": "Nothing", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "answer1_6cc7bff.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 4, + "is_active": true + } + ] + }, + { + "id": 323, + "title": "What time should Hadi sleep?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_qOceSmr.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 3, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1280, + "title": "Regular and early time", + "image_id": "af38813ccc", + "image_info": { + "filename": "answer1_f54wbXf.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 1, + "is_active": true + }, + { + "id": 1281, + "title": "Whenever she likes", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer1_m040LzQ.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1282, + "title": "Midnight", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer2_uqP0F9g.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1283, + "title": "Morning", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "hadihoda_user_cBWZXnR.png", + "size": 111332, + "extension": ".png" + }, + "order": 4, + "is_active": true + } + ] + } + ] + }, + { + "id": 37, + "order": 4, + "title": "Dressing Etiquette", + "questions": [ + { + "id": 331, + "title": "Hadi wants to get dressed. Which of her actions is correct?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_ybeoTaj.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 1, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1312, + "title": "She wears clean and neat clothes", + "image_id": "af38813ccc", + "image_info": { + "filename": "answer2_A84bsAn.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 1, + "is_active": true + }, + { + "id": 1313, + "title": "She wears dirty clothes", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer2_QM5Wdvi.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1314, + "title": "She wears torn clothes", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer2_tXODAh2.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1315, + "title": "She doesn't wear any clothes", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "answer2_whh6588.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 4, + "is_active": true + } + ] + }, + { + "id": 332, + "title": "What type of clothes should Hadi wear?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_Ykj79Vo.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 2, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1316, + "title": "Comfortable and appropriate clothes", + "image_id": "af38813ccc", + "image_info": { + "filename": "answer1_yPRbPmr.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 1, + "is_active": true + }, + { + "id": 1317, + "title": "Tight and uncomfortable clothes", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer2_uRVCkPW.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1318, + "title": "Big and loose clothes", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer2_CkVrLZ9.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1319, + "title": "Any clothes she likes", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "hadihoda_user_rAxXEPl.png", + "size": 111332, + "extension": ".png" + }, + "order": 4, + "is_active": true + } + ] + }, + { + "id": 333, + "title": "What should Hadi do before getting dressed?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_Vis5bMe.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 3, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1320, + "title": "Wash her hands", + "image_id": "af38813ccc", + "image_info": { + "filename": "hadihoda_user_AogGpyQ.png", + "size": 111332, + "extension": ".png" + }, + "order": 1, + "is_active": true + }, + { + "id": 1321, + "title": "Play", + "image_id": "c290649fdb", + "image_info": { + "filename": "hadihoda_user_kpAKrmx.png", + "size": 111332, + "extension": ".png" + }, + "order": 2, + "is_active": true + }, + { + "id": 1322, + "title": "Eat food", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer1_sLLo3y5.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1323, + "title": "Do nothing", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "answer2_K4AErHQ.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 4, + "is_active": true + } + ] + } + ] + }, + { + "id": 38, + "order": 5, + "title": "Personal Hygiene", + "questions": [ + { + "id": 341, + "title": "Hadi wants to be clean. Which of her actions is correct?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_ZdQd0iI.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 1, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1352, + "title": "Take a bath every day", + "image_id": "af38813ccc", + "image_info": { + "filename": "hadihoda_user_XhWZzwT.png", + "size": 111332, + "extension": ".png" + }, + "order": 1, + "is_active": true + }, + { + "id": 1353, + "title": "Take a bath once a week", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer2_nImB8qj.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1354, + "title": "Take a bath once a month", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer1_kondYqe.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1355, + "title": "Never take a bath", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "answer1_m39DWKt.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 4, + "is_active": true + } + ] + }, + { + "id": 342, + "title": "How many times a day should Hadi wash her hands?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_59q7VNM.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 2, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1356, + "title": "Several times", + "image_id": "af38813ccc", + "image_info": { + "filename": "hadihoda_user_ie6o3M4.png", + "size": 111332, + "extension": ".png" + }, + "order": 1, + "is_active": true + }, + { + "id": 1357, + "title": "Once", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer2_y1cBc6f.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1358, + "title": "Never", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer1_NoFjFsV.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1359, + "title": "Only when dirty", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "hadihoda_user_pdWCd5x.png", + "size": 111332, + "extension": ".png" + }, + "order": 4, + "is_active": true + } + ] + }, + { + "id": 343, + "title": "What should Hadi do after using the toilet?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_hg7QbGt.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 3, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1360, + "title": "Wash her hands with soap", + "image_id": "af38813ccc", + "image_info": { + "filename": "answer1_Q128oR0.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 1, + "is_active": true + }, + { + "id": 1361, + "title": "Do nothing", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer1_XXPgA9y.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1362, + "title": "Just rinse with water", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "answer2_ppNdIEg.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 3, + "is_active": true + }, + { + "id": 1363, + "title": "Wipe her hands", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "hadihoda_user_7OqltRo.png", + "size": 111332, + "extension": ".png" + }, + "order": 4, + "is_active": true + } + ] + } + ] + }, + { + "id": 39, + "order": 6, + "title": "Playing Etiquette", + "questions": [ + { + "id": 351, + "title": "Hadi wants her teeth to be clean. Which of her actions do you think is correct?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_Ckvo4Qh.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 1, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1392, + "title": "We carefully clean our teeth with a toothbrush", + "image_id": "af38813ccc", + "image_info": { + "filename": "answer1_bqavfEH.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 1, + "is_active": true + }, + { + "id": 1393, + "title": "We walk in the yard with a glass of juice", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer1_kqoMwPH.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1394, + "title": "We eat candy and watch TV", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "hadihoda_user_8L5eRRO.png", + "size": 111332, + "extension": ".png" + }, + "order": 3, + "is_active": true + }, + { + "id": 1395, + "title": "Take it and throw it away without saying anything", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "answer2_1gNQBhl.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 4, + "is_active": true + } + ] + }, + { + "id": 352, + "title": "What is the best time to brush teeth?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_3f98P2I.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 2, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1396, + "title": "Morning after waking up and night before sleeping", + "image_id": "af38813ccc", + "image_info": { + "filename": "hadihoda_user_on4hHkq.png", + "size": 111332, + "extension": ".png" + }, + "order": 1, + "is_active": true + }, + { + "id": 1397, + "title": "Only when we have toothache", + "image_id": "c290649fdb", + "image_info": { + "filename": "answer2_W5wOqId.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 2, + "is_active": true + }, + { + "id": 1398, + "title": "Only on weekends", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "hadihoda_user_AnvL0XG.png", + "size": 111332, + "extension": ".png" + }, + "order": 3, + "is_active": true + }, + { + "id": 1399, + "title": "When mom says", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "hadihoda_user_iJ1D06B.png", + "size": 111332, + "extension": ".png" + }, + "order": 4, + "is_active": true + } + ] + }, + { + "id": 353, + "title": "How many times a day should Hadi brush her teeth?", + "audio_id": "57fbe036af", + "audio_info": { + "filename": "01_atom_music_audio_-_cheeky_con_lXm4PaK.mp3", + "size": 2418892, + "extension": ".mp3" + }, + "order": 3, + "correct_answer": 1, + "is_active": true, + "answers": [ + { + "id": 1400, + "title": "Twice a day", + "image_id": "af38813ccc", + "image_info": { + "filename": "answer1_YcFpeE7.jpeg", + "size": 320543, + "extension": ".jpeg" + }, + "order": 1, + "is_active": true + }, + { + "id": 1401, + "title": "Once a day", + "image_id": "c290649fdb", + "image_info": { + "filename": "hadihoda_user_MxMcK1o.png", + "size": 111332, + "extension": ".png" + }, + "order": 2, + "is_active": true + }, + { + "id": 1402, + "title": "Three times a day", + "image_id": "d5d36b44e7", + "image_info": { + "filename": "hadihoda_user_2IPJQ7e.png", + "size": 111332, + "extension": ".png" + }, + "order": 3, + "is_active": true + }, + { + "id": 1403, + "title": "Only when she remembers", + "image_id": "f9f0bad05f", + "image_info": { + "filename": "answer2_EpzvwMB.jpeg", + "size": 346421, + "extension": ".jpeg" + }, + "order": 4, + "is_active": true + } + ] + } + ] + } + ] +} + diff --git a/lib/common_ui/resources/my_assets.dart b/lib/common_ui/resources/my_assets.dart index 862e6a3..aa38b53 100644 --- a/lib/common_ui/resources/my_assets.dart +++ b/lib/common_ui/resources/my_assets.dart @@ -3,7 +3,7 @@ class MyAssets { const MyAssets._internal(); factory MyAssets() => _i; - static const String backgroundIntro = 'assets/images/background_intro.png'; + static const String backgroundHome = 'assets/images/background_intro.png'; static const String closeBtn = 'assets/images/close_btn.svg'; static const String hadiHoda = 'assets/images/hadi_hoda.png'; static const String musicOff = 'assets/images/music_off.svg'; diff --git a/lib/common_ui/resources/my_text_style.dart b/lib/common_ui/resources/my_text_style.dart index 34c9b7a..5acd64d 100644 --- a/lib/common_ui/resources/my_text_style.dart +++ b/lib/common_ui/resources/my_text_style.dart @@ -7,7 +7,7 @@ class MyTextStyle { static const String fontFamily = 'dinokids'; - static const TextStyle normal = TextStyle( + static const TextStyle normal26 = TextStyle( fontFamily: fontFamily, fontSize: 26, fontWeight: FontWeight.w400, @@ -19,4 +19,10 @@ class MyTextStyle { ), ] ); + + static const TextStyle normal17 = TextStyle( + fontFamily: fontFamily, + fontSize: 17, + fontWeight: FontWeight.w400, + ); } diff --git a/lib/core/constants/my_constants.dart b/lib/core/constants/my_constants.dart index d03a559..c6e11bb 100644 --- a/lib/core/constants/my_constants.dart +++ b/lib/core/constants/my_constants.dart @@ -5,4 +5,5 @@ class MyConstants { static const String token = 'TOKEN'; static const String theme = 'THEME'; + static const String levelBox = 'LEVEL_BOX'; } \ No newline at end of file diff --git a/lib/core/middleware/auth_middleware.dart b/lib/core/middleware/auth_middleware.dart index 16064ec..a6901af 100644 --- a/lib/core/middleware/auth_middleware.dart +++ b/lib/core/middleware/auth_middleware.dart @@ -12,7 +12,7 @@ class AuthMiddleware { static FutureOr redirect(BuildContext context, GoRouterState state) async { if (AuthStorage.isLogin()) { - return Routes.introPage; + return Routes.homePage; } else { return null; } diff --git a/lib/core/params/home_params.dart b/lib/core/params/home_params.dart new file mode 100644 index 0000000..fdd3091 --- /dev/null +++ b/lib/core/params/home_params.dart @@ -0,0 +1,13 @@ +class HomeParams { + int? id; + + HomeParams({this.id}); + + HomeParams copyWith({ + int? id, + }) { + return HomeParams( + id: id ?? this.id, + ); + } +} diff --git a/lib/core/params/intro_params.dart b/lib/core/params/intro_params.dart deleted file mode 100644 index e03d91f..0000000 --- a/lib/core/params/intro_params.dart +++ /dev/null @@ -1,13 +0,0 @@ -class IntroParams { - int? id; - - IntroParams({this.id}); - - IntroParams copyWith({ - int? id, - }) { - return IntroParams( - id: id ?? this.id, - ); - } -} diff --git a/lib/core/params/no_params.dart b/lib/core/params/no_params.dart new file mode 100644 index 0000000..0d64db2 --- /dev/null +++ b/lib/core/params/no_params.dart @@ -0,0 +1 @@ +class NoParams {} diff --git a/lib/core/routers/my_routes.dart b/lib/core/routers/my_routes.dart index b69981b..2a88360 100644 --- a/lib/core/routers/my_routes.dart +++ b/lib/core/routers/my_routes.dart @@ -1,9 +1,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:hadi_hoda_flutter/core/utils/context_provider.dart'; +import 'package:hadi_hoda_flutter/features/home/presentation/bloc/home_bloc.dart'; +import 'package:hadi_hoda_flutter/features/home/presentation/ui/home_page.dart'; import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.dart'; import 'package:hadi_hoda_flutter/features/intro/presentation/ui/intro_page.dart'; import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_bloc.dart'; +import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_event.dart'; import 'package:hadi_hoda_flutter/features/level/presentation/ui/level_page.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_bloc.dart'; import 'package:hadi_hoda_flutter/features/question/presentation/ui/question_page.dart'; @@ -15,27 +18,36 @@ class Routes { factory Routes() => _i; static const String introPage = '/intro_page'; + static const String homePage = '/home_page'; static const String questionPage = '/question_page'; static const String levelPage = '/level_page'; } GoRouter get appPages => GoRouter( - initialLocation: Routes.levelPage, + initialLocation: Routes.introPage, navigatorKey: ContextProvider.navigatorKey, routes: [ GoRoute( name: Routes.introPage, path: Routes.introPage, builder: (context, state) => BlocProvider( - create: (context) => IntroBloc(locator()), + create: (context) => IntroBloc(locator(), locator(), locator()), child: const IntroPage(), ), ), + GoRoute( + name: Routes.homePage, + path: Routes.homePage, + builder: (context, state) => BlocProvider( + create: (context) => HomeBloc(locator()), + child: const HomePage(), + ), + ), GoRoute( name: Routes.levelPage, path: Routes.levelPage, builder: (context, state) => BlocProvider( - create: (context) => LevelBloc(locator()), + create: (context) => LevelBloc(locator())..add(GetLevelListEvent()), child: const LevelPage(), ), ), diff --git a/lib/core/utils/my_scroll_behavior.dart b/lib/core/utils/my_scroll_behavior.dart deleted file mode 100644 index 41ef961..0000000 --- a/lib/core/utils/my_scroll_behavior.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; - -class MyScrollBehavior extends ScrollBehavior { - @override - Set get dragDevices => { - PointerDeviceKind.touch, - PointerDeviceKind.mouse, - PointerDeviceKind.trackpad, - }; -} diff --git a/lib/core/widgets/answer_box/styles/picture_box.dart b/lib/core/widgets/answer_box/styles/picture_box.dart index 1b9ab51..a6e62c8 100644 --- a/lib/core/widgets/answer_box/styles/picture_box.dart +++ b/lib/core/widgets/answer_box/styles/picture_box.dart @@ -20,7 +20,7 @@ class AnswerPictureBox extends StatelessWidget { child: Stack( children: [ MyImage( - image: MyAssets.backgroundIntro, + image: MyAssets.backgroundHome, fit: BoxFit.cover, size: 170, ), diff --git a/lib/features/home/data/datasource/home_datasource.dart b/lib/features/home/data/datasource/home_datasource.dart new file mode 100644 index 0000000..17db1cf --- /dev/null +++ b/lib/features/home/data/datasource/home_datasource.dart @@ -0,0 +1,28 @@ +import 'package:hadi_hoda_flutter/core/constants/my_api.dart'; +import 'package:hadi_hoda_flutter/core/network/http_request.dart'; +import 'package:hadi_hoda_flutter/core/params/home_params.dart'; +import 'package:hadi_hoda_flutter/core/response/base_response.dart'; +import 'package:hadi_hoda_flutter/features/home/data/model/home_model.dart'; +import 'package:hadi_hoda_flutter/features/home/domain/entity/home_entity.dart'; + +abstract class IHomeDatasource { + Future getData({required HomeParams params}); +} + +class HomeDatasourceImpl implements IHomeDatasource { + final IHttpRequest httpRequest; + + const HomeDatasourceImpl(this.httpRequest); + + @override + Future getData({required HomeParams params}) async { + final response = await httpRequest.get( + path: MyApi.baseUrl, + ); + + return BaseResponse.getData( + response?['data'], + (json) => HomeModel.fromJson(json), + ); + } +} diff --git a/lib/features/home/data/model/home_model.dart b/lib/features/home/data/model/home_model.dart new file mode 100644 index 0000000..c63431f --- /dev/null +++ b/lib/features/home/data/model/home_model.dart @@ -0,0 +1,13 @@ +import 'package:hadi_hoda_flutter/features/home/domain/entity/home_entity.dart'; + +class HomeModel extends HomeEntity { + const HomeModel({ + super.id, + }); + + factory HomeModel.fromJson(Map json) { + return HomeModel( + id: json['id'], + ); + } +} diff --git a/lib/features/home/data/repository_impl/home_repository_impl.dart b/lib/features/home/data/repository_impl/home_repository_impl.dart new file mode 100644 index 0000000..594969b --- /dev/null +++ b/lib/features/home/data/repository_impl/home_repository_impl.dart @@ -0,0 +1,29 @@ +import 'package:hadi_hoda_flutter/core/params/home_params.dart'; +import 'package:flutter/foundation.dart'; +import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; +import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; +import 'package:hadi_hoda_flutter/features/home/data/datasource/home_datasource.dart'; +import 'package:hadi_hoda_flutter/features/home/domain/entity/home_entity.dart'; +import 'package:hadi_hoda_flutter/features/home/domain/repository/home_repository.dart'; + +class HomeRepositoryImpl implements IHomeRepository { + final IHomeDatasource datasource; + + const HomeRepositoryImpl(this.datasource); + + @override + Future> getData({required HomeParams params}) async { + try { + final HomeEntity response = await datasource.getData(params: params); + return DataState.success(response); + } on MyException catch (e) { + return DataState.error(e); + } catch (e) { + if (kDebugMode) { + rethrow; + } else { + return DataState.error(MyException(errorMessage: '$e')); + } + } + } +} diff --git a/lib/features/intro/domain/entity/intro_entity.dart b/lib/features/home/domain/entity/home_entity.dart similarity index 71% rename from lib/features/intro/domain/entity/intro_entity.dart rename to lib/features/home/domain/entity/home_entity.dart index 15c2e44..582723c 100644 --- a/lib/features/intro/domain/entity/intro_entity.dart +++ b/lib/features/home/domain/entity/home_entity.dart @@ -1,9 +1,9 @@ import 'package:equatable/equatable.dart'; -class IntroEntity extends Equatable { +class HomeEntity extends Equatable { final int? id; - const IntroEntity({ + const HomeEntity({ this.id, }); diff --git a/lib/features/home/domain/repository/home_repository.dart b/lib/features/home/domain/repository/home_repository.dart new file mode 100644 index 0000000..81ad27c --- /dev/null +++ b/lib/features/home/domain/repository/home_repository.dart @@ -0,0 +1,8 @@ +import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; +import 'package:hadi_hoda_flutter/core/params/home_params.dart'; +import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; +import 'package:hadi_hoda_flutter/features/home/domain/entity/home_entity.dart'; + +abstract class IHomeRepository { + Future> getData({required HomeParams params}); +} diff --git a/lib/features/home/domain/usecases/get_home_usecase.dart b/lib/features/home/domain/usecases/get_home_usecase.dart new file mode 100644 index 0000000..cbb629d --- /dev/null +++ b/lib/features/home/domain/usecases/get_home_usecase.dart @@ -0,0 +1,17 @@ +import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; +import 'package:hadi_hoda_flutter/core/params/home_params.dart'; +import 'package:hadi_hoda_flutter/core/usecase/usecase.dart'; +import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; +import 'package:hadi_hoda_flutter/features/home/domain/entity/home_entity.dart'; +import 'package:hadi_hoda_flutter/features/home/domain/repository/home_repository.dart'; + +class GetHomeUseCase implements UseCase { + final IHomeRepository repository; + + const GetHomeUseCase(this.repository); + + @override + Future> call(HomeParams params) { + return repository.getData(params: params); + } +} diff --git a/lib/features/home/presentation/bloc/home_bloc.dart b/lib/features/home/presentation/bloc/home_bloc.dart new file mode 100644 index 0000000..114736f --- /dev/null +++ b/lib/features/home/presentation/bloc/home_bloc.dart @@ -0,0 +1,47 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hadi_hoda_flutter/core/routers/my_routes.dart'; +import 'package:hadi_hoda_flutter/core/status/base_status.dart'; +import 'package:hadi_hoda_flutter/features/home/domain/entity/home_entity.dart'; +import 'package:hadi_hoda_flutter/features/home/domain/usecases/get_home_usecase.dart'; +import 'package:hadi_hoda_flutter/features/home/presentation/bloc/home_event.dart'; +import 'package:hadi_hoda_flutter/features/home/presentation/bloc/home_state.dart'; + +class HomeBloc extends Bloc { + /// ------------constructor------------ + HomeBloc( + this._getHomeUseCase, + ) : super(const HomeState()) { + on(_getHomeEvent); + } + + /// ------------UseCases------------ + final GetHomeUseCase _getHomeUseCase; + + /// ------------Variables------------ + + /// ------------Controllers------------ + + /// ------------Functions------------ + void goToLevelPage(BuildContext context){ + context.pushNamed(Routes.levelPage); + } + + /// ------------Api Calls------------ + FutureOr _getHomeEvent(event, emit) async { + await _getHomeUseCase(event.homeParams).then( + (value) { + value.fold( + (data) { + emit(state.copyWith(getHomeStatus: BaseComplete(data))); + }, + (error) { + emit(state.copyWith(getHomeStatus: BaseError(error.errorMessage))); + }, + ); + }, + ); + } +} diff --git a/lib/features/home/presentation/bloc/home_event.dart b/lib/features/home/presentation/bloc/home_event.dart new file mode 100644 index 0000000..2dc9c2f --- /dev/null +++ b/lib/features/home/presentation/bloc/home_event.dart @@ -0,0 +1,5 @@ +sealed class HomeEvent { + const HomeEvent(); +} + +class GetHomeEvent extends HomeEvent {} diff --git a/lib/features/home/presentation/bloc/home_state.dart b/lib/features/home/presentation/bloc/home_state.dart new file mode 100644 index 0000000..97a1646 --- /dev/null +++ b/lib/features/home/presentation/bloc/home_state.dart @@ -0,0 +1,15 @@ +import 'package:hadi_hoda_flutter/core/status/base_status.dart'; + +class HomeState { + final BaseStatus getHomeStatus; + + const HomeState({this.getHomeStatus = const BaseInit()}); + + HomeState copyWith({ + BaseStatus? getHomeStatus, + }) { + return HomeState( + getHomeStatus: getHomeStatus ?? this.getHomeStatus, + ); + } +} diff --git a/lib/features/home/presentation/ui/home_page.dart b/lib/features/home/presentation/ui/home_page.dart new file mode 100644 index 0000000..b6665fd --- /dev/null +++ b/lib/features/home/presentation/ui/home_page.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; +import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; +import 'package:hadi_hoda_flutter/core/utils/check_platform.dart'; +import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; +import 'package:hadi_hoda_flutter/core/utils/screen_size.dart'; +import 'package:hadi_hoda_flutter/features/home/presentation/bloc/home_bloc.dart'; + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(backgroundColor: context.noColor), + extendBodyBehindAppBar: true, + body: DecoratedBox( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(MyAssets.backgroundHome), + fit: BoxFit.cover, + ), + ), + child: SizedBox( + width: context.widthScreen, + height: context.heightScreen, + child: Stack( + alignment: Alignment.center, + children: [ + _music(context), + _name(context), + _bottomBtns(context), + ], + ), + ), + ), + ); + } + + Positioned _music(BuildContext context) { + return Positioned( + top: checkSize(context: context, mobile: 36, tablet: 60), + right: checkSize(context: context, mobile: 16, tablet: 30), + child: MyImage( + image: MyAssets.musicOn, + size: checkSize(context: context, tablet: 120), + ), + ); + } + + + Positioned _name(BuildContext context) { + return Positioned( + top: 146, + child: MyImage( + image: MyAssets.hadiHoda, + size: checkSize(context: context, mobile: 232, tablet: 400), + fit: BoxFit.cover, + ), + ); + } + + Positioned _bottomBtns(BuildContext context) { + return Positioned( + bottom: checkSize(context: context, mobile: 40, tablet: 60), + left: checkSize(context: context, mobile: 16, tablet: 30), + right: checkSize(context: context, mobile: 16, tablet: 30), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MyImage( + image: MyAssets.language, + size: checkSize(context: context, tablet: 120), + ), + InkWell( + child: MyImage( + image: MyAssets.start, + size: checkSize(context: context, mobile: 90, tablet: 160), + ), + onTap: () => + BlocProvider.of(context).goToLevelPage(context), + ), + MyImage( + image: MyAssets.theme, + size: checkSize(context: context, tablet: 120), + ), + ], + ), + ); + } +} diff --git a/lib/features/intro/data/datasource/intro_datasource.dart b/lib/features/intro/data/datasource/intro_datasource.dart index 996c916..89ed418 100644 --- a/lib/features/intro/data/datasource/intro_datasource.dart +++ b/lib/features/intro/data/datasource/intro_datasource.dart @@ -1,28 +1,93 @@ -import 'package:hadi_hoda_flutter/core/constants/my_api.dart'; +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/services.dart'; +import 'package:flutter_archive/flutter_archive.dart'; +import 'package:hadi_hoda_flutter/core/constants/my_constants.dart'; +import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; import 'package:hadi_hoda_flutter/core/network/http_request.dart'; -import 'package:hadi_hoda_flutter/core/params/intro_params.dart'; import 'package:hadi_hoda_flutter/core/response/base_response.dart'; -import 'package:hadi_hoda_flutter/features/intro/data/model/intro_model.dart'; -import 'package:hadi_hoda_flutter/features/intro/domain/entity/intro_entity.dart'; +import 'package:hadi_hoda_flutter/features/level/data/model/level_model.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; +import 'package:hive/hive.dart'; +import 'package:path_provider/path_provider.dart'; abstract class IIntroDatasource { - Future getData({required IntroParams params}); + Future saveLevels(); + Future extractData(); + Stream loadingStream(); } class IntroDatasourceImpl implements IIntroDatasource { final IHttpRequest httpRequest; + final StreamController streamController = StreamController.broadcast(); - const IntroDatasourceImpl(this.httpRequest); + IntroDatasourceImpl(this.httpRequest); @override - Future getData({required IntroParams params}) async { - final response = await httpRequest.get( - path: MyApi.baseUrl, - ); - - return BaseResponse.getData( - response?['data'], - (json) => IntroModel.fromJson(json), - ); + Future saveLevels() async { + try { + final Box levelBox = Hive.box(MyConstants.levelBox); + if (levelBox.isEmpty) { + final String levelAssets = await rootBundle.loadString( + 'assets/json/levels.json', + ); + final dynamic response = jsonDecode(levelAssets); + final List levelList = BaseResponse.getDataList( + response?['result'], + (json) => LevelModel.fromJson(json), + ); + + await levelBox.addAll(levelList); + } + } catch (e) { + throw MyException(errorMessage: '$e'); + } } + + @override + Future extractData() async { + try { + final Directory dir = await getApplicationDocumentsDirectory(); + final File file = File('${dir.path}/data.zip'); + if (!(await file.exists())) { + final ByteData assetFile = await rootBundle.load('assets/data/data.zip'); + await file.create(recursive: true); + await file.writeAsBytes( + assetFile.buffer.asUint8List( + assetFile.offsetInBytes, + assetFile.lengthInBytes, + ), + flush: true, + ); + + await ZipFile.extractToDirectory( + zipFile: file, + destinationDir: dir, + onExtracting: (zipEntry, progress) { + streamController.add(progress); + return ZipFileOperation.includeItem; + }, + ); + + } else { + streamController.add(20); + await Future.delayed(Duration(milliseconds: 150)); + streamController.add(40); + await Future.delayed(Duration(milliseconds: 150)); + streamController.add(60); + await Future.delayed(Duration(milliseconds: 150)); + streamController.add(80); + await Future.delayed(Duration(milliseconds: 150)); + streamController.add(100); + await Future.delayed(Duration(milliseconds: 150)); + } + } catch (e) { + throw MyException(errorMessage: '$e'); + } + } + + @override + Stream loadingStream() => streamController.stream; } diff --git a/lib/features/intro/data/model/intro_model.dart b/lib/features/intro/data/model/intro_model.dart deleted file mode 100644 index 548de3d..0000000 --- a/lib/features/intro/data/model/intro_model.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:hadi_hoda_flutter/features/intro/domain/entity/intro_entity.dart'; - -class IntroModel extends IntroEntity { - const IntroModel({ - super.id, - }); - - factory IntroModel.fromJson(Map json) { - return IntroModel( - id: json['id'], - ); - } -} diff --git a/lib/features/intro/data/repository_impl/intro_repository_impl.dart b/lib/features/intro/data/repository_impl/intro_repository_impl.dart index 362779c..10c458f 100644 --- a/lib/features/intro/data/repository_impl/intro_repository_impl.dart +++ b/lib/features/intro/data/repository_impl/intro_repository_impl.dart @@ -1,9 +1,8 @@ -import 'package:hadi_hoda_flutter/core/params/intro_params.dart'; import 'package:flutter/foundation.dart'; import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; +import 'package:hadi_hoda_flutter/core/params/no_params.dart'; import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; import 'package:hadi_hoda_flutter/features/intro/data/datasource/intro_datasource.dart'; -import 'package:hadi_hoda_flutter/features/intro/domain/entity/intro_entity.dart'; import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; class IntroRepositoryImpl implements IIntroRepository { @@ -12,10 +11,10 @@ class IntroRepositoryImpl implements IIntroRepository { const IntroRepositoryImpl(this.datasource); @override - Future> getData({required IntroParams params}) async { + Future> saveLevels() async { try { - final IntroEntity response = await datasource.getData(params: params); - return DataState.success(response); + await datasource.saveLevels(); + return DataState.success(NoParams()); } on MyException catch (e) { return DataState.error(e); } catch (e) { @@ -26,4 +25,25 @@ class IntroRepositoryImpl implements IIntroRepository { } } } + + @override + Future> extractData() async { + try { + await datasource.extractData(); + return DataState.success(NoParams()); + } on MyException catch (e) { + return DataState.error(e); + } catch (e) { + if (kDebugMode) { + rethrow; + } else { + return DataState.error(MyException(errorMessage: '$e')); + } + } + } + + @override + Stream loadingStream() { + return datasource.loadingStream(); + } } diff --git a/lib/features/intro/domain/repository/intro_repository.dart b/lib/features/intro/domain/repository/intro_repository.dart index a4b8814..6bff8db 100644 --- a/lib/features/intro/domain/repository/intro_repository.dart +++ b/lib/features/intro/domain/repository/intro_repository.dart @@ -1,8 +1,9 @@ import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; -import 'package:hadi_hoda_flutter/core/params/intro_params.dart'; +import 'package:hadi_hoda_flutter/core/params/no_params.dart'; import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; -import 'package:hadi_hoda_flutter/features/intro/domain/entity/intro_entity.dart'; abstract class IIntroRepository { - Future> getData({required IntroParams params}); + Future> saveLevels(); + Future> extractData(); + Stream loadingStream(); } diff --git a/lib/features/intro/domain/usecases/extract_data_usecase.dart b/lib/features/intro/domain/usecases/extract_data_usecase.dart new file mode 100644 index 0000000..c48e061 --- /dev/null +++ b/lib/features/intro/domain/usecases/extract_data_usecase.dart @@ -0,0 +1,16 @@ +import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; +import 'package:hadi_hoda_flutter/core/params/no_params.dart'; +import 'package:hadi_hoda_flutter/core/usecase/usecase.dart'; +import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; + +class ExtractDataUseCase implements UseCase { + final IIntroRepository repository; + + const ExtractDataUseCase(this.repository); + + @override + Future> call(NoParams params) { + return repository.extractData(); + } +} diff --git a/lib/features/intro/domain/usecases/get_intro_usecase.dart b/lib/features/intro/domain/usecases/get_intro_usecase.dart deleted file mode 100644 index 39c8bcc..0000000 --- a/lib/features/intro/domain/usecases/get_intro_usecase.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; -import 'package:hadi_hoda_flutter/core/params/intro_params.dart'; -import 'package:hadi_hoda_flutter/core/usecase/usecase.dart'; -import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; -import 'package:hadi_hoda_flutter/features/intro/domain/entity/intro_entity.dart'; -import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; - -class GetIntroUseCase implements UseCase { - final IIntroRepository repository; - - const GetIntroUseCase(this.repository); - - @override - Future> call(IntroParams params) { - return repository.getData(params: params); - } -} diff --git a/lib/features/intro/domain/usecases/loading_stream_usecase.dart b/lib/features/intro/domain/usecases/loading_stream_usecase.dart new file mode 100644 index 0000000..a24d6ed --- /dev/null +++ b/lib/features/intro/domain/usecases/loading_stream_usecase.dart @@ -0,0 +1,11 @@ +import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; + +class LoadingStreamUseCase { + final IIntroRepository repository; + + const LoadingStreamUseCase(this.repository); + + Stream call() { + return repository.loadingStream(); + } +} diff --git a/lib/features/intro/domain/usecases/save_levels_usecase.dart b/lib/features/intro/domain/usecases/save_levels_usecase.dart new file mode 100644 index 0000000..87f385b --- /dev/null +++ b/lib/features/intro/domain/usecases/save_levels_usecase.dart @@ -0,0 +1,16 @@ +import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; +import 'package:hadi_hoda_flutter/core/params/no_params.dart'; +import 'package:hadi_hoda_flutter/core/usecase/usecase.dart'; +import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; + +class SaveLevelsUseCase implements UseCase { + final IIntroRepository repository; + + const SaveLevelsUseCase(this.repository); + + @override + Future> call(NoParams params) { + return repository.saveLevels(); + } +} diff --git a/lib/features/intro/presentation/bloc/intro_bloc.dart b/lib/features/intro/presentation/bloc/intro_bloc.dart index bef4f8c..5a64c25 100644 --- a/lib/features/intro/presentation/bloc/intro_bloc.dart +++ b/lib/features/intro/presentation/bloc/intro_bloc.dart @@ -1,39 +1,70 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; -import 'package:hadi_hoda_flutter/core/status/base_status.dart'; -import 'package:hadi_hoda_flutter/features/intro/domain/entity/intro_entity.dart'; -import 'package:hadi_hoda_flutter/features/intro/domain/usecases/get_intro_usecase.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hadi_hoda_flutter/core/params/no_params.dart'; +import 'package:hadi_hoda_flutter/core/routers/my_routes.dart'; +import 'package:hadi_hoda_flutter/core/utils/context_provider.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/usecases/extract_data_usecase.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/usecases/loading_stream_usecase.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/usecases/save_levels_usecase.dart'; import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_event.dart'; import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_state.dart'; class IntroBloc extends Bloc { /// ------------constructor------------ IntroBloc( - this._getIntroUseCase, + this._saveLevelsUseCase, + this._extractDataUseCase, + this._loadingStreamUseCase, ) : super(const IntroState()) { - on(_getIntroEvent); + on(_saveLevelsEvent); + on(_extractDataEvent); + loadingStream = _loadingStreamUseCase(); } /// ------------UseCases------------ - final GetIntroUseCase _getIntroUseCase; + final SaveLevelsUseCase _saveLevelsUseCase; + final ExtractDataUseCase _extractDataUseCase; + final LoadingStreamUseCase _loadingStreamUseCase; /// ------------Variables------------ + Stream loadingStream = Stream.empty(); /// ------------Controllers------------ /// ------------Functions------------ /// ------------Api Calls------------ - FutureOr _getIntroEvent(event, emit) async { - await _getIntroUseCase(event.introParams).then( + FutureOr _saveLevelsEvent(SaveLevelsEvent event, + Emitter emit) async { + await _saveLevelsUseCase(NoParams()).then( (value) { value.fold( - (data) { - emit(state.copyWith(getIntroStatus: BaseComplete(data))); - }, - (error) { - emit(state.copyWith(getIntroStatus: BaseError(error.errorMessage))); - }, + (data) async { + + add(ExtractDataEvent()); + }, + (error) {}, + ); + }, + ); + } + + FutureOr _extractDataEvent(ExtractDataEvent event, + Emitter emit) async { + await _extractDataUseCase(NoParams()).then( + (value) { + value.fold( + (data) async { + await Future.delayed( + Duration(seconds: 1), () { + ContextProvider.context!.goNamed(Routes.homePage); + }, + ); + }, + (error) { + print(error.errorMessage); + }, ); }, ); diff --git a/lib/features/intro/presentation/bloc/intro_event.dart b/lib/features/intro/presentation/bloc/intro_event.dart index 4bdd16b..81aab0d 100644 --- a/lib/features/intro/presentation/bloc/intro_event.dart +++ b/lib/features/intro/presentation/bloc/intro_event.dart @@ -2,4 +2,5 @@ sealed class IntroEvent { const IntroEvent(); } -class GetIntroEvent extends IntroEvent {} +class SaveLevelsEvent extends IntroEvent {} +class ExtractDataEvent extends IntroEvent {} diff --git a/lib/features/intro/presentation/ui/intro_page.dart b/lib/features/intro/presentation/ui/intro_page.dart index bc2c0d5..9710e94 100644 --- a/lib/features/intro/presentation/ui/intro_page.dart +++ b/lib/features/intro/presentation/ui/intro_page.dart @@ -1,93 +1,65 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; -import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; -import 'package:hadi_hoda_flutter/core/routers/my_routes.dart'; -import 'package:hadi_hoda_flutter/core/utils/check_platform.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/core/utils/screen_size.dart'; +import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_bloc.dart'; +import 'package:hadi_hoda_flutter/features/intro/presentation/bloc/intro_event.dart'; +import 'package:hadi_hoda_flutter/features/intro/presentation/ui/widgets/intro_loading_widget.dart'; -class IntroPage extends StatelessWidget { +class IntroPage extends StatefulWidget { const IntroPage({super.key}); + @override + State createState() => _IntroPageState(); +} + +class _IntroPageState extends State { + + @override + void initState() { + super.initState(); + context.read().add(SaveLevelsEvent()); + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(backgroundColor: context.noColor), - extendBodyBehindAppBar: true, - body: DecoratedBox( + body: Container( + height: context.heightScreen, + width: context.widthScreen, decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0XFF00154C), Color(0XFF150532)], + ), image: DecorationImage( - image: AssetImage(MyAssets.backgroundIntro), - fit: BoxFit.cover, + image: AssetImage(MyAssets.pattern), + scale: 3, + repeat: ImageRepeat.repeat, + colorFilter: ColorFilter.mode( + Colors.white.withValues(alpha: 0.2), + BlendMode.srcIn, + ), ), ), - child: SizedBox( - width: context.widthScreen, - height: context.heightScreen, - child: Stack( - alignment: Alignment.center, - children: [ - _music(context), - _name(context), - _bottomBtns(context), - ], - ), + child: Stack( + alignment: Alignment.center, + children: [_image(), _loading(context)], ), ), ); } - Positioned _music(BuildContext context) { - return Positioned( - top: checkSize(context: context, mobile: 36, tablet: 60), - right: checkSize(context: context, mobile: 16, tablet: 30), - child: MyImage( - image: MyAssets.musicOn, - size: checkSize(context: context, tablet: 120), - ), - ); - } - - - Positioned _name(BuildContext context) { - return Positioned( - top: 146, - child: MyImage( - image: MyAssets.hadiHoda, - size: checkSize(context: context, mobile: 232, tablet: 400), - fit: BoxFit.cover, - ), - ); - } + MyImage _image() => MyImage(image: MyAssets.hadiHoda, size: 200); - Positioned _bottomBtns(BuildContext context) { + Positioned _loading(BuildContext context) { return Positioned( - bottom: checkSize(context: context, mobile: 40, tablet: 60), - left: checkSize(context: context, mobile: 16, tablet: 30), - right: checkSize(context: context, mobile: 16, tablet: 30), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - MyImage( - image: MyAssets.language, - size: checkSize(context: context, tablet: 120), - ), - InkWell( - child: MyImage( - image: MyAssets.start, - size: checkSize(context: context, mobile: 90, tablet: 160), - ), - onTap: () { - context.goNamed(Routes.questionPage); - }, - ), - MyImage( - image: MyAssets.theme, - size: checkSize(context: context, tablet: 120), - ), - ], + bottom: MediaQuery.viewPaddingOf(context).bottom, + child: IntroLoadingWidget( + percent: 80, + loadingStream: context.read().loadingStream, ), ); } diff --git a/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart b/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart new file mode 100644 index 0000000..42cf086 --- /dev/null +++ b/lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart'; + + +class IntroLoadingWidget extends StatelessWidget { + const IntroLoadingWidget({ + super.key, + this.percent, + this.loadingStream, + }); + + final double? percent; + final Stream? loadingStream; + + @override + Widget build(BuildContext context) { + return ClipPath( + clipper: BubbleClip(), + child: Container( + width: 300, + height: 60, + padding: EdgeInsets.symmetric( + vertical: MySpaces.s4, + horizontal: MySpaces.s2, + ), + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment(0, 1), // bottom + end: Alignment(0, -1), // top + colors: [ + Color(0xFFCADCFF), // #CADCFF + Colors.white, // #FFFFFF + ], + ), + ), + child: StreamBuilder( + initialData: 0, + stream: loadingStream, + builder: (context, snapshot) { + print(snapshot.data); + return Row( + children: [ + Expanded( + flex: 85, + child: ClipPath( + clipper: BubbleClip(), + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + padding: EdgeInsetsDirectional.only( + end: 260 - ((snapshot.data ?? 0) * 260 / 100), + ), + decoration: BoxDecoration( + color: Color(0xFF1F59BD).withValues(alpha: 0.25), + ), + child: ClipPath( + clipper: BubbleClip(), + child: Container( + decoration: BoxDecoration( + gradient: RadialGradient( + radius: 2, + colors: [ + Color(0xFFFFBD00), // #CADCFF + Color(0xFFFF772C), // #CADCFF + ], + ), + ), + ), + ), + ), + ), + ), + Expanded( + flex: 15, + child: Center( + child: Text( + '${snapshot.data?.toInt() ?? 0}%', + style: MyTextStyle.normal17.copyWith( + color: Color(0XFF6E83A8), + ), + ), + ), + ), + ], + ); + } + ), + ), + ); + } +} + +class BubbleClip extends CustomClipper { + @override + Path getClip(Size size) { + // Original SVG viewBox: 334 x 60 + const double w0 = 334.0; + const double h0 = 60.0; + final sx = size.width / w0; + final sy = size.height / h0; + + // SVG path: + // M9.82057 10.3597 + // C -1.70838 17.1589 -3.47995 44.4301 6.60447 53.1719 + // C 16.0075 61.291 305.076 61.9385 323.201 53.4956 + // C 341.326 45.0527 332.116 8.04571 324.829 5.7273 + // C 307.985 -2.06805 28.6539 -0.77294 9.82057 10.3597 + // Z + final p = Path() + ..moveTo(9.82057 * sx, 10.3597 * sy) + ..cubicTo( + -1.70838 * sx, 17.1589 * sy, + -3.47995 * sx, 44.4301 * sy, + 6.60447 * sx, 53.1719 * sy, + ) + ..cubicTo( + 16.0075 * sx, 61.291 * sy, + 305.076 * sx, 61.9385 * sy, + 323.201 * sx, 53.4956 * sy, + ) + ..cubicTo( + 341.326 * sx, 45.0527 * sy, + 332.116 * sx, 8.04571 * sy, + 324.829 * sx, 5.7273 * sy, + ) + ..cubicTo( + 307.985 * sx, -2.06805 * sy, + 28.6539 * sx, -0.77294 * sy, + 9.82057 * sx, 10.3597 * sy, + ) + ..close(); + + return p; + } + + @override + bool shouldReclip(covariant CustomClipper oldClipper) => false; +} diff --git a/lib/features/level/data/datasource/level_datasource.dart b/lib/features/level/data/datasource/level_datasource.dart index ec1f645..77abd2b 100644 --- a/lib/features/level/data/datasource/level_datasource.dart +++ b/lib/features/level/data/datasource/level_datasource.dart @@ -1,28 +1,49 @@ import 'package:hadi_hoda_flutter/core/constants/my_api.dart'; +import 'package:hadi_hoda_flutter/core/constants/my_constants.dart'; +import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; import 'package:hadi_hoda_flutter/core/network/http_request.dart'; import 'package:hadi_hoda_flutter/core/params/level_params.dart'; import 'package:hadi_hoda_flutter/core/response/base_response.dart'; import 'package:hadi_hoda_flutter/features/level/data/model/level_model.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; +import 'package:hive/hive.dart'; abstract class ILevelDatasource { - Future getData({required LevelParams params}); + Future> getLevels({required LevelParams params}); } -class LevelDatasourceImpl implements ILevelDatasource { +/// Remote +class RemoteLevelDatasourceImpl implements ILevelDatasource { final IHttpRequest httpRequest; - const LevelDatasourceImpl(this.httpRequest); + const RemoteLevelDatasourceImpl(this.httpRequest); @override - Future getData({required LevelParams params}) async { + Future> getLevels({required LevelParams params}) async { final response = await httpRequest.get( path: MyApi.baseUrl, ); - return BaseResponse.getData( + return BaseResponse.getDataList( response?['data'], (json) => LevelModel.fromJson(json), ); } } + + +/// Local +class LocalLevelDatasourceImpl implements ILevelDatasource { + + const LocalLevelDatasourceImpl(); + + @override + Future> getLevels({required LevelParams params}) async { + try { + final Box levelBox = Hive.box(MyConstants.levelBox); + return levelBox.values.toList(); + } catch (_) { + throw MyException(errorMessage: 'Operation Failed'); + } + } +} \ No newline at end of file diff --git a/lib/features/level/data/model/level_model.dart b/lib/features/level/data/model/level_model.dart index 1d3fb5f..2400039 100644 --- a/lib/features/level/data/model/level_model.dart +++ b/lib/features/level/data/model/level_model.dart @@ -1,13 +1,23 @@ import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; +import 'package:hadi_hoda_flutter/features/question/data/model/question_model.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart'; class LevelModel extends LevelEntity { - const LevelModel({ + LevelModel({ super.id, + super.order, + super.title, + super.questions, }); factory LevelModel.fromJson(Map json) { return LevelModel( id: json['id'], + order: json['order'], + title: json['title'], + questions: json['questions'] + ?.map((e) => QuestionModel.fromJson(e)) + .toList(), ); } } diff --git a/lib/features/level/data/repository_impl/level_repository_impl.dart b/lib/features/level/data/repository_impl/level_repository_impl.dart index 6673644..07d9366 100644 --- a/lib/features/level/data/repository_impl/level_repository_impl.dart +++ b/lib/features/level/data/repository_impl/level_repository_impl.dart @@ -1,6 +1,6 @@ -import 'package:hadi_hoda_flutter/core/params/level_params.dart'; import 'package:flutter/foundation.dart'; import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; +import 'package:hadi_hoda_flutter/core/params/level_params.dart'; import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; import 'package:hadi_hoda_flutter/features/level/data/datasource/level_datasource.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; @@ -12,9 +12,13 @@ class LevelRepositoryImpl implements ILevelRepository { const LevelRepositoryImpl(this.datasource); @override - Future> getData({required LevelParams params}) async { + Future, MyException>> getLevels({ + required LevelParams params, + }) async { try { - final LevelEntity response = await datasource.getData(params: params); + final List response = await datasource.getLevels( + params: params, + ); return DataState.success(response); } on MyException catch (e) { return DataState.error(e); diff --git a/lib/features/level/domain/entity/level_entity.dart b/lib/features/level/domain/entity/level_entity.dart index 4b4616b..196bd26 100644 --- a/lib/features/level/domain/entity/level_entity.dart +++ b/lib/features/level/domain/entity/level_entity.dart @@ -1,14 +1,23 @@ -import 'package:equatable/equatable.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart'; +import 'package:hive/hive.dart'; -class LevelEntity extends Equatable { - final int? id; +part 'level_entity.g.dart'; - const LevelEntity({ - this.id, - }); +@HiveType(typeId: 0) +class LevelEntity extends HiveObject { + @HiveField(0) + int? id; + @HiveField(1) + int? order; + @HiveField(2) + String? title; + @HiveField(3) + List? questions; - @override - List get props => [ - id, - ]; + LevelEntity({ + this.id, + this.order, + this.title, + this.questions, + }); } diff --git a/lib/features/level/domain/entity/level_entity.g.dart b/lib/features/level/domain/entity/level_entity.g.dart new file mode 100644 index 0000000..7c5ddd0 --- /dev/null +++ b/lib/features/level/domain/entity/level_entity.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'level_entity.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class LevelEntityAdapter extends TypeAdapter { + @override + final int typeId = 0; + + @override + LevelEntity read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return LevelEntity( + id: fields[0] as int?, + order: fields[1] as int?, + title: fields[2] as String?, + questions: (fields[3] as List?)?.cast(), + ); + } + + @override + void write(BinaryWriter writer, LevelEntity obj) { + writer + ..writeByte(4) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.order) + ..writeByte(2) + ..write(obj.title) + ..writeByte(3) + ..write(obj.questions); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is LevelEntityAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/features/level/domain/entity/level_location.dart b/lib/features/level/domain/entity/level_location.dart index 330e984..ca2bce8 100644 --- a/lib/features/level/domain/entity/level_location.dart +++ b/lib/features/level/domain/entity/level_location.dart @@ -1,11 +1,11 @@ -class MissionLocation { +class LevelLocation { final int? index; final double? top; final double? bottom; final double? right; final double? left; - const MissionLocation({ + const LevelLocation({ this.index, this.top, this.bottom, diff --git a/lib/features/level/domain/repository/level_repository.dart b/lib/features/level/domain/repository/level_repository.dart index 8d9b560..672d6f4 100644 --- a/lib/features/level/domain/repository/level_repository.dart +++ b/lib/features/level/domain/repository/level_repository.dart @@ -4,5 +4,5 @@ import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; abstract class ILevelRepository { - Future> getData({required LevelParams params}); + Future, MyException>> getLevels({required LevelParams params}); } diff --git a/lib/features/level/domain/usecases/get_level_usecase.dart b/lib/features/level/domain/usecases/get_level_usecase.dart index 76e4caf..f141d4c 100644 --- a/lib/features/level/domain/usecases/get_level_usecase.dart +++ b/lib/features/level/domain/usecases/get_level_usecase.dart @@ -5,13 +5,13 @@ import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; import 'package:hadi_hoda_flutter/features/level/domain/repository/level_repository.dart'; -class GetLevelUseCase implements UseCase { +class GetLevelUseCase implements UseCase, LevelParams> { final ILevelRepository repository; const GetLevelUseCase(this.repository); @override - Future> call(LevelParams params) { - return repository.getData(params: params); + Future, MyException>> call(LevelParams params) { + return repository.getLevels(params: params); } } diff --git a/lib/features/level/presentation/bloc/level_bloc.dart b/lib/features/level/presentation/bloc/level_bloc.dart index b19ba7f..a92b510 100644 --- a/lib/features/level/presentation/bloc/level_bloc.dart +++ b/lib/features/level/presentation/bloc/level_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; +import 'package:hadi_hoda_flutter/core/params/level_params.dart'; import 'package:hadi_hoda_flutter/core/status/base_status.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_location.dart'; @@ -12,56 +13,60 @@ class LevelBloc extends Bloc { LevelBloc( this._getLevelUseCase, ) : super(const LevelState()) { - on(_getLevelEvent); + on(_getLevelListEvent); } /// ------------UseCases------------ final GetLevelUseCase _getLevelUseCase; /// ------------Variables------------ - final List bottomLocationList = [ - MissionLocation(bottom: -30, left: 30, index: 1), - MissionLocation(bottom: 50, left: 100, index: 2), - MissionLocation(bottom: 150, left: 60, index: 3), - MissionLocation(bottom: 210, left: 120, index: 4), - MissionLocation(bottom: 250, right: 60, index: 5), - MissionLocation(top: 170, right: 40, index: 6), - MissionLocation(top: 70, right: 70, index: 7), - MissionLocation(top: -20, right: 70, index: 8), + final List bottomLocationList = [ + LevelLocation(bottom: -30, left: 30, index: 1), + LevelLocation(bottom: 50, left: 100, index: 2), + LevelLocation(bottom: 150, left: 60, index: 3), + LevelLocation(bottom: 210, left: 120, index: 4), + LevelLocation(bottom: 250, right: 60, index: 5), + LevelLocation(top: 170, right: 40, index: 6), + LevelLocation(top: 70, right: 70, index: 7), + LevelLocation(top: -20, right: 70, index: 8), ]; - final List topLocationList = [ - MissionLocation(bottom: 30, right: 80, index: 9), - MissionLocation(bottom: 70, left: 20, index: 10), - MissionLocation(bottom: 150, left: 50, index: 11), - MissionLocation(bottom: 180, left: 140, index: 12), - MissionLocation(bottom: 260, right: 20, index: 13), - MissionLocation(bottom: 370, right: 30, index: 14), - MissionLocation(bottom: 420, left: 40, index: 15), - MissionLocation(top: 410, left: 0, index: 16), - MissionLocation(top: 320, left: 60, index: 17), - MissionLocation(top: 220, left: 80, index: 18), - MissionLocation(top: 130, left: 20, index: 19), - MissionLocation(top: 50, left: 70, index: 20), + final List topLocationList = [ + LevelLocation(bottom: 30, right: 80, index: 9), + LevelLocation(bottom: 70, left: 20, index: 10), + LevelLocation(bottom: 150, left: 50, index: 11), + LevelLocation(bottom: 180, left: 140, index: 12), + LevelLocation(bottom: 260, right: 20, index: 13), + LevelLocation(bottom: 370, right: 30, index: 14), + LevelLocation(bottom: 420, left: 40, index: 15), + LevelLocation(top: 410, left: 0, index: 16), + LevelLocation(top: 320, left: 60, index: 17), + LevelLocation(top: 220, left: 80, index: 18), + LevelLocation(top: 130, left: 20, index: 19), + LevelLocation(top: 50, left: 70, index: 20), ]; + final List bottom8LevelList = []; + final List top12LevelList = []; + /// ------------Controllers------------ /// ------------Functions------------ /// ------------Api Calls------------ - FutureOr _getLevelEvent(event, emit) async { - await _getLevelUseCase(event.levelParams).then( - (value) { - value.fold( - (data) { - emit(state.copyWith(getLevelStatus: BaseComplete(data))); - }, - (error) { - emit(state.copyWith(getLevelStatus: BaseError(error.errorMessage))); - }, - ); - }, - ); + FutureOr _getLevelListEvent(GetLevelListEvent event, + Emitter emit) async { + await _getLevelUseCase(LevelParams()).then((value) { + value.fold( + (data) { + bottom8LevelList.addAll(data.take(8)); + if(data.length > 8){ + top12LevelList.addAll(data.sublist(8, data.length)); + } + emit(state.copyWith(getLevelStatus: const BaseComplete(''))); + }, + (error) {}, + ); + }); } } diff --git a/lib/features/level/presentation/bloc/level_event.dart b/lib/features/level/presentation/bloc/level_event.dart index 231b8ad..7344eb5 100644 --- a/lib/features/level/presentation/bloc/level_event.dart +++ b/lib/features/level/presentation/bloc/level_event.dart @@ -2,4 +2,4 @@ sealed class LevelEvent { const LevelEvent(); } -class GetLevelEvent extends LevelEvent {} +class GetLevelListEvent extends LevelEvent {} diff --git a/lib/features/level/presentation/ui/level_page.dart b/lib/features/level/presentation/ui/level_page.dart index 2c86672..d0bcb59 100644 --- a/lib/features/level/presentation/ui/level_page.dart +++ b/lib/features/level/presentation/ui/level_page.dart @@ -5,6 +5,7 @@ import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart'; import 'package:hadi_hoda_flutter/core/utils/my_image.dart'; import 'package:hadi_hoda_flutter/core/utils/screen_size.dart'; import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_bloc.dart'; +import 'package:hadi_hoda_flutter/features/level/presentation/bloc/level_state.dart'; import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/bottom_path.dart'; import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/hint_level_widget.dart'; import 'package:hadi_hoda_flutter/features/level/presentation/ui/widgets/level_widget.dart'; @@ -71,25 +72,27 @@ class LevelPage extends StatelessWidget { return Positioned( top: context.heightScreen * 0.16, left: context.widthScreen * 0.15, - child: Stack( - children: [ - TopPath( - height: 950, - width: context.widthScreen * 0.6, - ), - ...List.generate( - context.read().topLocationList.length, - (index) => Positioned( - top: context.read().topLocationList[index].top, - bottom: context.read().topLocationList[index].bottom, - right: context.read().topLocationList[index].right, - left: context.read().topLocationList[index].left, - child: LevelWidget( - index: context.read().topLocationList[index].index ?? 0, + child: BlocBuilder( + builder: (context, state) => Stack( + children: [ + TopPath( + height: 950, + width: context.widthScreen * 0.6, + ), + ...List.generate( + context.read().top12LevelList.length, + (index) => Positioned( + top: context.read().topLocationList[index].top, + bottom: context.read().topLocationList[index].bottom, + right: context.read().topLocationList[index].right, + left: context.read().topLocationList[index].left, + child: LevelWidget( + index: context.read().topLocationList[index].index ?? 0, + ), ), - ), - ), - ], + ), + ], + ), ), ); } @@ -98,26 +101,28 @@ class LevelPage extends StatelessWidget { return Positioned( bottom: context.heightScreen * 0.18, left: context.widthScreen * 0.2, - child: Stack( - clipBehavior: Clip.none, - children: [ - BottomPath( - width: context.widthScreen * 0.75, - height: context.heightScreen * 0.6, - ), - ...List.generate( - context.read().bottomLocationList.length, - (index) => Positioned( - top: context.read().bottomLocationList[index].top, - bottom: context.read().bottomLocationList[index].bottom, - right: context.read().bottomLocationList[index].right, - left: context.read().bottomLocationList[index].left, - child: LevelWidget( - index: context.read().bottomLocationList[index].index ?? 0, + child: BlocBuilder( + builder: (context, state) => Stack( + clipBehavior: Clip.none, + children: [ + BottomPath( + width: context.widthScreen * 0.75, + height: context.heightScreen * 0.6, + ), + ...List.generate( + context.read().bottom8LevelList.length, + (index) => Positioned( + top: context.read().bottomLocationList[index].top, + bottom: context.read().bottomLocationList[index].bottom, + right: context.read().bottomLocationList[index].right, + left: context.read().bottomLocationList[index].left, + child: LevelWidget( + index: context.read().bottomLocationList[index].index ?? 0, + ), ), - ), - ), - ], + ), + ], + ), ), ); } diff --git a/lib/features/level/presentation/ui/widgets/level_widget.dart b/lib/features/level/presentation/ui/widgets/level_widget.dart index 821bd75..ee6229a 100644 --- a/lib/features/level/presentation/ui/widgets/level_widget.dart +++ b/lib/features/level/presentation/ui/widgets/level_widget.dart @@ -31,7 +31,7 @@ class LevelWidget extends StatelessWidget { MyImage(image: LevelType.image[type] ?? MyAssets.level, size: 46), Text( '$index', - style: MyTextStyle.normal.copyWith(color: context.primaryColor), + style: MyTextStyle.normal26.copyWith(color: context.primaryColor), ), if(type == LevelType.current) Positioned( diff --git a/lib/features/question/data/model/answer_model.dart b/lib/features/question/data/model/answer_model.dart new file mode 100644 index 0000000..21536b3 --- /dev/null +++ b/lib/features/question/data/model/answer_model.dart @@ -0,0 +1,26 @@ +import 'package:hadi_hoda_flutter/features/question/data/model/file_model.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart'; + +class AnswerModel extends AnswerEntity { + AnswerModel({ + super.id, + super.title, + super.imageId, + super.imageInfo, + super.order, + super.isActive, + }); + + factory AnswerModel.fromJson(Map json) { + return AnswerModel( + id: json['id'], + title: json['title'], + imageId: json['image_id'], + imageInfo: json['image_info'] == null + ? null + : FileModel.fromJson(json['image_info']), + order: json['order'], + isActive: json['is_active'], + ); + } +} diff --git a/lib/features/question/data/model/file_model.dart b/lib/features/question/data/model/file_model.dart new file mode 100644 index 0000000..941f3f6 --- /dev/null +++ b/lib/features/question/data/model/file_model.dart @@ -0,0 +1,17 @@ +import 'package:hadi_hoda_flutter/features/question/domain/entity/file_entity.dart'; + +class FileModel extends FileEntity { + FileModel({ + super.filename, + super.size, + super.extension, + }); + + factory FileModel.fromJson(Map json) { + return FileModel( + filename: json['filename'], + size: json['size'], + extension: json['extension'], + ); + } +} diff --git a/lib/features/question/data/model/question_model.dart b/lib/features/question/data/model/question_model.dart index 489b66c..e634491 100644 --- a/lib/features/question/data/model/question_model.dart +++ b/lib/features/question/data/model/question_model.dart @@ -1,13 +1,34 @@ +import 'package:hadi_hoda_flutter/features/question/data/model/answer_model.dart'; +import 'package:hadi_hoda_flutter/features/question/data/model/file_model.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart'; import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart'; class QuestionModel extends QuestionEntity { - const QuestionModel({ + QuestionModel({ super.id, + super.title, + super.audioId, + super.audioInfo, + super.order, + super.correctAnswer, + super.isActive, + super.answers, }); factory QuestionModel.fromJson(Map json) { return QuestionModel( id: json['id'], + title: json['title'], + audioId: json['audio_id'], + audioInfo: json['audio_info'] == null + ? null + : FileModel.fromJson(json['audio_info']), + order: json['order'], + correctAnswer: json['correct_answer'], + isActive: json['is_active'], + answers: json['answers'] + ?.map((e) => AnswerModel.fromJson(e)) + .toList(), ); } } diff --git a/lib/features/question/domain/entity/answer_entity.dart b/lib/features/question/domain/entity/answer_entity.dart new file mode 100644 index 0000000..e3204e0 --- /dev/null +++ b/lib/features/question/domain/entity/answer_entity.dart @@ -0,0 +1,29 @@ +import 'package:hadi_hoda_flutter/features/question/domain/entity/file_entity.dart'; +import 'package:hive/hive.dart'; + +part 'answer_entity.g.dart'; + +@HiveType(typeId: 2) +class AnswerEntity extends HiveObject { + @HiveField(0) + int? id; + @HiveField(1) + String? title; + @HiveField(2) + String? imageId; + @HiveField(3) + FileEntity? imageInfo; + @HiveField(4) + int? order; + @HiveField(5) + bool? isActive; + + AnswerEntity({ + this.id, + this.title, + this.imageId, + this.imageInfo, + this.order, + this.isActive, + }); +} diff --git a/lib/features/question/domain/entity/answer_entity.g.dart b/lib/features/question/domain/entity/answer_entity.g.dart new file mode 100644 index 0000000..34a06de --- /dev/null +++ b/lib/features/question/domain/entity/answer_entity.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'answer_entity.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class AnswerEntityAdapter extends TypeAdapter { + @override + final int typeId = 2; + + @override + AnswerEntity read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return AnswerEntity( + id: fields[0] as int?, + title: fields[1] as String?, + imageId: fields[2] as String?, + imageInfo: fields[3] as FileEntity?, + order: fields[4] as int?, + isActive: fields[5] as bool?, + ); + } + + @override + void write(BinaryWriter writer, AnswerEntity obj) { + writer + ..writeByte(6) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.title) + ..writeByte(2) + ..write(obj.imageId) + ..writeByte(3) + ..write(obj.imageInfo) + ..writeByte(4) + ..write(obj.order) + ..writeByte(5) + ..write(obj.isActive); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AnswerEntityAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/features/question/domain/entity/file_entity.dart b/lib/features/question/domain/entity/file_entity.dart new file mode 100644 index 0000000..9446384 --- /dev/null +++ b/lib/features/question/domain/entity/file_entity.dart @@ -0,0 +1,19 @@ +import 'package:hive/hive.dart'; + +part 'file_entity.g.dart'; + +@HiveType(typeId: 3) +class FileEntity extends HiveObject { + @HiveField(0) + String? filename; + @HiveField(1) + int? size; + @HiveField(2) + String? extension; + + FileEntity({ + this.filename, + this.size, + this.extension, + }); +} diff --git a/lib/features/question/domain/entity/file_entity.g.dart b/lib/features/question/domain/entity/file_entity.g.dart new file mode 100644 index 0000000..964bef5 --- /dev/null +++ b/lib/features/question/domain/entity/file_entity.g.dart @@ -0,0 +1,47 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'file_entity.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class FileEntityAdapter extends TypeAdapter { + @override + final int typeId = 3; + + @override + FileEntity read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return FileEntity( + filename: fields[0] as String?, + size: fields[1] as int?, + extension: fields[2] as String?, + ); + } + + @override + void write(BinaryWriter writer, FileEntity obj) { + writer + ..writeByte(3) + ..writeByte(0) + ..write(obj.filename) + ..writeByte(1) + ..write(obj.size) + ..writeByte(2) + ..write(obj.extension); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is FileEntityAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/features/question/domain/entity/question_entity.dart b/lib/features/question/domain/entity/question_entity.dart index 377f2b1..4c05d26 100644 --- a/lib/features/question/domain/entity/question_entity.dart +++ b/lib/features/question/domain/entity/question_entity.dart @@ -1,14 +1,36 @@ -import 'package:equatable/equatable.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/file_entity.dart'; +import 'package:hive/hive.dart'; -class QuestionEntity extends Equatable { - final int? id; +part 'question_entity.g.dart'; - const QuestionEntity({ +@HiveType(typeId: 1) +class QuestionEntity extends HiveObject { + @HiveField(0) + int? id; + @HiveField(1) + String? title; + @HiveField(2) + String? audioId; + @HiveField(3) + FileEntity? audioInfo; + @HiveField(4) + int? order; + @HiveField(5) + int? correctAnswer; + @HiveField(6) + bool? isActive; + @HiveField(7) + List? answers; + + QuestionEntity({ this.id, + this.title, + this.audioId, + this.audioInfo, + this.order, + this.correctAnswer, + this.isActive, + this.answers, }); - - @override - List get props => [ - id, - ]; } diff --git a/lib/features/question/domain/entity/question_entity.g.dart b/lib/features/question/domain/entity/question_entity.g.dart new file mode 100644 index 0000000..d90e05f --- /dev/null +++ b/lib/features/question/domain/entity/question_entity.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'question_entity.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class QuestionEntityAdapter extends TypeAdapter { + @override + final int typeId = 1; + + @override + QuestionEntity read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return QuestionEntity( + id: fields[0] as int?, + title: fields[1] as String?, + audioId: fields[2] as String?, + audioInfo: fields[3] as FileEntity?, + order: fields[4] as int?, + correctAnswer: fields[5] as int?, + isActive: fields[6] as bool?, + answers: (fields[7] as List?)?.cast(), + ); + } + + @override + void write(BinaryWriter writer, QuestionEntity obj) { + writer + ..writeByte(8) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.title) + ..writeByte(2) + ..write(obj.audioId) + ..writeByte(3) + ..write(obj.audioInfo) + ..writeByte(4) + ..write(obj.order) + ..writeByte(5) + ..write(obj.correctAnswer) + ..writeByte(6) + ..write(obj.isActive) + ..writeByte(7) + ..write(obj.answers); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is QuestionEntityAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/init_bindings.dart b/lib/init_bindings.dart index 325ee32..a51866f 100644 --- a/lib/init_bindings.dart +++ b/lib/init_bindings.dart @@ -1,15 +1,28 @@ +import 'dart:io'; + +import 'package:hadi_hoda_flutter/core/constants/my_constants.dart'; import 'package:hadi_hoda_flutter/core/network/http_request.dart'; import 'package:hadi_hoda_flutter/core/network/http_request_impl.dart'; +import 'package:hadi_hoda_flutter/features/home/data/datasource/home_datasource.dart'; +import 'package:hadi_hoda_flutter/features/home/data/repository_impl/home_repository_impl.dart'; +import 'package:hadi_hoda_flutter/features/home/domain/repository/home_repository.dart'; +import 'package:hadi_hoda_flutter/features/home/domain/usecases/get_home_usecase.dart'; import 'package:hadi_hoda_flutter/features/intro/data/datasource/intro_datasource.dart'; import 'package:hadi_hoda_flutter/features/intro/data/repository_impl/intro_repository_impl.dart'; import 'package:hadi_hoda_flutter/features/intro/domain/repository/intro_repository.dart'; -import 'package:hadi_hoda_flutter/features/intro/domain/usecases/get_intro_usecase.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/usecases/extract_data_usecase.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/usecases/loading_stream_usecase.dart'; +import 'package:hadi_hoda_flutter/features/intro/domain/usecases/save_levels_usecase.dart'; import 'package:hadi_hoda_flutter/features/level/data/datasource/level_datasource.dart'; import 'package:hadi_hoda_flutter/features/level/data/repository_impl/level_repository_impl.dart'; +import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; import 'package:hadi_hoda_flutter/features/level/domain/repository/level_repository.dart'; import 'package:hadi_hoda_flutter/features/level/domain/usecases/get_level_usecase.dart'; import 'package:hadi_hoda_flutter/features/question/data/datasource/question_datasource.dart'; import 'package:hadi_hoda_flutter/features/question/data/repository_impl/question_repository_impl.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/file_entity.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart'; import 'package:hadi_hoda_flutter/features/question/domain/repository/question_repository.dart'; import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_question_usecase.dart'; import 'package:hadi_hoda_flutter/features/sample/data/datasource/sample_datasource.dart'; @@ -17,6 +30,8 @@ import 'package:hadi_hoda_flutter/features/sample/data/repository_impl/sample_re import 'package:hadi_hoda_flutter/features/sample/domain/repository/sample_repository.dart'; import 'package:hadi_hoda_flutter/features/sample/domain/usecases/get_sample_usecase.dart'; import 'package:get_it/get_it.dart'; +import 'package:hive/hive.dart'; +import 'package:path_provider/path_provider.dart'; final GetIt locator = GetIt.I; @@ -32,7 +47,14 @@ void initBindings() { /// Intro Feature locator.registerLazySingleton(() => IntroDatasourceImpl(locator())); locator.registerLazySingleton(() => IntroRepositoryImpl(locator())); - locator.registerLazySingleton(() => GetIntroUseCase(locator())); + locator.registerLazySingleton(() => SaveLevelsUseCase(locator())); + locator.registerLazySingleton(() => ExtractDataUseCase(locator())); + locator.registerLazySingleton(() => LoadingStreamUseCase(locator())); + + /// Home Feature + locator.registerLazySingleton(() => HomeDatasourceImpl(locator())); + locator.registerLazySingleton(() => HomeRepositoryImpl(locator())); + locator.registerLazySingleton(() => GetHomeUseCase(locator())); /// Question Feature locator.registerLazySingleton(() => QuestionDatasourceImpl(locator())); @@ -40,7 +62,19 @@ void initBindings() { locator.registerLazySingleton(() => GetQuestionUseCase(locator())); /// Level Feature - locator.registerLazySingleton(() => LevelDatasourceImpl(locator())); + locator.registerLazySingleton(() => LocalLevelDatasourceImpl()); locator.registerLazySingleton(() => LevelRepositoryImpl(locator())); locator.registerLazySingleton(() => GetLevelUseCase(locator())); } + +Future initDataBase() async { + final Directory dir = await getApplicationDocumentsDirectory(); + Hive + ..init(dir.path) + ..registerAdapter(FileEntityAdapter()) + ..registerAdapter(AnswerEntityAdapter()) + ..registerAdapter(QuestionEntityAdapter()) + ..registerAdapter(LevelEntityAdapter()); + + await Hive.openBox(MyConstants.levelBox); +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 613e7c8..4ca56b1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,13 +7,13 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:hadi_hoda_flutter/common_ui/theme/my_theme.dart'; import 'package:hadi_hoda_flutter/core/auth_storage/auth_storage.dart'; import 'package:hadi_hoda_flutter/core/routers/my_routes.dart'; -import 'package:hadi_hoda_flutter/core/utils/my_scroll_behavior.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); initBindings(); await Future.wait([ LocalStorage.init(), + initDataBase(), ]); AuthStorage.loadData(); runApp(const MainApp()); @@ -30,7 +30,6 @@ class MainApp extends StatelessWidget { themeMode: ThemeService.getTheme(), locale: const Locale('en', 'US'), supportedLocales: const [Locale('en', 'US')], - scrollBehavior: MyScrollBehavior(), routerConfig: appPages, localizationsDelegates: const [ AppLocalizations.delegate, diff --git a/pubspec.lock b/pubspec.lock index 0a9a3de..5e4c4f8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,22 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + url: "https://pub.dev" + source: hosted + version: "67.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + url: "https://pub.dev" + source: hosted + version: "6.4.1" archive: dependency: transitive description: @@ -41,6 +57,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" + url: "https://pub.dev" + source: hosted + version: "4.0.4" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + url: "https://pub.dev" + source: hosted + version: "2.4.13" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + url: "https://pub.dev" + source: hosted + version: "7.3.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d + url: "https://pub.dev" + source: hosted + version: "8.12.0" characters: dependency: transitive description: @@ -49,6 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" clock: dependency: transitive description: @@ -57,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" + url: "https://pub.dev" + source: hosted + version: "4.11.0" collection: dependency: transitive description: @@ -65,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" crypto: dependency: transitive description: @@ -73,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + url: "https://pub.dev" + source: hosted + version: "2.3.6" dio: dependency: "direct main" description: @@ -129,11 +241,27 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_archive: + dependency: "direct main" + description: + name: flutter_archive + sha256: "5ca235f304c12bf468979235f400f79846d204169d715939e39197106f5fc970" + url: "https://pub.dev" + source: hosted + version: "6.0.3" flutter_bloc: dependency: "direct main" description: @@ -173,6 +301,14 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" get_it: dependency: "direct main" description: @@ -181,6 +317,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.2.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" go_router: dependency: "direct main" description: @@ -197,6 +341,30 @@ packages: url: "https://pub.dev" source: hosted version: "6.3.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + url: "https://pub.dev" + source: hosted + version: "2.0.1" http: dependency: transitive description: @@ -205,6 +373,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" http_parser: dependency: transitive description: @@ -221,6 +397,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -309,6 +509,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" path: dependency: transitive description: @@ -334,7 +542,7 @@ packages: source: hosted version: "1.1.0" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" @@ -405,6 +613,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" posix: dependency: transitive description: @@ -429,6 +645,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" shared_preferences: dependency: "direct main" description: @@ -485,6 +717,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + url: "https://pub.dev" + source: hosted + version: "2.0.1" showcaseview: dependency: "direct main" description: @@ -498,6 +746,22 @@ packages: description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + url: "https://pub.dev" + source: hosted + version: "1.3.5" source_span: dependency: transitive description: @@ -522,6 +786,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -546,6 +818,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.6" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.dev" + source: hosted + version: "1.0.2" typed_data: dependency: transitive description: @@ -594,6 +874,14 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" + url: "https://pub.dev" + source: hosted + version: "1.1.3" web: dependency: transitive description: @@ -602,6 +890,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" xdg_directories: dependency: transitive description: @@ -618,6 +922,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.9.2 <4.0.0" flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index aaef526..59e9aa3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: equatable: ^2.0.7 flutter: sdk: flutter + flutter_archive: ^6.0.3 flutter_bloc: ^9.1.1 flutter_localizations: sdk: flutter @@ -20,8 +21,10 @@ dependencies: get_it: ^8.2.0 go_router: ^16.1.0 google_fonts: ^6.3.2 + hive: ^2.2.3 intl: ^0.20.2 path_drawing: ^1.0.1 + path_provider: ^2.1.5 pretty_dio_logger: ^1.4.0 shared_preferences: ^2.5.3 showcaseview: ^4.0.1 @@ -30,14 +33,19 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 + hive_generator: ^2.0.1 + build_runner: ^2.4.13 flutter: uses-material-design: true generate: true assets: - - assets/images/ + - assets/audio/ + - assets/data/ - assets/fonts/ + - assets/images/ + - assets/json/ fonts: