diff --git a/assets/images/landing_1.jpg b/assets/images/landing_1.jpg new file mode 100644 index 0000000..5eff078 Binary files /dev/null and b/assets/images/landing_1.jpg differ diff --git a/assets/images/landing_2.jpg b/assets/images/landing_2.jpg new file mode 100644 index 0000000..8345d37 Binary files /dev/null and b/assets/images/landing_2.jpg differ diff --git a/assets/images/landing_3.jpg b/assets/images/landing_3.jpg new file mode 100644 index 0000000..180fb68 Binary files /dev/null and b/assets/images/landing_3.jpg differ diff --git a/assets/images/onboarding_1.png b/assets/images/loading.png similarity index 100% rename from assets/images/onboarding_1.png rename to assets/images/loading.png diff --git a/assets/images/onboarding_2.png b/assets/images/onboarding_2.png deleted file mode 100644 index a53af54..0000000 Binary files a/assets/images/onboarding_2.png and /dev/null differ diff --git a/assets/images/onboarding_3.png b/assets/images/onboarding_3.png deleted file mode 100644 index 4d300f4..0000000 Binary files a/assets/images/onboarding_3.png and /dev/null differ diff --git a/assets/translations/am.json b/assets/translations/am.json index 229e41a..3f5a456 100644 --- a/assets/translations/am.json +++ b/assets/translations/am.json @@ -32,18 +32,21 @@ "code_sent_to_phone": "ኮዱ ወደ ስልክ ቁጥርዎ ተልኳል", "code_sent_to_email": "ኮዱ ወደ ኢሜል ተልኳል", "resend_code_in": "ኮዱን እንደገና ለመላክ የቀረው ጊዜ", - "reset_password": " የይለፍ ቃልን ይቀይሩ ", + "reset_password": " የይለፍ ቃልን ይቀይሩ", "enter_email_reset_code": "ኢሜይልዎን ያስገቡ። የይለፍ ቃል መለወጫ ኮድ እንልክልዎታለን።" , "please_wait": "እባክዎ ይጠብቁ", "reset_code_sent": "የመቀየሪያ ኮድ በተሳካ ሁኔታ ተልኳል" , "reset_code": " የመቀየሪያ ኮድ ", "new_password": "አዲስ የይለፍ ቃል", "logged_in_successfully": "በተሳካ ሁኔታ ገብተዋል", - "view_course": " ኮርሱን ይመልከቱ ", - "take_practice": " ልምምድ ያድርጉ ", + "view_course": " ኮርሱን ይመልከቱ", + "continue_learning": "መማርን ይቀጥሉ", + "start_learning": "ትምህርትን ይጀምሩ", + "completed": "ተጠናቋል", + "take_practice": " ልምምድ ያድርጉ", "your_current_level": "የአሁኑ ደረጃዎ", "overall_progress": "አጠቃላይ እድገት", - "great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው ", + "great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው", "view_module": "ሞጁሉን ይመልከቱ", "progress": "እድገት", "keep_going": " ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ", @@ -56,7 +59,7 @@ "learn": "ይማሩ ", "course": "ኮርስ", "profile": " ፕሮፋይል ", - "speaking_partner": "የንግግር ጓደኛ ", + "speaking_partner": "የንግግር ጓደኛ", "practice_what_you_learned": "አሁን የተማሩትን እንለማመድ", "practice_questions": "ጥቂት ጥያቄዎችን እጠይቃለሁ እና መልስ መስጠት ይችላሉ", "start_practice": "ልምምድ ጀምር", @@ -65,7 +68,7 @@ "continue_practice": "ልምምዱን ይቀጥሉ", "end_session": "ክፍለ ጊዜውን ያብቁ ", "tap_start_to_listen": "ለማዳመጥ የጀምር ቁልፉን ይጫኑ", - "practice_speaking": "ንግግርን ይለማመዱ ", + "practice_speaking": "ንግግርን ይለማመዱ", "tap_microphone": "ለመናገር ማይክሮፎኑን ይጫኑ", "reply": "እንደገና አዳምጥ", "cancel": "ይቅር", @@ -73,7 +76,7 @@ "practice_completed": "ልምምዱ ተጠናቅቋል", "great_improvement": "በዚህኛው በራስ መተማመንዎ ጨምሯል፤ ትልቅ መሻሻል ነው", "practice_again": "እንደገና ይለማመዱ", - "conversation_review": "የንግግር ግምገማ ", + "conversation_review": "የንግግር ግምገማ", "result": "ውጤት", "quick_tip": "ጠቃሚ ምክር", "retry": "እንደገና ይሞክሩ", @@ -90,8 +93,33 @@ "phone_number": "የስልክ ቁጥር", "country": "ሀገር", "region": "ክልል", - "occupation": "የስራ መስክ ", - "save_changes": "ለውጦችን ያስቀምጡ" + "select_region": "ክልል ይምረጡ", + "enter_your_city": "ከተማዎን ያስገቡ", + "occupation": "የስራ መስክ", + "select_occupation": "ሙያዎን ይምረጡ", + "save_changes": "ለውጦችን ያስቀምጡ", + "my_progress": "የእኔ እድገት", + "track_your_achievement": "ስኬቶችዎን እና ተከታታይ የትምህርት ጉዞዎን ይከታተሉ", + "account_and_privacy": "መለያ እና ግላዊነት", + "manage_settings": "ቅንብሮችን እና የመተግበሪያ ምርጫዎችን ያስተዳድሩ", + "support": "ድጋፍ", + "get_help": "በስልክ ወይም በቴሌግራም እገዛ ያግኙ", + "logout": "ውጣ", + "app_settings": "የመተግበሪያ ቅንብሮች", + "legal_and_information": "ሕጋዊ እና መረጃ", + "change_language": "ቋንቋ ቀይር", + "terms_and_conditions": "ውሎች እና ሁኔታዎች", + "delete_account": "መለያ ሰርዝ", + "language_preference": "የቋንቋ ምርጫ", + "choose_your_language": "ለውጦችን አስቀምጥ", + "switch_language_anytime": "ቋንቋዎችን በማንኛውም ጊዜ መቀየር ይችላሉ", + "need_help": "እገዛ ይፈልጋሉ?", + "call_support": "የስልክ ድጋፍ", + "talk_with_support": "በቀጥታ ከድጋፍ ቡድናችን ጋር ይነጋገሩ", + "telegram_support": "የቴሌግራም ድጋፍ", + "chat_via_telegram": "በቴሌግራም በፍጥነት ይወያዩ", + "call_our_support": "ከ3 ጠዋት እስከ 12 ማታ ድረስ የድጋፍ ቡድናችንን ይደውሉ", + "tap_to_call": "ለመደወል ይንኩ" } diff --git a/assets/translations/en.json b/assets/translations/en.json index 09e141c..29def5e 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -39,6 +39,9 @@ "reset_code": "Reset code", "new_password": "New password", "logged_in_successfully": "Logged in successfully", + "continue_learning": "Continue Learning", + "start_learning": "Start Learning", + "completed": "Completed", "view_course": "View course", "take_practice": "Take practice", "your_current_level": "Your current level", @@ -90,6 +93,32 @@ "phone_number": "Phone number", "country": "Country", "region": "Region", + "select_region": "Select region", + "enter_your_city": "Enter your city", "occupation": "Occupation", - "save_changes": "Save changes" + "select_occupation": "Select occupation", + "save_changes": "Save changes", + "my_progress": "My progress", + "track_your_achievement": "Track your achievements and learning streak", + "account_and_privacy": "Account & Privacy", + "manage_settings": "Manage settings and app preference", + "support": "Support", + "get_help": "Get help through phone or Telegram", + "logout": "Logout", + "app_settings": "App settings", + "legal_and_information": "Legal & Information", + "change_language": "Change language", + "terms_and_conditions":"Terms & Conditions", + "delete_account": "Delete account", + "language_preference": "Language preference", + "choose_your_language": "Choose your language", + "switch_language_anytime": "You can switch languages anytime", + "need_help": "Need help?", + "call_support": "Call support", + "talk_with_support": "Talk with our support team directly", + "telegram_support": "Telegram support", + "chat_via_telegram" :"Chat instantly via Telegram", + "call_our_support": "Call our support team between 9 AM - 6 PM", + "tap_to_call": "Tap to call" + } diff --git a/lib/app/app.dart b/lib/app/app.dart index 0367f95..d7cee32 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -24,7 +24,6 @@ import 'package:yimaru_app/services/api_service.dart'; import 'package:yimaru_app/services/secure_storage_service.dart'; import 'package:yimaru_app/services/dio_service.dart'; import 'package:yimaru_app/services/status_checker_service.dart'; -import 'package:yimaru_app/ui/views/welcome/welcome_view.dart'; import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart'; import 'package:yimaru_app/services/permission_handler_service.dart'; import 'package:yimaru_app/services/image_picker_service.dart'; @@ -33,10 +32,8 @@ import 'package:yimaru_app/services/image_downloader_service.dart'; import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart'; import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart'; import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart'; -import 'package:yimaru_app/ui/views/course_practice/course_practice_view.dart'; import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart'; import 'package:yimaru_app/ui/views/failure/failure_view.dart'; -import 'package:yimaru_app/ui/views/course_lesson/course_lesson_view.dart'; import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart'; import 'package:yimaru_app/services/notification_service.dart'; import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart'; @@ -45,7 +42,6 @@ import 'package:yimaru_app/services/course_service.dart'; import 'package:yimaru_app/ui/views/course/course_view.dart'; import 'package:yimaru_app/services/audio_player_service.dart'; import 'package:yimaru_app/services/voice_recorder_service.dart'; -import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_view.dart'; import 'package:yimaru_app/services/in_app_update_service.dart'; import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'; import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart'; @@ -60,6 +56,7 @@ import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart'; import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart'; import 'package:yimaru_app/services/localization_service.dart'; import 'package:yimaru_app/ui/views/landing/landing_view.dart'; +import 'package:yimaru_app/ui/views/course_module/course_module_view.dart'; // @stacked-import @StackedApp( @@ -81,19 +78,15 @@ import 'package:yimaru_app/ui/views/landing/landing_view.dart'; MaterialRoute(page: RegisterView), MaterialRoute(page: LoginView), MaterialRoute(page: LearnModuleView), - MaterialRoute(page: WelcomeView), MaterialRoute(page: LearnLessonView), MaterialRoute(page: ForgetPasswordView), MaterialRoute(page: LearnLessonDetailView), MaterialRoute(page: LearnPracticeView), - MaterialRoute(page: CoursePracticeView), MaterialRoute(page: CoursePaymentView), MaterialRoute(page: FailureView), - MaterialRoute(page: CourseLessonView), MaterialRoute(page: CourseLessonDetailView), MaterialRoute(page: DuolingoView), MaterialRoute(page: CourseView), - MaterialRoute(page: CoursePracticeQuestionView), MaterialRoute(page: LearnProgramView), MaterialRoute(page: LearnCourseView), MaterialRoute(page: AssessmentView), @@ -102,6 +95,8 @@ import 'package:yimaru_app/ui/views/landing/landing_view.dart'; MaterialRoute(page: CourseCatalogView), MaterialRoute(page: CourseUnitView), MaterialRoute(page: LandingView), + MaterialRoute(page: CourseModuleView), + MaterialRoute(page: LearnCourseView), // @stacked-route ], dependencies: [ diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart index 960110d..2171016 100644 --- a/lib/app/app.router.dart +++ b/lib/app/app.router.dart @@ -6,59 +6,56 @@ // ************************************************************************** // ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:flutter/material.dart' as _i37; import 'package:flutter/material.dart'; -import 'package:flutter/material.dart' as _i40; import 'package:stacked/stacked.dart' as _i1; -import 'package:stacked_services/stacked_services.dart' as _i48; -import 'package:yimaru_app/models/course.dart' as _i45; -import 'package:yimaru_app/models/course_catalog.dart' as _i47; -import 'package:yimaru_app/models/course_lesson.dart' as _i46; -import 'package:yimaru_app/models/learn_course.dart' as _i41; -import 'package:yimaru_app/models/learn_lesson.dart' as _i43; -import 'package:yimaru_app/models/learn_module.dart' as _i42; -import 'package:yimaru_app/ui/common/enmus.dart' as _i44; +import 'package:stacked_services/stacked_services.dart' as _i46; +import 'package:yimaru_app/models/course.dart' as _i42; +import 'package:yimaru_app/models/course_catalog.dart' as _i44; +import 'package:yimaru_app/models/course_lesson.dart' as _i43; +import 'package:yimaru_app/models/course_module.dart' as _i45; +import 'package:yimaru_app/models/learn_course.dart' as _i38; +import 'package:yimaru_app/models/learn_lesson.dart' as _i40; +import 'package:yimaru_app/models/learn_module.dart' as _i39; +import 'package:yimaru_app/ui/common/enmus.dart' as _i41; import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart' as _i9; -import 'package:yimaru_app/ui/views/arif_pay/arif_pay_view.dart' as _i36; -import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i34; +import 'package:yimaru_app/ui/views/arif_pay/arif_pay_view.dart' as _i32; +import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i30; import 'package:yimaru_app/ui/views/call_support/call_support_view.dart' as _i12; -import 'package:yimaru_app/ui/views/course/course_view.dart' as _i30; +import 'package:yimaru_app/ui/views/course/course_view.dart' as _i27; import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart' - as _i37; -import 'package:yimaru_app/ui/views/course_lesson/course_lesson_view.dart' - as _i27; + as _i33; import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart' - as _i28; -import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart' as _i25; -import 'package:yimaru_app/ui/views/course_practice/course_practice_view.dart' - as _i24; -import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_view.dart' - as _i31; -import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart' as _i38; +import 'package:yimaru_app/ui/views/course_module/course_module_view.dart' + as _i36; +import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart' + as _i23; +import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart' as _i34; import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7; -import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart' as _i29; -import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i26; +import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart' as _i26; +import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i24; import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart' - as _i21; + as _i20; import 'package:yimaru_app/ui/views/home/home_view.dart' as _i2; -import 'package:yimaru_app/ui/views/landing/landing_view.dart' as _i39; +import 'package:yimaru_app/ui/views/landing/landing_view.dart' as _i35; import 'package:yimaru_app/ui/views/language/language_view.dart' as _i13; import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart' - as _i33; + as _i29; import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart' - as _i20; + as _i19; import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart' - as _i22; + as _i21; import 'package:yimaru_app/ui/views/learn_module/learn_module_view.dart' as _i18; import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart' - as _i23; + as _i22; import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart' - as _i32; + as _i28; import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_view.dart' - as _i35; + as _i31; import 'package:yimaru_app/ui/views/login/login_view.dart' as _i17; import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i3; import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart' @@ -74,7 +71,6 @@ import 'package:yimaru_app/ui/views/telegram_support/telegram_support_view.dart' as _i11; import 'package:yimaru_app/ui/views/terms_and_conditions/terms_and_conditions_view.dart' as _i15; -import 'package:yimaru_app/ui/views/welcome/welcome_view.dart' as _i19; class Routes { static const homeView = '/home-view'; @@ -111,8 +107,6 @@ class Routes { static const learnModuleView = '/learn-module-view'; - static const welcomeView = '/welcome-view'; - static const learnLessonView = '/learn-lesson-view'; static const forgetPasswordView = '/forget-password-view'; @@ -121,22 +115,16 @@ class Routes { static const learnPracticeView = '/learn-practice-view'; - static const coursePracticeView = '/course-practice-view'; - static const coursePaymentView = '/course-payment-view'; static const failureView = '/failure-view'; - static const courseLessonView = '/course-lesson-view'; - static const courseLessonDetailView = '/course-lesson-detail-view'; static const duolingoView = '/duolingo-view'; static const courseView = '/course-view'; - static const coursePracticeQuestionView = '/course-practice-question-view'; - static const learnProgramView = '/learn-program-view'; static const learnCourseView = '/learn-course-view'; @@ -153,6 +141,9 @@ class Routes { static const landingView = '/landing-view'; + static const courseModuleView = '/course-module-view'; + + static const all = { homeView, onboardingView, @@ -171,19 +162,15 @@ class Routes { registerView, loginView, learnModuleView, - welcomeView, learnLessonView, forgetPasswordView, learnLessonDetailView, learnPracticeView, - coursePracticeView, coursePaymentView, failureView, - courseLessonView, courseLessonDetailView, duolingoView, courseView, - coursePracticeQuestionView, learnProgramView, learnCourseView, assessmentView, @@ -192,6 +179,7 @@ class Routes { courseCatalogView, courseUnitView, landingView, + courseModuleView, }; } @@ -265,89 +253,81 @@ class StackedRouter extends _i1.RouterBase { Routes.learnModuleView, page: _i18.LearnModuleView, ), - _i1.RouteDef( - Routes.welcomeView, - page: _i19.WelcomeView, - ), _i1.RouteDef( Routes.learnLessonView, - page: _i20.LearnLessonView, + page: _i19.LearnLessonView, ), _i1.RouteDef( Routes.forgetPasswordView, - page: _i21.ForgetPasswordView, + page: _i20.ForgetPasswordView, ), _i1.RouteDef( Routes.learnLessonDetailView, - page: _i22.LearnLessonDetailView, + page: _i21.LearnLessonDetailView, ), _i1.RouteDef( Routes.learnPracticeView, - page: _i23.LearnPracticeView, - ), - _i1.RouteDef( - Routes.coursePracticeView, - page: _i24.CoursePracticeView, + page: _i22.LearnPracticeView, ), _i1.RouteDef( Routes.coursePaymentView, - page: _i25.CoursePaymentView, + page: _i23.CoursePaymentView, ), _i1.RouteDef( Routes.failureView, - page: _i26.FailureView, - ), - _i1.RouteDef( - Routes.courseLessonView, - page: _i27.CourseLessonView, + page: _i24.FailureView, ), _i1.RouteDef( Routes.courseLessonDetailView, - page: _i28.CourseLessonDetailView, + page: _i25.CourseLessonDetailView, ), _i1.RouteDef( Routes.duolingoView, - page: _i29.DuolingoView, + page: _i26.DuolingoView, ), _i1.RouteDef( Routes.courseView, - page: _i30.CourseView, - ), - _i1.RouteDef( - Routes.coursePracticeQuestionView, - page: _i31.CoursePracticeQuestionView, + page: _i27.CourseView, ), _i1.RouteDef( Routes.learnProgramView, - page: _i32.LearnProgramView, + page: _i28.LearnProgramView, ), _i1.RouteDef( Routes.learnCourseView, - page: _i33.LearnCourseView, + page: _i29.LearnCourseView, ), _i1.RouteDef( Routes.assessmentView, - page: _i34.AssessmentView, + page: _i30.AssessmentView, ), _i1.RouteDef( Routes.learnSubscriptionView, - page: _i35.LearnSubscriptionView, + page: _i31.LearnSubscriptionView, ), _i1.RouteDef( Routes.arifPayView, - page: _i36.ArifPayView, + page: _i32.ArifPayView, ), _i1.RouteDef( Routes.courseCatalogView, - page: _i37.CourseCatalogView, + page: _i33.CourseCatalogView, ), _i1.RouteDef( Routes.courseUnitView, - page: _i38.CourseUnitView, + page: _i34.CourseUnitView, ), _i1.RouteDef( Routes.landingView, - page: _i39.LandingView, + page: _i35.LandingView, + ), + _i1.RouteDef( + Routes.courseModuleView, + page: _i36.CourseModuleView, + ), + _i1.RouteDef( + Routes.learnCourseView, + page: _i29.LearnCourseView, ), ]; @@ -356,7 +336,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const HomeViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i2.HomeView(key: args.key), settings: data, ); @@ -365,7 +345,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const OnboardingViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i3.OnboardingView(key: args.key), settings: data, ); @@ -374,7 +354,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const StartupViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i4.StartupView(key: args.key, label: args.label), settings: data, ); @@ -383,7 +363,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const ProfileViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i5.ProfileView(key: args.key), settings: data, ); @@ -392,7 +372,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const ProfileDetailViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i6.ProfileDetailView(key: args.key), settings: data, ); @@ -401,7 +381,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const DownloadsViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i7.DownloadsView(key: args.key), settings: data, ); @@ -410,7 +390,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const ProgressViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i8.ProgressView(key: args.key), settings: data, ); @@ -419,7 +399,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const AccountPrivacyViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i9.AccountPrivacyView(key: args.key), settings: data, ); @@ -428,7 +408,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const SupportViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i10.SupportView(key: args.key), settings: data, ); @@ -437,7 +417,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const TelegramSupportViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i11.TelegramSupportView(key: args.key), settings: data, ); @@ -446,7 +426,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const CallSupportViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i12.CallSupportView(key: args.key), settings: data, ); @@ -455,7 +435,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const LanguageViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i13.LanguageView(key: args.key), settings: data, ); @@ -464,7 +444,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const PrivacyPolicyViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i14.PrivacyPolicyView(key: args.key), settings: data, ); @@ -473,7 +453,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const TermsAndConditionsViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i15.TermsAndConditionsView(key: args.key), settings: data, ); @@ -482,7 +462,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const RegisterViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i16.RegisterView(key: args.key), settings: data, ); @@ -491,49 +471,40 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const LoginViewArguments(), ); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i17.LoginView(key: args.key), settings: data, ); }, _i18.LearnModuleView: (data) { final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => _i18.LearnModuleView(key: args.key, course: args.course), settings: data, ); }, - _i19.WelcomeView: (data) { - final args = data.getArgs( - orElse: () => const WelcomeViewArguments(), - ); - return _i40.MaterialPageRoute( - builder: (context) => _i19.WelcomeView(key: args.key), - settings: data, - ); - }, - _i20.LearnLessonView: (data) { + _i19.LearnLessonView: (data) { final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => - _i20.LearnLessonView(key: args.key, module: args.module), + _i19.LearnLessonView(key: args.key, module: args.module), settings: data, ); }, - _i21.ForgetPasswordView: (data) { + _i20.ForgetPasswordView: (data) { final args = data.getArgs( orElse: () => const ForgetPasswordViewArguments(), ); - return _i40.MaterialPageRoute( - builder: (context) => _i21.ForgetPasswordView(key: args.key), + return _i37.MaterialPageRoute( + builder: (context) => _i20.ForgetPasswordView(key: args.key), settings: data, ); }, - _i22.LearnLessonDetailView: (data) { + _i21.LearnLessonDetailView: (data) { final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( - builder: (context) => _i22.LearnLessonDetailView( + return _i37.MaterialPageRoute( + builder: (context) => _i21.LearnLessonDetailView( key: args.key, lesson: args.lesson, module: args.module, @@ -541,10 +512,10 @@ class StackedRouter extends _i1.RouterBase { settings: data, ); }, - _i23.LearnPracticeView: (data) { + _i22.LearnPracticeView: (data) { final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( - builder: (context) => _i23.LearnPracticeView( + return _i37.MaterialPageRoute( + builder: (context) => _i22.LearnPracticeView( key: args.key, level: args.level, id: args.id, @@ -555,137 +526,120 @@ class StackedRouter extends _i1.RouterBase { settings: data, ); }, - _i24.CoursePracticeView: (data) { - final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( - builder: (context) => - _i24.CoursePracticeView(key: args.key, id: args.id), - settings: data, - ); - }, - _i25.CoursePaymentView: (data) { + _i23.CoursePaymentView: (data) { final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => - _i25.CoursePaymentView(key: args.key, course: args.course), + _i23.CoursePaymentView(key: args.key, course: args.course), settings: data, ); }, - _i26.FailureView: (data) { + _i24.FailureView: (data) { final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( - builder: (context) => _i26.FailureView( + return _i37.MaterialPageRoute( + builder: (context) => _i24.FailureView( key: args.key, onTap: args.onTap, label: args.label), settings: data, ); }, - _i27.CourseLessonView: (data) { - final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( - builder: (context) => - _i27.CourseLessonView(key: args.key, course: args.course), - settings: data, - ); - }, - _i28.CourseLessonDetailView: (data) { + _i25.CourseLessonDetailView: (data) { final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => - _i28.CourseLessonDetailView(key: args.key, lesson: args.lesson), + _i25.CourseLessonDetailView(key: args.key, lesson: args.lesson), settings: data, ); }, - _i29.DuolingoView: (data) { + _i26.DuolingoView: (data) { final args = data.getArgs( orElse: () => const DuolingoViewArguments(), ); - return _i40.MaterialPageRoute( - builder: (context) => _i29.DuolingoView(key: args.key), + return _i37.MaterialPageRoute( + builder: (context) => _i26.DuolingoView(key: args.key), settings: data, ); }, - _i30.CourseView: (data) { + _i27.CourseView: (data) { final args = data.getArgs( orElse: () => const CourseViewArguments(), ); - return _i40.MaterialPageRoute( - builder: (context) => _i30.CourseView(key: args.key), + return _i37.MaterialPageRoute( + builder: (context) => _i27.CourseView(key: args.key), settings: data, ); }, - _i31.CoursePracticeQuestionView: (data) { - final args = - data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( - builder: (context) => - _i31.CoursePracticeQuestionView(key: args.key, id: args.id), - settings: data, - ); - }, - _i32.LearnProgramView: (data) { + _i28.LearnProgramView: (data) { final args = data.getArgs( orElse: () => const LearnProgramViewArguments(), ); - return _i40.MaterialPageRoute( - builder: (context) => _i32.LearnProgramView(key: args.key), + return _i37.MaterialPageRoute( + builder: (context) => _i28.LearnProgramView(key: args.key), settings: data, ); }, - _i33.LearnCourseView: (data) { + _i29.LearnCourseView: (data) { final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( - builder: (context) => _i33.LearnCourseView(key: args.key, id: args.id), + return _i37.MaterialPageRoute( + builder: (context) => _i29.LearnCourseView(key: args.key, id: args.id), settings: data, ); }, - _i34.AssessmentView: (data) { + _i30.AssessmentView: (data) { final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => - _i34.AssessmentView(key: args.key, data: args.data), + _i30.AssessmentView(key: args.key, data: args.data), settings: data, ); }, - _i35.LearnSubscriptionView: (data) { + _i31.LearnSubscriptionView: (data) { final args = data.getArgs( orElse: () => const LearnSubscriptionViewArguments(), ); - return _i40.MaterialPageRoute( - builder: (context) => _i35.LearnSubscriptionView(key: args.key), + return _i37.MaterialPageRoute( + builder: (context) => _i31.LearnSubscriptionView(key: args.key), settings: data, ); }, - _i36.ArifPayView: (data) { + _i32.ArifPayView: (data) { final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => - _i36.ArifPayView(key: args.key, phone: args.phone), + _i32.ArifPayView(key: args.key, phone: args.phone), settings: data, ); }, - _i37.CourseCatalogView: (data) { + _i33.CourseCatalogView: (data) { final args = data.getArgs( orElse: () => const CourseCatalogViewArguments(), ); - return _i40.MaterialPageRoute( - builder: (context) => _i37.CourseCatalogView(key: args.key), + return _i37.MaterialPageRoute( + builder: (context) => _i33.CourseCatalogView(key: args.key), settings: data, ); }, - _i38.CourseUnitView: (data) { + _i34.CourseUnitView: (data) { final args = data.getArgs(nullOk: false); - return _i40.MaterialPageRoute( + return _i37.MaterialPageRoute( builder: (context) => - _i38.CourseUnitView(key: args.key, catalog: args.catalog), + _i34.CourseUnitView(key: args.key, catalog: args.catalog), settings: data, ); }, - _i39.LandingView: (data) { + _i35.LandingView: (data) { final args = data.getArgs( orElse: () => const LandingViewArguments(), ); - return _i40.MaterialPageRoute( - builder: (context) => _i39.LandingView(key: args.key), + return _i37.MaterialPageRoute( + builder: (context) => _i35.LandingView(key: args.key), + settings: data, + ); + }, + _i36.CourseModuleView: (data) { + final args = data.getArgs(nullOk: false); + return _i37.MaterialPageRoute( + builder: (context) => _i36.CourseModuleView( + key: args.key, module: args.module, catalog: args.catalog), settings: data, ); }, @@ -701,7 +655,7 @@ class StackedRouter extends _i1.RouterBase { class HomeViewArguments { const HomeViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -723,7 +677,7 @@ class HomeViewArguments { class OnboardingViewArguments { const OnboardingViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -748,7 +702,7 @@ class StartupViewArguments { this.label, }); - final _i40.Key? key; + final _i37.Key? key; final String? label; @@ -772,7 +726,7 @@ class StartupViewArguments { class ProfileViewArguments { const ProfileViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -794,7 +748,7 @@ class ProfileViewArguments { class ProfileDetailViewArguments { const ProfileDetailViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -816,7 +770,7 @@ class ProfileDetailViewArguments { class DownloadsViewArguments { const DownloadsViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -838,7 +792,7 @@ class DownloadsViewArguments { class ProgressViewArguments { const ProgressViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -860,7 +814,7 @@ class ProgressViewArguments { class AccountPrivacyViewArguments { const AccountPrivacyViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -882,7 +836,7 @@ class AccountPrivacyViewArguments { class SupportViewArguments { const SupportViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -904,7 +858,7 @@ class SupportViewArguments { class TelegramSupportViewArguments { const TelegramSupportViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -926,7 +880,7 @@ class TelegramSupportViewArguments { class CallSupportViewArguments { const CallSupportViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -948,7 +902,7 @@ class CallSupportViewArguments { class LanguageViewArguments { const LanguageViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -970,7 +924,7 @@ class LanguageViewArguments { class PrivacyPolicyViewArguments { const PrivacyPolicyViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -992,7 +946,7 @@ class PrivacyPolicyViewArguments { class TermsAndConditionsViewArguments { const TermsAndConditionsViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -1014,7 +968,7 @@ class TermsAndConditionsViewArguments { class RegisterViewArguments { const RegisterViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -1036,7 +990,7 @@ class RegisterViewArguments { class LoginViewArguments { const LoginViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -1061,9 +1015,9 @@ class LearnModuleViewArguments { required this.course, }); - final _i40.Key? key; + final _i37.Key? key; - final _i41.LearnCourse course; + final _i38.LearnCourse course; @override String toString() { @@ -1082,37 +1036,15 @@ class LearnModuleViewArguments { } } -class WelcomeViewArguments { - const WelcomeViewArguments({this.key}); - - final _i40.Key? key; - - @override - String toString() { - return '{"key": "$key"}'; - } - - @override - bool operator ==(covariant WelcomeViewArguments other) { - if (identical(this, other)) return true; - return other.key == key; - } - - @override - int get hashCode { - return key.hashCode; - } -} - class LearnLessonViewArguments { const LearnLessonViewArguments({ this.key, required this.module, }); - final _i40.Key? key; + final _i37.Key? key; - final _i42.LearnModule module; + final _i39.LearnModule module; @override String toString() { @@ -1134,7 +1066,7 @@ class LearnLessonViewArguments { class ForgetPasswordViewArguments { const ForgetPasswordViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -1161,11 +1093,11 @@ class LearnLessonDetailViewArguments { required this.hasPractice, }); - final _i40.Key? key; + final _i37.Key? key; - final _i43.LearnLesson lesson; + final _i40.LearnLesson lesson; - final _i42.LearnModule module; + final _i39.LearnModule module; final bool hasPractice; @@ -1203,7 +1135,7 @@ class LearnPracticeViewArguments { required this.subtitle, }); - final _i40.Key? key; + final _i37.Key? key; final String? level; @@ -1213,7 +1145,7 @@ class LearnPracticeViewArguments { final String title; - final _i44.LearnPractices practice; + final _i41.LearnPractices practice; final String subtitle; @@ -1246,42 +1178,15 @@ class LearnPracticeViewArguments { } } -class CoursePracticeViewArguments { - const CoursePracticeViewArguments({ - this.key, - required this.id, - }); - - final _i40.Key? key; - - final int id; - - @override - String toString() { - return '{"key": "$key", "id": "$id"}'; - } - - @override - bool operator ==(covariant CoursePracticeViewArguments other) { - if (identical(this, other)) return true; - return other.key == key && other.id == id; - } - - @override - int get hashCode { - return key.hashCode ^ id.hashCode; - } -} - class CoursePaymentViewArguments { const CoursePaymentViewArguments({ this.key, required this.course, }); - final _i40.Key? key; + final _i37.Key? key; - final _i45.Course course; + final _i42.Course course; @override String toString() { @@ -1307,7 +1212,7 @@ class FailureViewArguments { required this.label, }); - final _i40.Key? key; + final _i37.Key? key; final void Function() onTap; @@ -1330,42 +1235,15 @@ class FailureViewArguments { } } -class CourseLessonViewArguments { - const CourseLessonViewArguments({ - this.key, - required this.course, - }); - - final _i40.Key? key; - - final _i45.Course course; - - @override - String toString() { - return '{"key": "$key", "course": "$course"}'; - } - - @override - bool operator ==(covariant CourseLessonViewArguments other) { - if (identical(this, other)) return true; - return other.key == key && other.course == course; - } - - @override - int get hashCode { - return key.hashCode ^ course.hashCode; - } -} - class CourseLessonDetailViewArguments { const CourseLessonDetailViewArguments({ this.key, required this.lesson, }); - final _i40.Key? key; + final _i37.Key? key; - final _i46.CourseLesson lesson; + final _i43.CourseLesson lesson; @override String toString() { @@ -1387,7 +1265,7 @@ class CourseLessonDetailViewArguments { class DuolingoViewArguments { const DuolingoViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -1409,7 +1287,7 @@ class DuolingoViewArguments { class CourseViewArguments { const CourseViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -1428,37 +1306,10 @@ class CourseViewArguments { } } -class CoursePracticeQuestionViewArguments { - const CoursePracticeQuestionViewArguments({ - this.key, - required this.id, - }); - - final _i40.Key? key; - - final int id; - - @override - String toString() { - return '{"key": "$key", "id": "$id"}'; - } - - @override - bool operator ==(covariant CoursePracticeQuestionViewArguments other) { - if (identical(this, other)) return true; - return other.key == key && other.id == id; - } - - @override - int get hashCode { - return key.hashCode ^ id.hashCode; - } -} - class LearnProgramViewArguments { const LearnProgramViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -1483,7 +1334,7 @@ class LearnCourseViewArguments { required this.id, }); - final _i40.Key? key; + final _i37.Key? key; final int id; @@ -1510,7 +1361,7 @@ class AssessmentViewArguments { required this.data, }); - final _i40.Key? key; + final _i37.Key? key; final Map data; @@ -1534,7 +1385,7 @@ class AssessmentViewArguments { class LearnSubscriptionViewArguments { const LearnSubscriptionViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -1559,7 +1410,7 @@ class ArifPayViewArguments { required this.phone, }); - final _i40.Key? key; + final _i37.Key? key; final String phone; @@ -1583,7 +1434,7 @@ class ArifPayViewArguments { class CourseCatalogViewArguments { const CourseCatalogViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -1608,9 +1459,9 @@ class CourseUnitViewArguments { required this.catalog, }); - final _i40.Key? key; + final _i37.Key? key; - final _i47.CourseCatalog catalog; + final _i44.CourseCatalog catalog; @override String toString() { @@ -1632,7 +1483,7 @@ class CourseUnitViewArguments { class LandingViewArguments { const LandingViewArguments({this.key}); - final _i40.Key? key; + final _i37.Key? key; @override String toString() { @@ -1651,9 +1502,41 @@ class LandingViewArguments { } } -extension NavigatorStateExtension on _i48.NavigationService { +class CourseModuleViewArguments { + const CourseModuleViewArguments({ + this.key, + required this.module, + required this.catalog, + }); + + final _i37.Key? key; + + final _i45.CourseModule? module; + + final _i44.CourseCatalog catalog; + + @override + String toString() { + return '{"key": "$key", "module": "$module", "catalog": "$catalog"}'; + } + + @override + bool operator ==(covariant CourseModuleViewArguments other) { + if (identical(this, other)) return true; + return other.key == key && + other.module == module && + other.catalog == catalog; + } + + @override + int get hashCode { + return key.hashCode ^ module.hashCode ^ catalog.hashCode; + } +} + +extension NavigatorStateExtension on _i46.NavigationService { Future navigateToHomeView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1669,7 +1552,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToOnboardingView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1685,7 +1568,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToStartupView({ - _i40.Key? key, + _i37.Key? key, String? label, int? routerId, bool preventDuplicates = true, @@ -1702,7 +1585,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToProfileView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1718,7 +1601,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToProfileDetailView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1734,7 +1617,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToDownloadsView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1750,7 +1633,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToProgressView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1766,7 +1649,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToAccountPrivacyView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1782,7 +1665,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToSupportView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1798,7 +1681,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToTelegramSupportView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1814,7 +1697,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToCallSupportView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1830,7 +1713,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToLanguageView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1846,7 +1729,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToPrivacyPolicyView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1862,7 +1745,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToTermsAndConditionsView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1878,7 +1761,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToRegisterView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1894,7 +1777,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToLoginView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1910,8 +1793,8 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToLearnModuleView({ - _i40.Key? key, - required _i41.LearnCourse course, + _i37.Key? key, + required _i38.LearnCourse course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1926,25 +1809,9 @@ extension NavigatorStateExtension on _i48.NavigationService { transition: transition); } - Future navigateToWelcomeView({ - _i40.Key? key, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo(Routes.welcomeView, - arguments: WelcomeViewArguments(key: key), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future navigateToLearnLessonView({ - _i40.Key? key, - required _i42.LearnModule module, + _i37.Key? key, + required _i39.LearnModule module, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1960,7 +1827,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToForgetPasswordView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1976,9 +1843,9 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToLearnLessonDetailView({ - _i40.Key? key, - required _i43.LearnLesson lesson, - required _i42.LearnModule module, + _i37.Key? key, + required _i40.LearnLesson lesson, + required _i39.LearnModule module, required bool hasPractice, int? routerId, bool preventDuplicates = true, @@ -1996,12 +1863,12 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToLearnPracticeView({ - _i40.Key? key, + _i37.Key? key, String? level, required int id, required String label, required String title, - required _i44.LearnPractices practice, + required _i41.LearnPractices practice, required String subtitle, int? routerId, bool preventDuplicates = true, @@ -2024,26 +1891,9 @@ extension NavigatorStateExtension on _i48.NavigationService { transition: transition); } - Future navigateToCoursePracticeView({ - _i40.Key? key, - required int id, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo(Routes.coursePracticeView, - arguments: CoursePracticeViewArguments(key: key, id: id), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future navigateToCoursePaymentView({ - _i40.Key? key, - required _i45.Course course, + _i37.Key? key, + required _i42.Course course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2059,7 +1909,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToFailureView({ - _i40.Key? key, + _i37.Key? key, required void Function() onTap, required String label, int? routerId, @@ -2076,26 +1926,9 @@ extension NavigatorStateExtension on _i48.NavigationService { transition: transition); } - Future navigateToCourseLessonView({ - _i40.Key? key, - required _i45.Course course, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo(Routes.courseLessonView, - arguments: CourseLessonViewArguments(key: key, course: course), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future navigateToCourseLessonDetailView({ - _i40.Key? key, - required _i46.CourseLesson lesson, + _i37.Key? key, + required _i43.CourseLesson lesson, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2111,7 +1944,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToDuolingoView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2127,7 +1960,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToCourseView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2142,25 +1975,8 @@ extension NavigatorStateExtension on _i48.NavigationService { transition: transition); } - Future navigateToCoursePracticeQuestionView({ - _i40.Key? key, - required int id, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo(Routes.coursePracticeQuestionView, - arguments: CoursePracticeQuestionViewArguments(key: key, id: id), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future navigateToLearnProgramView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2176,7 +1992,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToLearnCourseView({ - _i40.Key? key, + _i37.Key? key, required int id, int? routerId, bool preventDuplicates = true, @@ -2193,7 +2009,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToAssessmentView({ - _i40.Key? key, + _i37.Key? key, required Map data, int? routerId, bool preventDuplicates = true, @@ -2210,7 +2026,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToLearnSubscriptionView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2226,7 +2042,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToArifPayView({ - _i40.Key? key, + _i37.Key? key, required String phone, int? routerId, bool preventDuplicates = true, @@ -2243,7 +2059,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToCourseCatalogView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2259,8 +2075,8 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToCourseUnitView({ - _i40.Key? key, - required _i47.CourseCatalog catalog, + _i37.Key? key, + required _i44.CourseCatalog catalog, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2276,7 +2092,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future navigateToLandingView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2291,8 +2107,29 @@ extension NavigatorStateExtension on _i48.NavigationService { transition: transition); } + Future navigateToCourseModuleView({ + _i37.Key? key, + required _i45.CourseModule? module, + required _i44.CourseCatalog catalog, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo(Routes.courseModuleView, + arguments: CourseModuleViewArguments( + key: key, module: module, catalog: catalog), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + + Future replaceWithHomeView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2308,7 +2145,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithOnboardingView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2324,7 +2161,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithStartupView({ - _i40.Key? key, + _i37.Key? key, String? label, int? routerId, bool preventDuplicates = true, @@ -2341,7 +2178,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithProfileView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2357,7 +2194,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithProfileDetailView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2373,7 +2210,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithDownloadsView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2389,7 +2226,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithProgressView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2405,7 +2242,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithAccountPrivacyView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2421,7 +2258,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithSupportView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2437,7 +2274,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithTelegramSupportView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2453,7 +2290,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithCallSupportView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2469,7 +2306,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithLanguageView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2485,7 +2322,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithPrivacyPolicyView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2501,7 +2338,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithTermsAndConditionsView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2517,7 +2354,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithRegisterView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2533,7 +2370,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithLoginView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2549,8 +2386,8 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithLearnModuleView({ - _i40.Key? key, - required _i41.LearnCourse course, + _i37.Key? key, + required _i38.LearnCourse course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2565,25 +2402,9 @@ extension NavigatorStateExtension on _i48.NavigationService { transition: transition); } - Future replaceWithWelcomeView({ - _i40.Key? key, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return replaceWith(Routes.welcomeView, - arguments: WelcomeViewArguments(key: key), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future replaceWithLearnLessonView({ - _i40.Key? key, - required _i42.LearnModule module, + _i37.Key? key, + required _i39.LearnModule module, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2599,7 +2420,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithForgetPasswordView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2615,9 +2436,9 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithLearnLessonDetailView({ - _i40.Key? key, - required _i43.LearnLesson lesson, - required _i42.LearnModule module, + _i37.Key? key, + required _i40.LearnLesson lesson, + required _i39.LearnModule module, required bool hasPractice, int? routerId, bool preventDuplicates = true, @@ -2635,12 +2456,12 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithLearnPracticeView({ - _i40.Key? key, + _i37.Key? key, String? level, required int id, required String label, required String title, - required _i44.LearnPractices practice, + required _i41.LearnPractices practice, required String subtitle, int? routerId, bool preventDuplicates = true, @@ -2663,26 +2484,9 @@ extension NavigatorStateExtension on _i48.NavigationService { transition: transition); } - Future replaceWithCoursePracticeView({ - _i40.Key? key, - required int id, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return replaceWith(Routes.coursePracticeView, - arguments: CoursePracticeViewArguments(key: key, id: id), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future replaceWithCoursePaymentView({ - _i40.Key? key, - required _i45.Course course, + _i37.Key? key, + required _i42.Course course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2698,7 +2502,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithFailureView({ - _i40.Key? key, + _i37.Key? key, required void Function() onTap, required String label, int? routerId, @@ -2715,26 +2519,9 @@ extension NavigatorStateExtension on _i48.NavigationService { transition: transition); } - Future replaceWithCourseLessonView({ - _i40.Key? key, - required _i45.Course course, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return replaceWith(Routes.courseLessonView, - arguments: CourseLessonViewArguments(key: key, course: course), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future replaceWithCourseLessonDetailView({ - _i40.Key? key, - required _i46.CourseLesson lesson, + _i37.Key? key, + required _i43.CourseLesson lesson, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2750,7 +2537,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithDuolingoView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2766,7 +2553,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithCourseView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2781,25 +2568,8 @@ extension NavigatorStateExtension on _i48.NavigationService { transition: transition); } - Future replaceWithCoursePracticeQuestionView({ - _i40.Key? key, - required int id, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return replaceWith(Routes.coursePracticeQuestionView, - arguments: CoursePracticeQuestionViewArguments(key: key, id: id), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future replaceWithLearnProgramView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2815,7 +2585,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithLearnCourseView({ - _i40.Key? key, + _i37.Key? key, required int id, int? routerId, bool preventDuplicates = true, @@ -2832,7 +2602,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithAssessmentView({ - _i40.Key? key, + _i37.Key? key, required Map data, int? routerId, bool preventDuplicates = true, @@ -2849,7 +2619,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithLearnSubscriptionView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2865,7 +2635,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithArifPayView({ - _i40.Key? key, + _i37.Key? key, required String phone, int? routerId, bool preventDuplicates = true, @@ -2882,7 +2652,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithCourseCatalogView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2898,8 +2668,8 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithCourseUnitView({ - _i40.Key? key, - required _i47.CourseCatalog catalog, + _i37.Key? key, + required _i44.CourseCatalog catalog, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2915,7 +2685,7 @@ extension NavigatorStateExtension on _i48.NavigationService { } Future replaceWithLandingView({ - _i40.Key? key, + _i37.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2929,4 +2699,25 @@ extension NavigatorStateExtension on _i48.NavigationService { parameters: parameters, transition: transition); } + + Future replaceWithCourseModuleView({ + _i37.Key? key, + required _i45.CourseModule? module, + required _i44.CourseCatalog catalog, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return replaceWith(Routes.courseModuleView, + arguments: CourseModuleViewArguments( + key: key, module: module, catalog: catalog), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + } diff --git a/lib/models/course_lesson.dart b/lib/models/course_lesson.dart index 6c9116b..195d33e 100644 --- a/lib/models/course_lesson.dart +++ b/lib/models/course_lesson.dart @@ -4,51 +4,35 @@ part 'course_lesson.g.dart'; @JsonSerializable() class CourseLesson { - int? id; + final int? id; - String? title; + final String? title; - int? duration; + final String? thumbnail; - String? status; - - String? thumbnail; - - String? resolution; - - String? visibility; - - String? description; + final String? description; @JsonKey(name: 'video_url') - String? videoUrl; + final String? videoUrl; - @JsonKey(name: 'vimeo_status') - String? vimeoStatus; + @JsonKey(name: 'sort_order') + final int? sortOrder; - @JsonKey(name: 'instructor_id') - int? instructorId; + @JsonKey(name: 'has_practice') + final bool? hasPractice; - @JsonKey(name: 'sub_course_id') - int? courseId; + @JsonKey(name: 'unit_module_id') + final int? unitModuleId; - @JsonKey(name: 'display_order') - int? displayOrder; - - CourseLesson( + const CourseLesson( {this.id, this.title, - this.status, - this.courseId, this.videoUrl, - this.duration, + this.sortOrder, this.thumbnail, - this.visibility, - this.resolution, - this.vimeoStatus, this.description, - this.displayOrder, - this.instructorId}); + this.hasPractice, + this.unitModuleId}); factory CourseLesson.fromJson(Map json) => _$CourseLessonFromJson(json); diff --git a/lib/models/course_lesson.g.dart b/lib/models/course_lesson.g.dart index c4055be..ccf2f04 100644 --- a/lib/models/course_lesson.g.dart +++ b/lib/models/course_lesson.g.dart @@ -9,32 +9,22 @@ part of 'course_lesson.dart'; CourseLesson _$CourseLessonFromJson(Map json) => CourseLesson( id: (json['id'] as num?)?.toInt(), title: json['title'] as String?, - status: json['status'] as String?, - courseId: (json['sub_course_id'] as num?)?.toInt(), videoUrl: json['video_url'] as String?, - duration: (json['duration'] as num?)?.toInt(), + sortOrder: (json['sort_order'] as num?)?.toInt(), thumbnail: json['thumbnail'] as String?, - visibility: json['visibility'] as String?, - resolution: json['resolution'] as String?, - vimeoStatus: json['vimeo_status'] as String?, description: json['description'] as String?, - displayOrder: (json['display_order'] as num?)?.toInt(), - instructorId: (json['instructor_id'] as num?)?.toInt(), + hasPractice: json['has_practice'] as bool?, + unitModuleId: (json['unit_module_id'] as num?)?.toInt(), ); Map _$CourseLessonToJson(CourseLesson instance) => { 'id': instance.id, 'title': instance.title, - 'duration': instance.duration, - 'status': instance.status, 'thumbnail': instance.thumbnail, - 'resolution': instance.resolution, - 'visibility': instance.visibility, 'description': instance.description, 'video_url': instance.videoUrl, - 'vimeo_status': instance.vimeoStatus, - 'instructor_id': instance.instructorId, - 'sub_course_id': instance.courseId, - 'display_order': instance.displayOrder, + 'sort_order': instance.sortOrder, + 'has_practice': instance.hasPractice, + 'unit_module_id': instance.unitModuleId, }; diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index ef9d48c..5b5781d 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -2,13 +2,9 @@ import 'package:dio/dio.dart'; import 'package:yimaru_app/models/learn_lesson.dart'; import 'package:yimaru_app/models/learn_practice.dart'; import 'package:yimaru_app/models/learn_program.dart'; -import 'package:yimaru_app/models/level.dart'; import 'package:yimaru_app/models/assessment_question.dart'; import 'package:yimaru_app/models/course_catalog.dart'; import 'package:yimaru_app/models/course_lesson.dart'; -import 'package:yimaru_app/models/course_progress.dart'; -import 'package:yimaru_app/models/course.dart'; -import 'package:yimaru_app/models/practice.dart'; import 'package:yimaru_app/models/user.dart'; import 'package:yimaru_app/services/dio_service.dart'; import 'package:yimaru_app/ui/common/app_constants.dart'; @@ -20,10 +16,7 @@ import '../models/learn_course.dart'; import '../models/learn_module.dart'; import '../models/learn_question.dart'; import '../models/learn_subscription.dart'; -import '../models/lesson.dart'; -import '../models/module.dart'; import '../models/assessment.dart'; -import '../models/submodule.dart'; import '../models/learn_subscription_request.dart'; import '../ui/common/enmus.dart'; @@ -779,303 +772,20 @@ class ApiService { } } - /* TO BE MODIFIED*/ - - // Get courses - // Future> getCourses(int id) async { - // try { - // List courses = []; - // - // final Response response = await _service.dio - // .get('$kBaseUrl/$kCourseBaseUrl/$kCoursesUrl/$id/$kSubcoursesUrl'); - // - // if (response.statusCode == 200) { - // var data = response.data; - // var decodedData = data['data']['sub_courses'] as List; - // courses = decodedData.map( - // (e) { - // return Course.fromJson(e); - // }, - // ).toList(); - // return courses; - // } - // return []; - // } catch (e) { - // return []; - // } - // } - - // Get course progress - Future> getCourseProgress(int id) async { - try { - List courseProgress = []; - - final Response response = - await _service.dio.get('$kBaseUrl/$kCourseProgressUrl/$id'); - - if (response.statusCode == 200) { - var data = response.data; - var decodedData = data['data'] as List; - courseProgress = decodedData.map( - (e) { - return CourseProgress.fromJson(e); - }, - ).toList(); - return courseProgress; - } - return []; - } catch (e) { - return []; - } - } - // Get course lessons Future> getCourseLessons(int id) async { try { - List courseLessons = []; + List lessons = []; final Response response = await _service.dio.get( - '$kBaseUrl/$kCourseBaseUrl/$kSubcoursesUrl/$id/$kPublishedVideos'); + '$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kExamPrepUrl/$kModulesUrl/$id/$kLessonsUrl'); if (response.statusCode == 200) { var data = response.data; - var decodedData = data['data'] as List; - courseLessons = decodedData.map( - (e) { - return CourseLesson.fromJson(e); - }, - ).toList(); - return courseLessons; - } - return []; - } catch (e) { - return []; - } - } - - // Complete lesson - Future> completeLesson(int id) async { - try { - Response response = await _service.dio.post( - '$kBaseUrl/$kLessonProgressUrl/$id/$kCompleteUrl', - ); - - if (response.statusCode == 200) { - return {'status': ResponseStatus.success, 'message': 'Video completed'}; - } else { - return { - 'status': ResponseStatus.failure, - 'message': 'Unknown Error Occurred' - }; - } - } on DioException catch (e) { - return { - 'status': ResponseStatus.failure, - 'message': e.response?.data.toString(), - }; - } - } - - // Course practices - Future> getCoursePractices(int id) async { - try { - List coursePractices = []; - - final Response response = await _service.dio.get( - '$kBaseUrl/$kPracticeBaseUrl/$kCoursePractice?owner_type=SUB_COURSE&owner_id=$id'); - - if (response.statusCode == 200) { - var data = response.data; - var decodedData = data['data'] as List; - coursePractices = decodedData.map( - (e) { - return Practice.fromJson(e); - }, - ).toList(); - return coursePractices; - } - return []; - } catch (e) { - return []; - } - } - - // Get course practic questions - Future> getCoursePracticeQuestions(int id) async { - try { - List coursePracticeQuestions = []; - - final Response response = await _service.dio - .get('$kBaseUrl/$kPracticeBaseUrl/$id/$kCoursePracticeQuestions'); - - if (response.statusCode == 200) { - var data = response.data; - var decodedData = data['data'] as List; - coursePracticeQuestions = decodedData.map( - (e) { - return AssessmentQuestion.fromJson(e); - }, - ).toList(); - return coursePracticeQuestions; - } - return []; - } catch (e) { - return []; - } - } - - // Get course practice question - Future getCoursePracticeQuestion(int id) async { - try { - final Response response = - await _service.dio.get('$kBaseUrl/$kCoursePracticeQuestion/$id'); - - if (response.statusCode == 200) { - AssessmentQuestion question = - AssessmentQuestion.fromJson(response.data['data']); - - return question; - } - return null; - } catch (e) { - return null; - } - } - - // Get learn subcategories - Future> getLearnSubcategories() async { - try { - List learnSubcategories = []; - - final Response response = await _service.dio.get( - '$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kLearnSubcategoriesUrl'); - - if (response.statusCode == 200) { - var data = response.data; - var decodedData = data['data']['sub_categories'] as List; - learnSubcategories = decodedData.map( - (e) { - return CourseCatalog.fromJson(e); - }, - ).toList(); - return learnSubcategories; - } - return []; - } catch (e) { - return []; - } - } - - // Get courses - Future> getCourses(int id) async { - try { - List courses = []; - - final Response response = await _service.dio.get( - '$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kSubcategoriesUrl/$id/$kCoursesUrl'); - - if (response.statusCode == 200) { - var data = response.data; - var decodedData = data['data']['courses'] as List; - courses = decodedData.map( - (e) { - return Course.fromJson(e); - }, - ).toList(); - return courses; - } - return []; - } catch (e) { - return []; - } - } - - // Get levels - Future> getLevels(int id) async { - try { - List levels = []; - - final Response response = await _service.dio.get( - '$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kCoursesUrl/$id/$kLevelsUrl'); - - if (response.statusCode == 200) { - var data = response.data; - var decodedData = data['data']['levels'] as List; - levels = decodedData.map( - (e) { - return Level.fromJson(e); - }, - ).toList(); - return levels; - } - return []; - } catch (e) { - return []; - } - } - - // Get modules - Future> getModules(int id) async { - try { - List modules = []; - - final Response response = await _service.dio.get( - '$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kLevelsUrl/$id/$kModulesUrl'); - - if (response.statusCode == 200) { - var data = response.data; - var decodedData = data['data']['modules'] as List; - modules = decodedData.map( - (e) { - return Module.fromJson(e); - }, - ).toList(); - return modules; - } - return []; - } catch (e) { - return []; - } - } - - // Get submodules - Future> getSubmodules(int id) async { - try { - List submodules = []; - - final Response response = await _service.dio.get( - '$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kModulesUrl/$id/$kSubmodulesUrl'); - - if (response.statusCode == 200) { - var data = response.data; - var decodedData = data['data']['sub_modules'] as List; - submodules = decodedData.map( - (e) { - return Submodule.fromJson(e); - }, - ).toList(); - return submodules; - } - return []; - } catch (e) { - return []; - } - } - - // Get lessons - Future> getLessons(int id) async { - try { - List lessons = []; - - final Response response = await _service.dio.get( - '$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kSubmodulesUrl/$id/$kLessonsUrl'); - - if (response.statusCode == 200) { - var data = response.data; - var decodedData = data['data'] as List; + var decodedData = data['data']['lessons'] as List; lessons = decodedData.map( (e) { - return Lesson.fromJson(e); + return CourseLesson.fromJson(e); }, ).toList(); return lessons; @@ -1085,52 +795,4 @@ class ApiService { return []; } } - - // Practices - Future> getPractices(int id) async { - try { - List coursePractices = []; - - final Response response = await _service.dio.get( - '$kBaseUrl/$kPracticeBaseUrl/$kCoursePractice?owner_type=SUB_MODULE&owner_id=$id'); - - if (response.statusCode == 200) { - var data = response.data; - var decodedData = data['data'] as List; - coursePractices = decodedData.map( - (e) { - return Practice.fromJson(e); - }, - ).toList(); - return coursePractices; - } - return []; - } catch (e) { - return []; - } - } - - // Questions - Future> getQuestions(int id) async { - try { - List questions = []; - - final Response response = await _service.dio.get( - '$kBaseUrl/api/$kApiVersionUrl/$kQuestionSetsUrl/$id/$kQuestionsUrl'); - - if (response.statusCode == 200) { - var data = response.data; - var decodedData = data['data'] as List; - questions = decodedData.map( - (e) { - return AssessmentQuestion.fromJson(e); - }, - ).toList(); - return questions; - } - return []; - } catch (e) { - return []; - } - } } diff --git a/lib/services/authentication_service.dart b/lib/services/authentication_service.dart index 82a0bf6..89295be 100644 --- a/lib/services/authentication_service.dart +++ b/lib/services/authentication_service.dart @@ -3,10 +3,14 @@ import 'package:yimaru_app/app/app.locator.dart'; import 'package:yimaru_app/models/user.dart'; import 'package:yimaru_app/services/secure_storage_service.dart'; +import 'localization_service.dart'; + class AuthenticationService with ListenableServiceMixin { // Dependency injection final _secureService = locator(); + final _localizationService = locator(); + // User data User? _user; @@ -14,7 +18,7 @@ class AuthenticationService with ListenableServiceMixin { // Initialization AuthenticationService() { - listenToReactiveValues([_user]); + listenToReactiveValues([_user, _localizationService]); } // Check user logged in @@ -172,9 +176,12 @@ class AuthenticationService with ListenableServiceMixin { // Logout Future logout() async { bool firstTimeInstall = await isFirstTimeInstall(); + String language = await _localizationService.selectedLanguage['code']; + _user = null; await _secureService.clear(); await setFirstTimeInstall(firstTimeInstall); + await _secureService.setString('language', language); notifyListeners(); } } diff --git a/lib/services/course_service.dart b/lib/services/course_service.dart index 173832d..b4758d8 100644 --- a/lib/services/course_service.dart +++ b/lib/services/course_service.dart @@ -1,10 +1,9 @@ import 'package:stacked/stacked.dart'; import 'package:yimaru_app/app/app.locator.dart'; -import 'package:yimaru_app/models/course_progress.dart'; import 'package:yimaru_app/services/api_service.dart'; import '../models/course_catalog.dart'; -import '../models/course_detail.dart'; +import '../models/course_lesson.dart'; import '../models/course_module.dart'; import '../models/course_unit.dart'; @@ -28,10 +27,15 @@ class CourseService with ListenableServiceMixin { List get units => _units; // Course modules - List _modules = []; + final List _modules = []; List get modules => _modules; + // Course lessons + List _lessons = []; + + List get lessons => _lessons; + // Course catalogs Future getCourseCatalogs() async { _catalogs = await _apiService.getCourseCatalogs(); @@ -47,7 +51,7 @@ class CourseService with ListenableServiceMixin { } // Course modules - Future getCourseUnitModule({ + Future getCourseModules({ required int id, required int index, }) async { @@ -66,26 +70,10 @@ class CourseService with ListenableServiceMixin { notifyListeners(); } - Future getCourseModules(int id) async { - _modules = await _apiService.getCourseModules(id); - _modules.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); + // Course units + Future getCourseLessons(int id) async { + _lessons = await _apiService.getCourseLessons(id); + _lessons.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); notifyListeners(); } - - // Get course detail - Future> getCoursesDetail(int id) async { - final courses = await _apiService.getCourses(id); - final progress = await _apiService.getCourseProgress(id); - - final progressMap = { - for (var p in progress.whereType()) p.courseId: p - }; - - return courses.map((course) { - return CourseDetail( - course: course, - courseProgress: progressMap[course.id], - ); - }).toList(); - } } diff --git a/lib/services/localization_service.dart b/lib/services/localization_service.dart index a7860bc..23e6978 100644 --- a/lib/services/localization_service.dart +++ b/lib/services/localization_service.dart @@ -1,8 +1,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/services/secure_storage_service.dart'; + +import '../app/app.locator.dart'; class LocalizationService with ListenableServiceMixin { + // Dependency injection + final _secureService = locator(); + // Initialization localizationService() { listenToReactiveValues([_selectedLanguage]); @@ -10,7 +16,7 @@ class LocalizationService with ListenableServiceMixin { // Languages Map _selectedLanguage = { - 'code': 'EN', + 'code': 'en', 'language': 'English' }; @@ -18,7 +24,7 @@ class LocalizationService with ListenableServiceMixin { final List> _languages = [ {'code': 'አማ', 'language': 'አማርኛ'}, - {'code': 'EN', 'language': 'English'}, + {'code': 'en', 'language': 'English'}, ]; List> get languages => _languages; @@ -34,14 +40,33 @@ class LocalizationService with ListenableServiceMixin { if (title['code'] == 'አማ') { await setAmharicLanguage(context); } else { - await setAmharicLanguage(context); + await setEnglishLanguage(context); } notifyListeners(); } - Future setAmharicLanguage(BuildContext context) async => - await context.setLocale(const Locale('am')); + Future loadSelectedLanguage() async { + String language = await _secureService.getString('language') ?? 'en'; - Future setEnglishLanguage(BuildContext context) async => - await context.setLocale(const Locale('en')); + if (language == 'en') { + _selectedLanguage = {'code': 'en', 'language': 'English'}; + + } else { + _selectedLanguage = {'code': 'አማ', 'language': 'አማርኛ'}; + } +notifyListeners(); + print('SELECTED LANGUAGE: $language $_selectedLanguage'); + } + + Future setAmharicLanguage(BuildContext context) async { + await context.setLocale(const Locale('am')); + await _secureService.setString('language', 'am'); + notifyListeners(); + } + + Future setEnglishLanguage(BuildContext context) async { + await context.setLocale(const Locale('en')); + await _secureService.setString('language', 'en'); + notifyListeners(); + } } diff --git a/lib/ui/common/enmus.dart b/lib/ui/common/enmus.dart index 8b1ec9a..57ccf67 100644 --- a/lib/ui/common/enmus.dart +++ b/lib/ui/common/enmus.dart @@ -40,7 +40,6 @@ enum StateObjects { learnCourses, profileImage, learnPrograms, - courseModules, courseLessons, profileUpdate, resetPassword, diff --git a/lib/ui/common/translations/codegen_loader.g.dart b/lib/ui/common/translations/codegen_loader.g.dart index 0afd60c..bfefa66 100644 --- a/lib/ui/common/translations/codegen_loader.g.dart +++ b/lib/ui/common/translations/codegen_loader.g.dart @@ -6,7 +6,7 @@ import 'dart:ui'; import 'package:easy_localization/easy_localization.dart' show AssetLoader; -class CodegenLoader extends AssetLoader { +class CodegenLoader extends AssetLoader{ const CodegenLoader(); @override @@ -14,203 +14,251 @@ class CodegenLoader extends AssetLoader { return Future.value(mapLocales[locale.toString()]); } - static const Map _am = { - "loading": "በመጫን ላይ", - "welcome_back": "እንኳን በደህና ተመለሱ", - "checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ", - "dont_have_account": "መለያ የለዎትም? ይመዝገቡ", - "email": "ኢሜይል", - "password": "የይለፍ ቃል", - "forgot_password": "የይለፍ ቃል ረሱ?", - "cont": "ቀጥል", - "register": "ይመዝገቡ", - "login_with_google": "በጉግል ይግቡ", - "or": "ወይም", - "login_with_phone": "በስልክ ቁጥር ይግቡ", - "create_account": "አዲስ መለያ ይፍጠሩ", - "already_have_account": "መለያ አለዎት?", - "login": " ይግቡ ", - "register_with_google": "በጉግል ይመዝገቡ", - "register_with_phone": "በስልክ ቁጥር ይመዝገቡ", - "enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።", - "login_with_email": "በኢሜይል ይግቡ", - "create_password": "የይለፍ ቃል ይፍጠሩ", - "confirm_password": "የይለፍ ቃል ያረጋግጡ", - "eight_character_minimum": "ቢያንስ 8 ፊደላት", - "password_match": "የይለፍ ቃሉ ተመሳስሏል", - "sign_up_agreement": - "‘ይመዝገቡ’ የሚለውን ሲጫኑ በ‘አገልግሎት ውሎች’ እና ‘በግላዊነት ፖሊሲ’ ይስማማሉ።", - "terms_of_services": "የአገልግሎት ውሎች", - "and": "እና", - "privacy_policy": "የግላዊነት ፖሊሲ", - "register_with_email": "በኢሜል ይመዝገቡ", - "verification_code": "የማረጋገጫ ኮድ", - "resend_code": "ኮዱን እንደገና ላክ", - "code_sent_to_phone": "ኮዱ ወደ ስልክ ቁጥርዎ ተልኳል", - "code_sent_to_email": "ኮዱ ወደ ኢሜል ተልኳል", - "resend_code_in": "ኮዱን እንደገና ለመላክ የቀረው ጊዜ", - "reset_password": " የይለፍ ቃልን ይቀይሩ ", - "enter_email_reset_code": "ኢሜይልዎን ያስገቡ። የይለፍ ቃል መለወጫ ኮድ እንልክልዎታለን።", - "please_wait": "እባክዎ ይጠብቁ", - "reset_code_sent": "የመቀየሪያ ኮድ በተሳካ ሁኔታ ተልኳል", - "reset_code": " የመቀየሪያ ኮድ ", - "new_password": "አዲስ የይለፍ ቃል", - "logged_in_successfully": "በተሳካ ሁኔታ ገብተዋል", - "view_course": " ኮርሱን ይመልከቱ ", - "take_practice": " ልምምድ ያድርጉ ", - "your_current_level": "የአሁኑ ደረጃዎ", - "overall_progress": "አጠቃላይ እድገት", - "great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው ", - "view_module": "ሞጁሉን ይመልከቱ", - "progress": "እድገት", - "keep_going": " ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ", - "lessons_in_module": " በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ", - "practice": "ልምምድ", - "start": "ጀምር", - "in_progress": "በሂደት ላይ", - "hello": "ሰላም", - "ready_to_learn": " ዛሬ እንግሊዝኛ ለመማር ተዘጋጅተዋል? ", - "learn": "ይማሩ ", - "course": "ኮርስ", - "profile": " ፕሮፋይል ", - "speaking_partner": "የንግግር ጓደኛ ", - "practice_what_you_learned": "አሁን የተማሩትን እንለማመድ", - "practice_questions": "ጥቂት ጥያቄዎችን እጠይቃለሁ እና መልስ መስጠት ይችላሉ", - "start_practice": "ልምምድ ጀምር", - "almost_there": "ሊጨርሱ ተቃርበዋል", - "finish_session": "እድገትዎን ለማየት ክፍለ ጊዜውን ያጠናቅቁ", - "continue_practice": "ልምምዱን ይቀጥሉ", - "end_session": "ክፍለ ጊዜውን ያብቁ ", - "tap_start_to_listen": "ለማዳመጥ የጀምር ቁልፉን ይጫኑ", - "practice_speaking": "ንግግርን ይለማመዱ ", - "tap_microphone": "ለመናገር ማይክሮፎኑን ይጫኑ", - "reply": "እንደገና አዳምጥ", - "cancel": "ይቅር", - "you_are_speaking": "እየተናገሩ ነው", - "practice_completed": "ልምምዱ ተጠናቅቋል", - "great_improvement": "በዚህኛው በራስ መተማመንዎ ጨምሯል፤ ትልቅ መሻሻል ነው", - "practice_again": "እንደገና ይለማመዱ", - "conversation_review": "የንግግር ግምገማ ", - "result": "ውጤት", - "quick_tip": "ጠቃሚ ምክር", - "retry": "እንደገና ይሞክሩ", - "completed_a1": "እንኳን ደስ አለዎት! A1 ደረጃን አጠናቅቀዋል", - "analyzing_speaking": "የንግግር ችሎታዎን እየገመገምን ነው", - "view_profile": "ፕሮፋይሎን ይመልከቱ ", - "hi": "ሰላም", - "edit_profile": "መገለጫ ያስተካክሉ", - "first_name": "የመጀመሪያ ስም", - "last_name": "የአባት ስም", - "gender": "ፆታ", - "male": "ወንድ", - "female": "ሴት", - "phone_number": "የስልክ ቁጥር", - "country": "ሀገር", - "region": "ክልል", - "occupation": "የስራ መስክ ", - "save_changes": "ለውጦችን ያስቀምጡ" - }; - static const Map _en = { - "loading": "Loading", - "welcome_back": "Welcome back", - "checking_user_info": "Checking user info", - "dont_have_account": "Don't have an account? Register", - "email": "Email", - "password": "Password", - "forgot_password": "Forgot password?", - "cont": "Continue", - "register": "Register", - "login_with_google": "Login with Google", - "or": "Or", - "login_with_phone": "Login with phone number", - "create_account": "Create an account", - "already_have_account": "Already have an account?", - "login": "Login", - "register_with_google": "Register with Google", - "register_with_phone": "Register with phone number", - "enter_phone_number": - "Enter your phone number. We will send you a confirmation code there.", - "login_with_email": "Login with email", - "create_password": "Create password", - "confirm_password": "Confirm password", - "eight_character_minimum": "8 characters minimum", - "password_math": "password match", - "sign_up_agreement": - "By clicking ‘Sign Up’, you agree to our ‘Terms of Service’ and ‘Privacy Policy’", - "terms_of_services": "Terms of Service", - "and": "and", - "privacy_policy": "Privacy Policy", - "register_with_email": "Register with email", - "verification_code": "Verification Code", - "resend_code": "Resend Code", - "code_sent_to_phone": "Code sent to your number", - "code_sent_to_email": "Code sent to your email", - "resend_code_in": "Resend code in", - "reset_password": "Reset Password", - "enter_email_reset_code": - "Enter your email. We will send you a reset code.", - "please_wait": "Please wait", - "reset_code_sent": "Reset code sent successfully", - "reset_code": "Reset code", - "new_password": "New password", - "logged_in_successfully": "Logged in successfully", - "view_course": "View course", - "take_practice": "Take practice", - "your_current_level": "Your current level", - "overall_progress": "Overall progress", - "great_work": "Keep up the great work! You're doing amazing", - "view_module": "View module", - "progress": "Progress", - "keep_going": "Let's keep going - you're more than half there", - "lessons_in_module": "Lessons in this module", - "practice": "Practice", - "start": "Start", - "in_progress": "In Progress", - "hello": "Hello", - "ready_to_learn": "Ready to keep learning English today", - "learn": "Learn", - "course": "Course", - "profile": "Profile", - "speaking_partner": "Speaking partner", - "practice_what_you_learned": "Let's practice what you just learnt", - "practice_questions": "I will ask you a few questions and you can respond", - "start_practice": "Start practice", - "almost_there": "You're almost there", - "finish_session": "Finish the session to see your progress", - "continue_practice": "Continue practice", - "end_session": "End session", - "tap_start_to_listen": "Tap the start button to listen", - "practice_speaking": "Practice speaking", - "tap_microphone": "Tap the microphone to speak", - "reply": "Reply", - "cancel": "Cancel", - "you_are_speaking": "You're speaking", - "practice_completed": "Practice completed", - "great_improvement": - "You sound more confident this time, great improvement", - "practice_again": "Practice again", - "conversation_review": "Conversation review", - "result": "Result", - "quick_tip": "Quick tip", - "retry": "Retry", - "completed_a1": "Yay, you've completed A1", - "analyzing_speaking": "We're now analyzing your speaking skill", - "view_profile": "View profile", - "hi": "Hi", - "edit_profile": "Edit profile", - "first_name": "First name", - "last_name": "Last name", - "gender": "Gender", - "male": "Male", - "female": "Female", - "phone_number": "Phone number", - "country": "Country", - "region": "Region", - "occupation": "Occupation", - "save_changes": "Save changes" - }; - static const Map> mapLocales = { - "am": _am, - "en": _en - }; + static const Map _am = { + "loading": "በመጫን ላይ", + "welcome_back": "እንኳን በደህና ተመለሱ", + "checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ", + "dont_have_account": "መለያ የለዎትም? ይመዝገቡ", + "email": "ኢሜይል", + "password": "የይለፍ ቃል", + "forgot_password": "የይለፍ ቃል ረሱ?", + "cont": "ቀጥል", + "register": "ይመዝገቡ", + "login_with_google": "በጉግል ይግቡ", + "or": "ወይም", + "login_with_phone": "በስልክ ቁጥር ይግቡ", + "create_account": "አዲስ መለያ ይፍጠሩ", + "already_have_account": "መለያ አለዎት?", + "login": " ይግቡ ", + "register_with_google": "በጉግል ይመዝገቡ", + "register_with_phone": "በስልክ ቁጥር ይመዝገቡ", + "enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።", + "login_with_email": "በኢሜይል ይግቡ", + "create_password": "የይለፍ ቃል ይፍጠሩ", + "confirm_password": "የይለፍ ቃል ያረጋግጡ", + "eight_character_minimum": "ቢያንስ 8 ፊደላት", + "password_match": "የይለፍ ቃሉ ተመሳስሏል", + "sign_up_agreement": "‘ይመዝገቡ’ የሚለውን ሲጫኑ በ‘አገልግሎት ውሎች’ እና ‘በግላዊነት ፖሊሲ’ ይስማማሉ።", + "terms_of_services": "የአገልግሎት ውሎች", + "and": "እና", + "privacy_policy": "የግላዊነት ፖሊሲ", + "register_with_email": "በኢሜል ይመዝገቡ", + "verification_code": "የማረጋገጫ ኮድ", + "resend_code": "ኮዱን እንደገና ላክ", + "code_sent_to_phone": "ኮዱ ወደ ስልክ ቁጥርዎ ተልኳል", + "code_sent_to_email": "ኮዱ ወደ ኢሜል ተልኳል", + "resend_code_in": "ኮዱን እንደገና ለመላክ የቀረው ጊዜ", + "reset_password": " የይለፍ ቃልን ይቀይሩ", + "enter_email_reset_code": "ኢሜይልዎን ያስገቡ። የይለፍ ቃል መለወጫ ኮድ እንልክልዎታለን።", + "please_wait": "እባክዎ ይጠብቁ", + "reset_code_sent": "የመቀየሪያ ኮድ በተሳካ ሁኔታ ተልኳል", + "reset_code": " የመቀየሪያ ኮድ ", + "new_password": "አዲስ የይለፍ ቃል", + "logged_in_successfully": "በተሳካ ሁኔታ ገብተዋል", + "view_course": " ኮርሱን ይመልከቱ", + "continue_learning": "መማርን ይቀጥሉ", + "start_learning": "ትምህርትን ይጀምሩ", + "completed": "ተጠናቋል", + "take_practice": " ልምምድ ያድርጉ", + "your_current_level": "የአሁኑ ደረጃዎ", + "overall_progress": "አጠቃላይ እድገት", + "great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው", + "view_module": "ሞጁሉን ይመልከቱ", + "progress": "እድገት", + "keep_going": " ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ", + "lessons_in_module": " በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ", + "practice": "ልምምድ", + "start": "ጀምር", + "in_progress": "በሂደት ላይ", + "hello": "ሰላም", + "ready_to_learn": " ዛሬ እንግሊዝኛ ለመማር ተዘጋጅተዋል? ", + "learn": "ይማሩ ", + "course": "ኮርስ", + "profile": " ፕሮፋይል ", + "speaking_partner": "የንግግር ጓደኛ", + "practice_what_you_learned": "አሁን የተማሩትን እንለማመድ", + "practice_questions": "ጥቂት ጥያቄዎችን እጠይቃለሁ እና መልስ መስጠት ይችላሉ", + "start_practice": "ልምምድ ጀምር", + "almost_there": "ሊጨርሱ ተቃርበዋል", + "finish_session": "እድገትዎን ለማየት ክፍለ ጊዜውን ያጠናቅቁ", + "continue_practice": "ልምምዱን ይቀጥሉ", + "end_session": "ክፍለ ጊዜውን ያብቁ ", + "tap_start_to_listen": "ለማዳመጥ የጀምር ቁልፉን ይጫኑ", + "practice_speaking": "ንግግርን ይለማመዱ", + "tap_microphone": "ለመናገር ማይክሮፎኑን ይጫኑ", + "reply": "እንደገና አዳምጥ", + "cancel": "ይቅር", + "you_are_speaking": "እየተናገሩ ነው", + "practice_completed": "ልምምዱ ተጠናቅቋል", + "great_improvement": "በዚህኛው በራስ መተማመንዎ ጨምሯል፤ ትልቅ መሻሻል ነው", + "practice_again": "እንደገና ይለማመዱ", + "conversation_review": "የንግግር ግምገማ", + "result": "ውጤት", + "quick_tip": "ጠቃሚ ምክር", + "retry": "እንደገና ይሞክሩ", + "completed_a1": "እንኳን ደስ አለዎት! A1 ደረጃን አጠናቅቀዋል", + "analyzing_speaking": "የንግግር ችሎታዎን እየገመገምን ነው", + "view_profile": "ፕሮፋይሎን ይመልከቱ ", + "hi": "ሰላም", + "edit_profile": "መገለጫ ያስተካክሉ", + "first_name": "የመጀመሪያ ስም", + "last_name": "የአባት ስም", + "gender": "ፆታ", + "male": "ወንድ", + "female": "ሴት", + "phone_number": "የስልክ ቁጥር", + "country": "ሀገር", + "region": "ክልል", + "select_region": "ክልል ይምረጡ", + "enter_your_city": "ከተማዎን ያስገቡ", + "occupation": "የስራ መስክ", + "select_occupation": "ሙያዎን ይምረጡ", + "save_changes": "ለውጦችን ያስቀምጡ", + "my_progress": "የእኔ እድገት", + "track_your_achievement": "ስኬቶችዎን እና ተከታታይ የትምህርት ጉዞዎን ይከታተሉ", + "account_and_privacy": "መለያ እና ግላዊነት", + "manage_settings": "ቅንብሮችን እና የመተግበሪያ ምርጫዎችን ያስተዳድሩ", + "support": "ድጋፍ", + "get_help": "በስልክ ወይም በቴሌግራም እገዛ ያግኙ", + "logout": "ውጣ", + "app_settings": "የመተግበሪያ ቅንብሮች", + "legal_and_information": "ሕጋዊ እና መረጃ", + "change_language": "ቋንቋ ቀይር", + "terms_and_conditions": "ውሎች እና ሁኔታዎች", + "delete_account": "መለያ ሰርዝ", + "language_preference": "የቋንቋ ምርጫ", + "choose_your_language": "ለውጦችን አስቀምጥ", + "switch_language_anytime": "ቋንቋዎችን በማንኛውም ጊዜ መቀየር ይችላሉ", + "need_help": "እገዛ ይፈልጋሉ?", + "call_support": "የስልክ ድጋፍ", + "talk_with_support": "በቀጥታ ከድጋፍ ቡድናችን ጋር ይነጋገሩ", + "telegram_support": "የቴሌግራም ድጋፍ", + "chat_via_telegram": "በቴሌግራም በፍጥነት ይወያዩ", + "call_our_support": "ከ3 ጠዋት እስከ 12 ማታ ድረስ የድጋፍ ቡድናችንን ይደውሉ", + "tap_to_call": "ለመደወል ይንኩ" +}; +static const Map _en = { + "loading": "Loading", + "welcome_back": "Welcome back", + "checking_user_info": "Checking user info", + "dont_have_account": "Don't have an account? Register", + "email": "Email", + "password": "Password", + "forgot_password": "Forgot password?", + "cont": "Continue", + "register": "Register", + "login_with_google": "Login with Google", + "or": "Or", + "login_with_phone": "Login with phone number", + "create_account": "Create an account", + "already_have_account": "Already have an account?", + "login": "Login", + "register_with_google": "Register with Google", + "register_with_phone": "Register with phone number", + "enter_phone_number": "Enter your phone number. We will send you a confirmation code there.", + "login_with_email": "Login with email", + "create_password": "Create password", + "confirm_password": "Confirm password", + "eight_character_minimum": "8 characters minimum", + "password_math": "password match", + "sign_up_agreement": "By clicking ‘Sign Up’, you agree to our ‘Terms of Service’ and ‘Privacy Policy’", + "terms_of_services": "Terms of Service", + "and": "and", + "privacy_policy": "Privacy Policy", + "register_with_email": "Register with email", + "verification_code": "Verification Code", + "resend_code": "Resend Code", + "code_sent_to_phone": "Code sent to your number", + "code_sent_to_email": "Code sent to your email", + "resend_code_in": "Resend code in", + "reset_password": "Reset Password", + "enter_email_reset_code": "Enter your email. We will send you a reset code.", + "please_wait": "Please wait", + "reset_code_sent": "Reset code sent successfully", + "reset_code": "Reset code", + "new_password": "New password", + "logged_in_successfully": "Logged in successfully", + "continue_learning": "Continue Learning", + "start_learning": "Start Learning", + "completed": "Completed", + "view_course": "View course", + "take_practice": "Take practice", + "your_current_level": "Your current level", + "overall_progress": "Overall progress", + "great_work": "Keep up the great work! You're doing amazing", + "view_module": "View module", + "progress": "Progress", + "keep_going": "Let's keep going - you're more than half there", + "lessons_in_module": "Lessons in this module", + "practice": "Practice", + "start": "Start", + "in_progress": "In Progress", + "hello": "Hello", + "ready_to_learn": "Ready to keep learning English today", + "learn": "Learn", + "course": "Course", + "profile": "Profile", + "speaking_partner": "Speaking partner", + "practice_what_you_learned": "Let's practice what you just learnt", + "practice_questions": "I will ask you a few questions and you can respond", + "start_practice": "Start practice", + "almost_there": "You're almost there", + "finish_session": "Finish the session to see your progress", + "continue_practice": "Continue practice", + "end_session": "End session", + "tap_start_to_listen": "Tap the start button to listen", + "practice_speaking": "Practice speaking", + "tap_microphone": "Tap the microphone to speak", + "reply": "Reply", + "cancel": "Cancel", + "you_are_speaking": "You're speaking", + "practice_completed": "Practice completed", + "great_improvement": "You sound more confident this time, great improvement", + "practice_again": "Practice again", + "conversation_review": "Conversation review", + "result": "Result", + "quick_tip": "Quick tip", + "retry": "Retry", + "completed_a1": "Yay, you've completed A1", + "analyzing_speaking": "We're now analyzing your speaking skill", + "view_profile": "View profile", + "hi": "Hi", + "edit_profile": "Edit profile", + "first_name": "First name", + "last_name": "Last name", + "gender": "Gender", + "male": "Male", + "female": "Female", + "phone_number": "Phone number", + "country": "Country", + "region": "Region", + "select_region": "Select region", + "enter_your_city": "Enter your city", + "occupation": "Occupation", + "select_occupation": "Select occupation", + "save_changes": "Save changes", + "my_progress": "My progress", + "track_your_achievement": "Track your achievements and learning streak", + "account_and_privacy": "Account & Privacy", + "manage_settings": "Manage settings and app preference", + "support": "Support", + "get_help": "Get help through phone or Telegram", + "logout": "Logout", + "app_settings": "App settings", + "legal_and_information": "Legal & Information", + "change_language": "Change language", + "terms_and_conditions": "Terms & Conditions", + "delete_account": "Delete account", + "language_preference": "Language preference", + "choose_your_language": "Choose your language", + "switch_language_anytime": "You can switch languages anytime", + "need_help": "Need help?", + "call_support": "Call support", + "talk_with_support": "Talk with our support team directly", + "telegram_support": "Telegram support", + "chat_via_telegram": "Chat instantly via Telegram", + "call_our_support": "Call our support team between 9 AM - 6 PM", + "tap_to_call": "Tap to call" +}; +static const Map> mapLocales = {"am": _am, "en": _en}; } diff --git a/lib/ui/common/translations/locale_keys.g.dart b/lib/ui/common/translations/locale_keys.g.dart index 870487b..4c30a2f 100644 --- a/lib/ui/common/translations/locale_keys.g.dart +++ b/lib/ui/common/translations/locale_keys.g.dart @@ -2,7 +2,7 @@ // ignore_for_file: constant_identifier_names -abstract class LocaleKeys { +abstract class LocaleKeys { static const loading = 'loading'; static const welcome_back = 'welcome_back'; static const checking_user_info = 'checking_user_info'; @@ -44,6 +44,9 @@ abstract class LocaleKeys { static const new_password = 'new_password'; static const logged_in_successfully = 'logged_in_successfully'; static const view_course = 'view_course'; + static const continue_learning = 'continue_learning'; + static const start_learning = 'start_learning'; + static const completed = 'completed'; static const take_practice = 'take_practice'; static const your_current_level = 'your_current_level'; static const overall_progress = 'overall_progress'; @@ -92,6 +95,32 @@ abstract class LocaleKeys { static const phone_number = 'phone_number'; static const country = 'country'; static const region = 'region'; + static const select_region = 'select_region'; + static const enter_your_city = 'enter_your_city'; static const occupation = 'occupation'; + static const select_occupation = 'select_occupation'; static const save_changes = 'save_changes'; + static const my_progress = 'my_progress'; + static const track_your_achievement = 'track_your_achievement'; + static const account_and_privacy = 'account_and_privacy'; + static const manage_settings = 'manage_settings'; + static const support = 'support'; + static const get_help = 'get_help'; + static const logout = 'logout'; + static const app_settings = 'app_settings'; + static const legal_and_information = 'legal_and_information'; + static const change_language = 'change_language'; + static const terms_and_conditions = 'terms_and_conditions'; + static const delete_account = 'delete_account'; + static const language_preference = 'language_preference'; + static const choose_your_language = 'choose_your_language'; + static const switch_language_anytime = 'switch_language_anytime'; + static const need_help = 'need_help'; + static const call_support = 'call_support'; + static const talk_with_support = 'talk_with_support'; + static const telegram_support = 'telegram_support'; + static const chat_via_telegram = 'chat_via_telegram'; + static const call_our_support = 'call_our_support'; + static const tap_to_call = 'tap_to_call'; + } diff --git a/lib/ui/common/ui_helpers.dart b/lib/ui/common/ui_helpers.dart index fbfa7ad..7f2e1b0 100644 --- a/lib/ui/common/ui_helpers.dart +++ b/lib/ui/common/ui_helpers.dart @@ -197,12 +197,8 @@ TextStyle style18W600 = const TextStyle( fontWeight: FontWeight.w600, ); -TextStyle style25W400 = const TextStyle( - fontSize: 25, - color: kcWhite, - fontWeight: FontWeight.w400 -); - +TextStyle style25W400 = + const TextStyle(fontSize: 25, color: kcWhite, fontWeight: FontWeight.w400); TextStyle style25W600 = const TextStyle( fontSize: 25, @@ -275,6 +271,12 @@ TextStyle style16P600 = const TextStyle( fontWeight: FontWeight.w600, ); +TextStyle style16P900 = const TextStyle( + fontSize: 16, + color: kcPrimaryColor, + fontWeight: FontWeight.w900, +); + TextStyle style16DG500 = const TextStyle( fontSize: 16, color: kcDarkGrey, diff --git a/lib/ui/views/account_privacy/account_privacy_view.dart b/lib/ui/views/account_privacy/account_privacy_view.dart index 8970ea5..b04d213 100644 --- a/lib/ui/views/account_privacy/account_privacy_view.dart +++ b/lib/ui/views/account_privacy/account_privacy_view.dart @@ -1,5 +1,7 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/widgets/custom_list_tile.dart'; import '../../common/app_colors.dart'; @@ -57,7 +59,7 @@ class AccountPrivacyView extends StackedView { Widget _buildAppbar(AccountPrivacyViewModel viewModel) => SmallAppBar( showBackButton: true, onPop: viewModel.pop, - title: 'Account Privacy', + title: LocaleKeys.account_and_privacy.tr(), ); Widget _buildContentWrapper(AccountPrivacyViewModel viewModel) => @@ -92,12 +94,12 @@ class AccountPrivacyView extends StackedView { List _buildMenuColumnChildren(AccountPrivacyViewModel viewModel) => [ verticalSpaceLarge, - _buildHeader('App Settings'), + _buildHeader(LocaleKeys.app_settings.tr()), verticalSpaceSmall, _buildLanguageMenu(viewModel), _buildDividerWrapper(), verticalSpaceMedium, - _buildHeader('Legal & Information'), + _buildHeader(LocaleKeys.legal_and_information.tr()), verticalSpaceSmall, _buildTermsAndConditionsMenu(viewModel), _buildPrivacyPolicy(viewModel), @@ -112,23 +114,23 @@ class AccountPrivacyView extends StackedView { Widget _buildLanguageMenu(AccountPrivacyViewModel viewModel) => CustomListTile( isLanguage: true, - language: 'English', icon: Icons.language, - title: 'Change Language', + title: LocaleKeys.change_language.tr(), + language: viewModel.selectedLanguage['language'], onTap: () async => await viewModel.navigateToLanguage(), ); Widget _buildTermsAndConditionsMenu(AccountPrivacyViewModel viewModel) => CustomListTile( icon: Icons.handshake, - title: 'Terms & Conditions', + title: LocaleKeys.terms_and_conditions.tr(), onTap: () async => await viewModel.navigateToTerms(), ); Widget _buildPrivacyPolicy(AccountPrivacyViewModel viewModel) => CustomListTile( - title: 'Privacy Policy', icon: Icons.shield_moon_outlined, + title: LocaleKeys.privacy_policy.tr(), onTap: () async => await viewModel.navigateToPrivacyPolicy(), ); @@ -146,8 +148,8 @@ class AccountPrivacyView extends StackedView { Widget _buildDeleteButton() => CustomElevatedButton( height: 55, borderRadius: 12, - text: 'Delete Account', foregroundColor: kcRed, + text: LocaleKeys.delete_account.tr(), backgroundColor: kcRed.withOpacity(0.25), ); } diff --git a/lib/ui/views/account_privacy/account_privacy_viewmodel.dart b/lib/ui/views/account_privacy/account_privacy_viewmodel.dart index cba03ed..8bc8d4b 100644 --- a/lib/ui/views/account_privacy/account_privacy_viewmodel.dart +++ b/lib/ui/views/account_privacy/account_privacy_viewmodel.dart @@ -3,11 +3,24 @@ import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/app/app.router.dart'; import '../../../app/app.locator.dart'; +import '../../../services/localization_service.dart'; -class AccountPrivacyViewModel extends BaseViewModel { +class AccountPrivacyViewModel extends ReactiveViewModel { // Dependency injection final _navigationService = locator(); + final _localizationService = locator(); + + @override + List get listenableServices => + [ _localizationService]; + + // Languages + Map get _selectedLanguage => + _localizationService.selectedLanguage; + + Map get selectedLanguage => _selectedLanguage; + // Navigation void pop() => _navigationService.back(); diff --git a/lib/ui/views/call_support/call_support_view.dart b/lib/ui/views/call_support/call_support_view.dart index 68641b0..211807f 100644 --- a/lib/ui/views/call_support/call_support_view.dart +++ b/lib/ui/views/call_support/call_support_view.dart @@ -1,6 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_constants.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import '../../common/app_colors.dart'; import '../../common/ui_helpers.dart'; @@ -51,7 +53,7 @@ class CallSupportView extends StackedView { Widget _buildAppbar(CallSupportViewModel viewModel) => SmallAppBar( showBackButton: true, onPop: viewModel.pop, - title: 'Call Support', + title: LocaleKeys.call_support.tr(), ); Widget _buildExpandedColumn(CallSupportViewModel viewModel) => @@ -91,7 +93,7 @@ class CallSupportView extends StackedView { const CircularIcon(icon: Icons.call, size: 50, color: kcPrimaryColor); Widget _buildTitle() => Text( - 'Call our support team between 9 AM - 6 PM', + LocaleKeys.call_our_support.tr(), style: style25DG600, textAlign: TextAlign.center, ); @@ -111,10 +113,10 @@ class CallSupportView extends StackedView { CustomElevatedButton( height: 55, borderRadius: 12, - text: 'Tap to Call', leadingIcon: Icons.call, foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, + text:LocaleKeys.tap_to_call.tr(), onTap: () async => await viewModel.callSupport(), ); } diff --git a/lib/ui/views/course/course_view.dart b/lib/ui/views/course/course_view.dart index 1aaad41..82b09fd 100644 --- a/lib/ui/views/course/course_view.dart +++ b/lib/ui/views/course/course_view.dart @@ -23,7 +23,12 @@ class CourseView extends StackedView { Widget _buildScaffoldWrapper(CourseViewModel viewModel) => Scaffold( backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), + body: _buildScaffoldContainer(viewModel), + ); + + Widget _buildScaffoldContainer(CourseViewModel viewModel) => Container( + decoration: bgDecoration, + child: _buildScaffold(viewModel), ); Widget _buildScaffold(CourseViewModel viewModel) => diff --git a/lib/ui/views/course_catalog/course_catalog_view.dart b/lib/ui/views/course_catalog/course_catalog_view.dart index ee859de..f006ee1 100644 --- a/lib/ui/views/course_catalog/course_catalog_view.dart +++ b/lib/ui/views/course_catalog/course_catalog_view.dart @@ -33,7 +33,12 @@ class CourseCatalogView extends StackedView { Widget _buildScaffoldWrapper(CourseCatalogViewModel viewModel) => Scaffold( backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), + body: _buildScaffoldContainer(viewModel), + ); + + Widget _buildScaffoldContainer(CourseCatalogViewModel viewModel) => Container( + decoration: bgDecoration, + child: _buildScaffold(viewModel), ); Widget _buildScaffold(CourseCatalogViewModel viewModel) => diff --git a/lib/ui/views/course_catalog/course_catalog_viewmodel.dart b/lib/ui/views/course_catalog/course_catalog_viewmodel.dart index a448903..420d89c 100644 --- a/lib/ui/views/course_catalog/course_catalog_viewmodel.dart +++ b/lib/ui/views/course_catalog/course_catalog_viewmodel.dart @@ -27,9 +27,6 @@ class CourseCatalogViewModel extends ReactiveViewModel { // Navigation void pop() => _navigationService.back(); - Future navigateToCoursePractice(int id) async => - _navigationService.navigateToCoursePracticeView(id: id); - Future navigateToCourseUnit(CourseCatalog catalog) async => await _navigationService.navigateToCourseUnitView(catalog: catalog); diff --git a/lib/ui/views/course_lesson/course_lesson_view.dart b/lib/ui/views/course_lesson/course_lesson_view.dart deleted file mode 100644 index b311cd5..0000000 --- a/lib/ui/views/course_lesson/course_lesson_view.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; -import 'package:yimaru_app/models/course.dart'; -import 'package:yimaru_app/models/course_lesson.dart'; -import 'package:yimaru_app/ui/widgets/course_lesson_tile.dart'; - -import '../../common/app_colors.dart'; -import '../../common/enmus.dart'; -import '../../common/ui_helpers.dart'; -import '../../widgets/custom_circular_progress_indicator.dart'; -import '../../widgets/small_app_bar.dart'; -import 'course_lesson_viewmodel.dart'; - -class CourseLessonView extends StackedView { - final Course course; - - const CourseLessonView({Key? key, required this.course}) : super(key: key); - - @override - void onViewModelReady(CourseLessonViewModel viewModel) async { - await viewModel.getCourseLessons(course.id ?? 0); - super.onViewModelReady(viewModel); - } - - @override - CourseLessonViewModel viewModelBuilder(BuildContext context) => - CourseLessonViewModel(); - - @override - Widget builder( - BuildContext context, - CourseLessonViewModel viewModel, - Widget? child, - ) => - _buildScaffoldWrapper(viewModel); - - Widget _buildScaffoldWrapper(CourseLessonViewModel viewModel) => Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), - ); - - Widget _buildScaffold(CourseLessonViewModel viewModel) => - SafeArea(child: _buildBody(viewModel)); - - Widget _buildBody(CourseLessonViewModel viewModel) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildColumn(viewModel), - ); - - Widget _buildColumn(CourseLessonViewModel viewModel) => Column( - children: [ - verticalSpaceMedium, - _buildAppBar(viewModel), - verticalSpaceMedium, - _buildLessonColumnWrapper(viewModel), - ], - ); - - Widget _buildAppBar(CourseLessonViewModel viewModel) => SmallAppBar( - onPop: viewModel.pop, - showBackButton: true, - title: 'Course Detail', - ); - - Widget _buildLessonColumnWrapper(CourseLessonViewModel viewModel) => - Expanded(child: _buildLessonColumnScrollView(viewModel)); - - Widget _buildLessonColumnScrollView(CourseLessonViewModel viewModel) => - SingleChildScrollView( - child: _buildLessonColumn(viewModel), - ); - - Widget _buildLessonColumn(CourseLessonViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: _buildLessonColumnChildren(viewModel), - ); - - List _buildLessonColumnChildren(CourseLessonViewModel viewModel) => - [_buildTitle(), verticalSpaceMedium, _buildListViewBuilder(viewModel)]; - - Widget _buildTitle() => Text( - '${course.title} course lessons', - style: style18DG700, - textAlign: TextAlign.center, - ); - - Widget _buildListViewBuilder(CourseLessonViewModel viewModel) => - viewModel.busy(StateObjects.courseLessons) - ? _buildProgressIndicator() - : _buildListView(viewModel); - - Widget _buildProgressIndicator() => const Center( - child: CustomCircularProgressIndicator(color: kcPrimaryColor), - ); - - Widget _buildListView(CourseLessonViewModel viewModel) => ListView.builder( - shrinkWrap: true, - itemCount: viewModel.courseLessons.length, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => _buildTile( - lesson: viewModel.courseLessons[index], - onPracticeTap: () async => - await viewModel.navigateToCoursePractice(course.id ?? 0), - onVideoTap: () async => await viewModel - .navigateToCourseLessonDetail(viewModel.courseLessons[index])), - ); - - Widget _buildTile({ - required CourseLesson lesson, - required GestureTapCallback onVideoTap, - required GestureTapCallback onPracticeTap, - }) => - CourseLessonTile( - lesson: lesson, - onVideoTap: onVideoTap, - onPracticeTap: onPracticeTap, - ); -} diff --git a/lib/ui/views/course_lesson_detail/course_lesson_detail_view.dart b/lib/ui/views/course_lesson_detail/course_lesson_detail_view.dart index 52da985..cbb1c5d 100644 --- a/lib/ui/views/course_lesson_detail/course_lesson_detail_view.dart +++ b/lib/ui/views/course_lesson_detail/course_lesson_detail_view.dart @@ -1,50 +1,73 @@ import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/models/course_lesson.dart'; +import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_viewmodel.dart'; -import '../../../models/course_lesson.dart'; import '../../common/app_colors.dart'; import '../../common/enmus.dart'; import '../../common/ui_helpers.dart'; import '../../widgets/custom_elevated_button.dart'; import '../../widgets/empty_video_player.dart'; import '../../widgets/small_app_bar.dart'; -import 'course_lesson_detail_viewmodel.dart'; -class CourseLessonDetailView extends StackedView { +class CourseLessonDetailView extends StackedView { final CourseLesson lesson; - const CourseLessonDetailView({Key? key, required this.lesson}) - : super(key: key); + const CourseLessonDetailView({ + Key? key, + required this.lesson, + }) : super(key: key); + + Future _navigate(CourseLessonDetailViewmodel viewModel) async { + await viewModel.pause(); + // await viewModel.navigateToLearnPractice(lesson.id ?? 0); + } @override - void onViewModelReady(CourseLessonDetailViewModel viewModel) async { - await viewModel.initializePlayer(lesson); + void onViewModelReady(CourseLessonDetailViewmodel viewModel) async { + await viewModel.initializePlayer( + lessonId: 0, + moduleId: 0, + url: lesson.videoUrl ?? '', + ); super.onViewModelReady(viewModel); } @override - CourseLessonDetailViewModel viewModelBuilder(BuildContext context) => - CourseLessonDetailViewModel(); + void onDispose(CourseLessonDetailViewmodel viewModel) { + viewModel.close(); + super.onDispose(viewModel); + } + + @override + CourseLessonDetailViewmodel viewModelBuilder(BuildContext context) => + CourseLessonDetailViewmodel(); @override Widget builder( BuildContext context, - CourseLessonDetailViewModel viewModel, + CourseLessonDetailViewmodel viewModel, Widget? child, ) => _buildScaffoldWrapper(viewModel); - Widget _buildScaffoldWrapper(CourseLessonDetailViewModel viewModel) => + Widget _buildScaffoldWrapper(CourseLessonDetailViewmodel viewModel) => Scaffold( backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), + body: _buildScaffoldContainer(viewModel), ); - Widget _buildScaffold(CourseLessonDetailViewModel viewModel) => + Widget _buildScaffoldContainer(CourseLessonDetailViewmodel viewModel) => + Container( + decoration: bgDecoration, + child: _buildScaffold(viewModel), + ); + + Widget _buildScaffold(CourseLessonDetailViewmodel viewModel) => SafeArea(child: _buildColumn(viewModel)); - Widget _buildColumn(CourseLessonDetailViewModel viewModel) => Column( + Widget _buildColumn(CourseLessonDetailViewmodel viewModel) => Column( children: [ verticalSpaceMedium, _buildAppBarWrapper(viewModel), @@ -52,57 +75,58 @@ class CourseLessonDetailView extends StackedView { ], ); - Widget _buildAppBarWrapper(CourseLessonDetailViewModel viewModel) => Padding( + Widget _buildAppBarWrapper(CourseLessonDetailViewmodel viewModel) => Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: _buildAppBar(viewModel)); - Widget _buildAppBar(CourseLessonDetailViewModel viewModel) => SmallAppBar( + Widget _buildAppBar(CourseLessonDetailViewmodel viewModel) => SmallAppBar( onPop: viewModel.pop, showBackButton: true, - title: lesson.title ?? '', ); - Widget _buildBodyColumnWrapper(CourseLessonDetailViewModel viewModel) => + Widget _buildBodyColumnWrapper(CourseLessonDetailViewmodel viewModel) => Expanded( child: _buildBodyColumn(viewModel), ); - Widget _buildBodyColumn(CourseLessonDetailViewModel viewModel) => Column( + Widget _buildBodyColumn(CourseLessonDetailViewmodel viewModel) => Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: _buildBodyColumnChildren(viewModel), ); List _buildBodyColumnChildren( - CourseLessonDetailViewModel viewModel) => + CourseLessonDetailViewmodel viewModel) => [ _buildLevelsColumnWrapper(viewModel), - // _buildContinueButtonWrapper(viewModel) + if (lesson.hasPractice ?? false) _buildPracticeButtonWrapper(viewModel) ]; - Widget _buildLevelsColumnWrapper(CourseLessonDetailViewModel viewModel) => + Widget _buildLevelsColumnWrapper(CourseLessonDetailViewmodel viewModel) => Expanded(child: _buildLevelsColumnScrollView(viewModel)); - Widget _buildLevelsColumnScrollView(CourseLessonDetailViewModel viewModel) => + Widget _buildLevelsColumnScrollView(CourseLessonDetailViewmodel viewModel) => SingleChildScrollView( child: _buildLevelsColumn(viewModel), ); - Widget _buildLevelsColumn(CourseLessonDetailViewModel viewModel) => Column( + Widget _buildLevelsColumn(CourseLessonDetailViewmodel viewModel) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: _buildLevelsColumnChildren(viewModel), ); List _buildLevelsColumnChildren( - CourseLessonDetailViewModel viewModel) => + CourseLessonDetailViewmodel viewModel) => [ - verticalSpaceLarge, + verticalSpaceMedium, _buildVideoPlayerWrapper(viewModel), verticalSpaceMedium, + _buildTitleWrapper(), + verticalSpaceMedium, _buildDescriptionWrapper(), ]; - Widget _buildVideoPlayerWrapper(CourseLessonDetailViewModel viewModel) => + Widget _buildVideoPlayerWrapper(CourseLessonDetailViewmodel viewModel) => Container( height: 200, color: kcBlack, @@ -110,21 +134,32 @@ class CourseLessonDetailView extends StackedView { child: _buildVideoPlayerState(viewModel), ); - Widget _buildVideoPlayerState(CourseLessonDetailViewModel viewModel) => + Widget _buildVideoPlayerState(CourseLessonDetailViewmodel viewModel) => viewModel.chewieController != null && viewModel.videoPlayerController != null && - !viewModel.busy(StateObjects.loadCourseVideo) + !viewModel.busy(StateObjects.loadLessonVideo) ? _buildVideoPlayer(viewModel) : _buildEmptyVideoPlayer(); - Widget _buildVideoPlayer(CourseLessonDetailViewModel viewModel) => + Widget _buildVideoPlayer(CourseLessonDetailViewmodel viewModel) => _buildChewiePlayer(viewModel); - Widget _buildChewiePlayer(CourseLessonDetailViewModel viewModel) => - Chewie(controller: viewModel.chewieController!); + Widget _buildChewiePlayer(CourseLessonDetailViewmodel viewModel) => Chewie( + controller: viewModel.chewieController!, + ); Widget _buildEmptyVideoPlayer() => const EmptyVideoPlayer(); + Widget _buildTitleWrapper() => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildTitle(), + ); + + Widget _buildTitle() => Text( + lesson.title ?? '', + style: style16DG600, + ); + Widget _buildDescriptionWrapper() => Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: _buildDescription(), @@ -132,10 +167,10 @@ class CourseLessonDetailView extends StackedView { Widget _buildDescription() => Text( lesson.description ?? '', - style: style14DG600, + style: style14DG400, ); - Widget _buildPracticeButtonWrapper(CourseLessonDetailViewModel viewModel) => + Widget _buildPracticeButtonWrapper(CourseLessonDetailViewmodel viewModel) => Padding( padding: const EdgeInsets.only( left: 15, @@ -145,14 +180,13 @@ class CourseLessonDetailView extends StackedView { child: _buildPracticeButton(viewModel), ); - Widget _buildPracticeButton(CourseLessonDetailViewModel viewModel) => + Widget _buildPracticeButton(CourseLessonDetailViewmodel viewModel) => CustomElevatedButton( height: 55, - text: 'Practice', borderRadius: 12, + text: 'Start Assessment', foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, - onTap: () async => - await viewModel.navigateToCoursePractice(lesson.courseId ?? 0), + onTap: () async => await _navigate(viewModel), ); } diff --git a/lib/ui/views/course_lesson_detail/course_lesson_detail_viewmodel.dart b/lib/ui/views/course_lesson_detail/course_lesson_detail_viewmodel.dart index c0ac889..1d9bb8c 100644 --- a/lib/ui/views/course_lesson_detail/course_lesson_detail_viewmodel.dart +++ b/lib/ui/views/course_lesson_detail/course_lesson_detail_viewmodel.dart @@ -2,23 +2,27 @@ import 'package:chewie/chewie.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import 'package:video_player/video_player.dart'; -import 'package:yimaru_app/app/app.router.dart'; -import 'package:yimaru_app/models/course_lesson.dart'; import 'package:yimaru_app/services/api_service.dart'; import '../../../app/app.locator.dart'; -import '../../common/app_constants.dart'; +import '../../../services/status_checker_service.dart'; +import '../../../services/vimeo_service.dart'; import '../../common/enmus.dart'; import '../../common/ui_helpers.dart'; -class CourseLessonDetailViewModel extends BaseViewModel { +class CourseLessonDetailViewmodel extends ReactiveViewModel { // Dependency injection + final _apiService = locator(); + final _vimeoService = locator(); + + final _statusChecker = locator(); + final _navigationService = locator(); // Video player config - bool _isCompleted = false; + bool _lessonCompleted = false; ChewieController? _chewieController; @@ -29,82 +33,78 @@ class CourseLessonDetailViewModel extends BaseViewModel { VideoPlayerController? get videoPlayerController => _videoPlayerController; // Video player - - @override - void dispose() { + void close() { _videoPlayerController?.dispose(); _chewieController?.dispose(); - super.dispose(); } Future pause() async { await _chewieController?.pause(); } - Future _videoListener(CourseLesson lesson) async { - final controller = _videoPlayerController; + Future initializePlayer( + {required String url, + required int lessonId, + required int moduleId}) async => + await runBusyFuture( + _initializePlayer(url: url, moduleId: moduleId, lessonId: lessonId), + busyObject: StateObjects.loadLessonVideo); - if (controller == null || !controller.value.isInitialized) return; + Future _initializePlayer( + {required String url, + required int lessonId, + required int moduleId}) async { + final playableUrl = await _vimeoService.getVideoUrl(url); - final position = controller.value.position; - final duration = controller.value.duration; - - if (duration.inSeconds == 0) return; - - double progress = position.inSeconds / duration.inSeconds; - - print("Video progress: ${(progress * 100).toStringAsFixed(2)}%"); - - // Example: mark completion at 95% - if (progress >= 0.95) { - await _onVideoCompleted(lesson); + if (playableUrl == null) { + throw Exception("Unable to load video"); } - } - Future initializePlayer(CourseLesson lesson) async => - await runBusyFuture(_initializePlayer(lesson), - busyObject: StateObjects.loadCourseVideo); - - Future _initializePlayer(CourseLesson lesson) async { - print('URL: $kSampleVideoUrl'); _videoPlayerController = - VideoPlayerController.networkUrl(Uri.parse(kSampleVideoUrl)); + VideoPlayerController.networkUrl(Uri.parse(playableUrl)); await _videoPlayerController?.initialize(); - _videoPlayerController - ?.addListener(() async => await _videoListener(lesson)); + // Listen for video completion + _videoPlayerController?.addListener(() async { + final controller = _videoPlayerController; - if (_videoPlayerController != null) { - _chewieController = ChewieController( - looping: true, - autoPlay: true, - showOptions: true, - showControls: true, - aspectRatio: 16 / 9, - autoInitialize: true, - allowedScreenSleep: false, - videoPlayerController: _videoPlayerController!, - materialProgressColors: buildChewieProgressIndicator, - ); - } + if (controller == null || _lessonCompleted) return; - rebuildUi(); - } + final position = controller.value.position.inSeconds; + final duration = controller.value.duration.inSeconds; - Future _onVideoCompleted(CourseLesson lesson) async { - if (_isCompleted) return; + if (duration <= 0) return; - _isCompleted = true; + // Calculate watched percentage + final progress = position / duration; - print("Video completed!"); + // Mark complete at 95% + if (progress >= 0.95) { + _lessonCompleted = true; - await _apiService.completeLesson(lesson.id ?? 0); + /* await completeLearnLesson( + lessonId: lessonId, + moduleId: moduleId, + );*/ + } + }); + + _chewieController = ChewieController( + looping: false, + autoPlay: true, + showOptions: true, + showControls: true, + aspectRatio: 16 / 9, + autoInitialize: true, + allowedScreenSleep: false, + videoPlayerController: _videoPlayerController!, + materialProgressColors: buildChewieProgressIndicator, + ); + + notifyListeners(); } // Navigation void pop() => _navigationService.back(); - - Future navigateToCoursePractice(int id) => - _navigationService.navigateToCoursePracticeView(id: id); } diff --git a/lib/ui/views/course_module/course_module_view.dart b/lib/ui/views/course_module/course_module_view.dart new file mode 100644 index 0000000..13e714f --- /dev/null +++ b/lib/ui/views/course_module/course_module_view.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/widgets/course_module_tile_large.dart'; + +import '../../../models/course_catalog.dart'; +import '../../../models/course_module.dart'; +import '../../common/app_colors.dart'; +import '../../common/enmus.dart'; +import '../../common/ui_helpers.dart'; +import '../../widgets/custom_circular_progress_indicator.dart'; +import '../../widgets/small_app_bar.dart'; +import 'course_module_viewmodel.dart'; + +class CourseModuleView extends StackedView { + final CourseModule? module; + final CourseCatalog catalog; + + const CourseModuleView( + {Key? key, required this.module, required this.catalog}) + : super(key: key); + + @override + void onViewModelReady(CourseModuleViewModel viewModel) async { + await viewModel.getCourseLessons(module?.id ?? 0); + super.onViewModelReady(viewModel); + } + + @override + CourseModuleViewModel viewModelBuilder(BuildContext context) => + CourseModuleViewModel(); + + @override + Widget builder( + BuildContext context, + CourseModuleViewModel viewModel, + Widget? child, + ) => + _buildScaffoldWrapper(viewModel); + + Widget _buildScaffoldWrapper(CourseModuleViewModel viewModel) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffoldContainer(viewModel), + ); + + Widget _buildScaffoldContainer(CourseModuleViewModel viewModel) => Container( + decoration: bgDecoration, + child: _buildScaffold(viewModel), + ); + + Widget _buildScaffold(CourseModuleViewModel viewModel) => + SafeArea(child: _buildBody(viewModel)); + + Widget _buildBody(CourseModuleViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildColumn(viewModel), + ); + + Widget _buildColumn(CourseModuleViewModel viewModel) => Column( + children: [ + verticalSpaceMedium, + _buildAppBar(viewModel), + verticalSpaceMedium, + _buildModulesColumnWrapper(viewModel), + ], + ); + + Widget _buildAppBar(CourseModuleViewModel viewModel) => SmallAppBar( + onPop: viewModel.pop, + showBackButton: true, + title: 'Module Detail', + ); + + Widget _buildModulesColumnWrapper(CourseModuleViewModel viewModel) => + Expanded(child: _buildLevelsColumnScrollView(viewModel)); + + Widget _buildLevelsColumnScrollView(CourseModuleViewModel viewModel) => + SingleChildScrollView( + child: _buildLevelsColumn(viewModel), + ); + + Widget _buildLevelsColumn(CourseModuleViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: _buildLevelsColumnChildren(viewModel), + ); + + List _buildLevelsColumnChildren(CourseModuleViewModel viewModel) => [ + verticalSpaceMedium, + _buildTitle(), + verticalSpaceMedium, + _buildListViewBuilder(viewModel) + ]; + + Widget _buildTitle() => Text( + catalog.name ?? '', + style: style25DG600, + ); + + Widget _buildListViewBuilder(CourseModuleViewModel viewModel) => + viewModel.busy(StateObjects.courseLessons) + ? _buildProgressIndicator() + : _buildTile(viewModel); + + Widget _buildProgressIndicator() => const Center( + child: CustomCircularProgressIndicator(color: kcPrimaryColor), + ); + + Widget _buildTile(CourseModuleViewModel viewModel) => CourseModuleTileLarge( + module: module, + lessons: viewModel.lessons, + onContinueTap: () {}, + ); +} diff --git a/lib/ui/views/course_lesson/course_lesson_viewmodel.dart b/lib/ui/views/course_module/course_module_viewmodel.dart similarity index 55% rename from lib/ui/views/course_lesson/course_lesson_viewmodel.dart rename to lib/ui/views/course_module/course_module_viewmodel.dart index 44c9c73..b19f585 100644 --- a/lib/ui/views/course_lesson/course_lesson_viewmodel.dart +++ b/lib/ui/views/course_module/course_module_viewmodel.dart @@ -1,45 +1,45 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/app/app.router.dart'; +import 'package:yimaru_app/models/course_lesson.dart'; import '../../../app/app.locator.dart'; -import '../../../models/course_lesson.dart'; -import '../../../services/api_service.dart'; +import '../../../services/course_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/enmus.dart'; -class CourseLessonViewModel extends BaseViewModel { +class CourseModuleViewModel extends ReactiveViewModel { // Dependency injection - final _apiService = locator(); + final _courseService = locator(); final _statusChecker = locator(); final _navigationService = locator(); - // Course lessons - List _courseLessons = []; + @override + List get listenableServices => [_courseService]; - List get courseLessons => _courseLessons; + // Course lessons + List get _lessons => _courseService.lessons; + + List get lessons => _lessons; // Navigation void pop() => _navigationService.back(); - Future navigateToCoursePractice(int id) => - _navigationService.navigateToCoursePracticeView(id: id); - Future navigateToCourseLessonDetail(CourseLesson lesson) async => await _navigationService.navigateToCourseLessonDetailView(lesson: lesson); // Remote api call - // Course lessons - Future getCourseLessons(int courseId) async => - await runBusyFuture(_getCourseLessons(courseId), + // Course modules + Future getCourseLessons(int id) async => + await runBusyFuture(_getCourseLessons(id), busyObject: StateObjects.courseLessons); - Future _getCourseLessons(int courseId) async { + Future _getCourseLessons(int id) async { if (await _statusChecker.checkConnection()) { - _courseLessons = await _apiService.getCourseLessons(1); + await _courseService.getCourseLessons(id); } } } diff --git a/lib/ui/views/course_payment/course_payment_view.dart b/lib/ui/views/course_payment/course_payment_view.dart index 5ca8630..193f50b 100644 --- a/lib/ui/views/course_payment/course_payment_view.dart +++ b/lib/ui/views/course_payment/course_payment_view.dart @@ -127,7 +127,7 @@ class CoursePaymentView extends StackedView { text: 'Subscribe Now', foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, - onTap: () async => await viewModel.navigateToCourseLesson(course), + onTap: () {}, ); Widget _buildSecurePaymentWrapper() => Align( diff --git a/lib/ui/views/course_payment/course_payment_viewmodel.dart b/lib/ui/views/course_payment/course_payment_viewmodel.dart index 6e3a260..15504cc 100644 --- a/lib/ui/views/course_payment/course_payment_viewmodel.dart +++ b/lib/ui/views/course_payment/course_payment_viewmodel.dart @@ -1,7 +1,5 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; -import 'package:yimaru_app/app/app.router.dart'; -import 'package:yimaru_app/models/course.dart'; import '../../../app/app.locator.dart'; @@ -11,7 +9,4 @@ class CoursePaymentViewModel extends BaseViewModel { // Navigation void pop() => _navigationService.back(); - - Future navigateToCourseLesson(Course course) async => - await _navigationService.navigateToCourseLessonView(course: course); } diff --git a/lib/ui/views/course_practice/course_practice_view.dart b/lib/ui/views/course_practice/course_practice_view.dart deleted file mode 100644 index ad4e19e..0000000 --- a/lib/ui/views/course_practice/course_practice_view.dart +++ /dev/null @@ -1,116 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; -import 'package:yimaru_app/ui/widgets/course_practice_card.dart'; - -import '../../common/app_colors.dart'; -import '../../common/ui_helpers.dart'; -import '../../widgets/small_app_bar.dart'; -import 'course_practice_viewmodel.dart'; - -class CoursePracticeView extends StackedView { - final int id; - - const CoursePracticeView({Key? key, required this.id}) : super(key: key); - - @override - void onViewModelReady(CoursePracticeViewModel viewModel) async { - await viewModel.getCoursePractice(id); - super.onViewModelReady(viewModel); - } - - @override - CoursePracticeViewModel viewModelBuilder(BuildContext context) => - CoursePracticeViewModel(); - - @override - Widget builder( - BuildContext context, - CoursePracticeViewModel viewModel, - Widget? child, - ) => - _buildScaffoldWrapper(viewModel); - - Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), - ); - - Widget _buildScaffold(CoursePracticeViewModel viewModel) => - SafeArea(child: _buildBody(viewModel)); - - Widget _buildBody(CoursePracticeViewModel viewModel) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildColumn(viewModel), - ); - - Widget _buildColumn(CoursePracticeViewModel viewModel) => Column( - children: [ - verticalSpaceMedium, - _buildAppBar(viewModel), - verticalSpaceMedium, - _buildPracticeColumnWrapper(viewModel), - ], - ); - - Widget _buildAppBar(CoursePracticeViewModel viewModel) => SmallAppBar( - onPop: viewModel.pop, - showBackButton: true, - ); - - Widget _buildPracticeColumnWrapper(CoursePracticeViewModel viewModel) => - Expanded(child: _buildPracticeColumnScrollView(viewModel)); - - Widget _buildPracticeColumnScrollView(CoursePracticeViewModel viewModel) => - SingleChildScrollView( - child: _buildPracticeColumn(viewModel), - ); - - Widget _buildPracticeColumn(CoursePracticeViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildPracticeColumnChildren(viewModel), - ); - - List _buildPracticeColumnChildren( - CoursePracticeViewModel viewModel) => - [ - verticalSpaceMedium, - _buildTitle(), - _buildSubtitle(), - verticalSpaceMedium, - _buildListView(viewModel) - ]; - - Widget _buildTitle() => Text( - 'Course Practices', - style: style18DG700, - ); - - Widget _buildSubtitle() => Text( - 'Select a practice test your progress', - style: style14DG400, - ); - - Widget _buildListView(CoursePracticeViewModel viewModel) => GridView.builder( - shrinkWrap: true, - itemCount: viewModel.coursePractices.length, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - mainAxisSpacing: 15, - crossAxisSpacing: 15, - childAspectRatio: 1.2, - ), - itemBuilder: (context, index) => _buildCard( - title: viewModel.coursePractices[index].title ?? '', - onTap: () async => await viewModel.navigateToCoursePracticeQuestion( - viewModel.coursePractices[index].id ?? 0), - ), - ); - - Widget _buildCard({ - required String title, - GestureTapCallback? onTap, - }) => - CoursePracticeCard(onTap: onTap, title: title); -} diff --git a/lib/ui/views/course_practice/course_practice_viewmodel.dart b/lib/ui/views/course_practice/course_practice_viewmodel.dart deleted file mode 100644 index f7b1453..0000000 --- a/lib/ui/views/course_practice/course_practice_viewmodel.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:stacked/stacked.dart'; -import 'package:stacked_services/stacked_services.dart'; -import 'package:yimaru_app/app/app.router.dart'; -import 'package:yimaru_app/models/practice.dart'; - -import '../../../app/app.locator.dart'; -import '../../../services/api_service.dart'; -import '../../../services/status_checker_service.dart'; -import '../../common/enmus.dart'; - -class CoursePracticeViewModel extends BaseViewModel { - // Dependency injection - final _apiService = locator(); - - final _statusChecker = locator(); - - final _navigationService = locator(); - - // Course practices - List _coursePractices = []; - - List get coursePractices => _coursePractices; - - // Navigation - void pop() => _navigationService.back(); - - Future navigateToCoursePracticeQuestion(int id) async => - await _navigationService.navigateToCoursePracticeQuestionView(id: id); - - // Remote api call - - // Course practices - Future getCoursePractice(int id) async => - await runBusyFuture(_getCoursePractice(id), - busyObject: StateObjects.coursePractice); - - Future _getCoursePractice(int id) async { - if (await _statusChecker.checkConnection()) { - _coursePractices = await _apiService.getCoursePractices(id); - } - } -} diff --git a/lib/ui/views/course_practice_question/course_practice_question_view.dart b/lib/ui/views/course_practice_question/course_practice_question_view.dart deleted file mode 100644 index 0b6f9d3..0000000 --- a/lib/ui/views/course_practice_question/course_practice_question_view.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; -import 'package:stacked/stacked_annotations.dart'; -import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_view.form.dart'; -import 'package:yimaru_app/ui/views/course_practice_question/screens/practice_questions_screen.dart'; -import 'package:yimaru_app/ui/views/course_practice_question/screens/practice_result_screen.dart'; - -import '../../common/validators/form_validator.dart'; -import 'course_practice_question_viewmodel.dart'; - -@FormView(fields: [ - FormTextField(name: 'answer', validator: FormValidator.validateForm), -]) -class CoursePracticeQuestionView - extends StackedView - with $CoursePracticeQuestionView { - final int id; - - const CoursePracticeQuestionView({Key? key, required this.id}) - : super(key: key); - - @override - void onViewModelReady(CoursePracticeQuestionViewModel viewModel) async { - await viewModel.getCoursePracticeQuestions(id); - syncFormWithViewModel(viewModel); - super.onViewModelReady(viewModel); - } - - @override - CoursePracticeQuestionViewModel viewModelBuilder(BuildContext context) => - CoursePracticeQuestionViewModel(); - - @override - Widget builder( - BuildContext context, - CoursePracticeQuestionViewModel viewModel, - Widget? child, - ) => - _buildAssessmentScreensWrapper(viewModel); - - Widget _buildAssessmentScreensWrapper( - CoursePracticeQuestionViewModel viewModel) => - PopScope( - canPop: false, - onPopInvokedWithResult: (value, data) => viewModel.previousQuestion(), - child: _buildAssessmentScreens(viewModel)); - - Widget _buildAssessmentScreens(CoursePracticeQuestionViewModel viewModel) => - IndexedStack( - index: viewModel.currentPage, - children: _buildScreens(), - ); - - List _buildScreens() => [ - _buildPracticeQuestionScreen(), - _buildPracticeResultScreen(), - ]; - - Widget _buildPracticeQuestionScreen() => - PracticeQuestionsScreen(id: id, answerController: answerController); - - Widget _buildPracticeResultScreen() => const PracticeResultScreen(); -} diff --git a/lib/ui/views/course_practice_question/course_practice_question_view.form.dart b/lib/ui/views/course_practice_question/course_practice_question_view.form.dart deleted file mode 100644 index 039ff64..0000000 --- a/lib/ui/views/course_practice_question/course_practice_question_view.form.dart +++ /dev/null @@ -1,181 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// dart format width=80 - -// ************************************************************************** -// StackedFormGenerator -// ************************************************************************** - -// ignore_for_file: public_member_api_docs, constant_identifier_names, non_constant_identifier_names,unnecessary_this - -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; -import 'package:yimaru_app/ui/common/validators/form_validator.dart'; - -const bool _autoTextFieldValidation = true; - -const String AnswerValueKey = 'answer'; - -final Map - _CoursePracticeQuestionViewTextEditingControllers = {}; - -final Map _CoursePracticeQuestionViewFocusNodes = {}; - -final Map - _CoursePracticeQuestionViewTextValidations = { - AnswerValueKey: FormValidator.validateForm, -}; - -mixin $CoursePracticeQuestionView { - TextEditingController get answerController => - _getFormTextEditingController(AnswerValueKey); - - FocusNode get answerFocusNode => _getFormFocusNode(AnswerValueKey); - - TextEditingController _getFormTextEditingController( - String key, { - String? initialValue, - }) { - if (_CoursePracticeQuestionViewTextEditingControllers.containsKey(key)) { - return _CoursePracticeQuestionViewTextEditingControllers[key]!; - } - - _CoursePracticeQuestionViewTextEditingControllers[key] = - TextEditingController(text: initialValue); - return _CoursePracticeQuestionViewTextEditingControllers[key]!; - } - - FocusNode _getFormFocusNode(String key) { - if (_CoursePracticeQuestionViewFocusNodes.containsKey(key)) { - return _CoursePracticeQuestionViewFocusNodes[key]!; - } - _CoursePracticeQuestionViewFocusNodes[key] = FocusNode(); - return _CoursePracticeQuestionViewFocusNodes[key]!; - } - - /// Registers a listener on every generated controller that calls [model.setData()] - /// with the latest textController values - void syncFormWithViewModel(FormStateHelper model) { - answerController.addListener(() => _updateFormData(model)); - - _updateFormData(model, forceValidate: _autoTextFieldValidation); - } - - /// Registers a listener on every generated controller that calls [model.setData()] - /// with the latest textController values - @Deprecated( - 'Use syncFormWithViewModel instead.' - 'This feature was deprecated after 3.1.0.', - ) - void listenToFormUpdated(FormViewModel model) { - answerController.addListener(() => _updateFormData(model)); - - _updateFormData(model, forceValidate: _autoTextFieldValidation); - } - - /// Updates the formData on the FormViewModel - void _updateFormData(FormStateHelper model, {bool forceValidate = false}) { - model.setData( - model.formValueMap - ..addAll({ - AnswerValueKey: answerController.text, - }), - ); - - if (_autoTextFieldValidation || forceValidate) { - updateValidationData(model); - } - } - - bool validateFormFields(FormViewModel model) { - _updateFormData(model, forceValidate: true); - return model.isFormValid; - } - - /// Calls dispose on all the generated controllers and focus nodes - void disposeForm() { - // The dispose function for a TextEditingController sets all listeners to null - - for (var controller - in _CoursePracticeQuestionViewTextEditingControllers.values) { - controller.dispose(); - } - for (var focusNode in _CoursePracticeQuestionViewFocusNodes.values) { - focusNode.dispose(); - } - - _CoursePracticeQuestionViewTextEditingControllers.clear(); - _CoursePracticeQuestionViewFocusNodes.clear(); - } -} - -extension ValueProperties on FormStateHelper { - bool get hasAnyValidationMessage => this - .fieldsValidationMessages - .values - .any((validation) => validation != null); - - bool get isFormValid { - if (!_autoTextFieldValidation) this.validateForm(); - - return !hasAnyValidationMessage; - } - - String? get answerValue => this.formValueMap[AnswerValueKey] as String?; - - set answerValue(String? value) { - this.setData( - this.formValueMap..addAll({AnswerValueKey: value}), - ); - - if (_CoursePracticeQuestionViewTextEditingControllers.containsKey( - AnswerValueKey)) { - _CoursePracticeQuestionViewTextEditingControllers[AnswerValueKey]?.text = - value ?? ''; - } - } - - bool get hasAnswer => - this.formValueMap.containsKey(AnswerValueKey) && - (answerValue?.isNotEmpty ?? false); - - bool get hasAnswerValidationMessage => - this.fieldsValidationMessages[AnswerValueKey]?.isNotEmpty ?? false; - - String? get answerValidationMessage => - this.fieldsValidationMessages[AnswerValueKey]; -} - -extension Methods on FormStateHelper { - void setAnswerValidationMessage(String? validationMessage) => - this.fieldsValidationMessages[AnswerValueKey] = validationMessage; - - /// Clears text input fields on the Form - void clearForm() { - answerValue = ''; - } - - /// Validates text input fields on the Form - void validateForm() { - this.setValidationMessages({ - AnswerValueKey: getValidationMessage(AnswerValueKey), - }); - } -} - -/// Returns the validation message for the given key -String? getValidationMessage(String key) { - final validatorForKey = _CoursePracticeQuestionViewTextValidations[key]; - if (validatorForKey == null) return null; - - String? validationMessageForKey = validatorForKey( - _CoursePracticeQuestionViewTextEditingControllers[key]?.text, - ); - - return validationMessageForKey; -} - -/// Updates the fieldsValidationMessages on the FormViewModel -void updateValidationData(FormStateHelper model) => - model.setValidationMessages({ - AnswerValueKey: getValidationMessage(AnswerValueKey), - }); diff --git a/lib/ui/views/course_practice_question/course_practice_question_viewmodel.dart b/lib/ui/views/course_practice_question/course_practice_question_viewmodel.dart deleted file mode 100644 index 2b243de..0000000 --- a/lib/ui/views/course_practice_question/course_practice_question_viewmodel.dart +++ /dev/null @@ -1,202 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:stacked/stacked.dart'; -import 'package:stacked_services/stacked_services.dart'; - -import '../../../app/app.locator.dart'; -import '../../../models/option.dart'; -import '../../../models/assessment_question.dart'; -import '../../../services/api_service.dart'; -import '../../../services/status_checker_service.dart'; -import '../../common/app_colors.dart'; -import '../../common/enmus.dart'; - -class CoursePracticeQuestionViewModel extends FormViewModel { - // Dependency injection - final _apiService = locator(); - - final _dialogService = locator(); - - final _statusChecker = locator(); - - final _navigationService = locator(); - - // In-app navigation - int _currentPage = 0; - - int get currentPage => _currentPage; - - int _previousPage = 0; - - int get previousPage => _previousPage; - - final PageController _pageController = PageController(); - - PageController get pageController => _pageController; - - // Course practice questions - bool _focusAnswer = false; - - bool get focusAnswer => _focusAnswer; - - AssessmentQuestion? _currentQuestion; - - AssessmentQuestion? get currentQuestion => _currentQuestion; - - List _coursePracticeQuestions = []; - - List get coursePracticeQuestions => - _coursePracticeQuestions; - - int _currentQuestionIndex = 0; - - int get currentQuestionIndex => _currentQuestionIndex; - - final Map _selectedAnswers = {}; - - Map get selectedAnswers => _selectedAnswers; - - // Question navigation - void previousQuestion() { - if (_currentQuestionIndex != 0) { - _currentQuestionIndex--; - _pageController.previousPage( - duration: const Duration(microseconds: 100), curve: Curves.linear); - rebuildUi(); - } - } - - // In-app navigation - void goBack() { - if (_currentPage == 0) { - pop(); - } else { - _currentPage = 0; - rebuildUi(); - } - } - - void goTo(int page) { - _currentPage = page; - rebuildUi(); - } - - void next({int? page}) async { - if (page == null) { - if (_previousPage != 0) { - _currentPage = _previousPage; - } else { - _currentPage++; - } - } else { - _previousPage = _currentPage; - _currentPage = page; - } - rebuildUi(); - } - - // Answer - void reset() { - _selectedAnswers.clear(); - rebuildUi(); - } - - void setAnswerFocus() { - _focusAnswer = true; - rebuildUi(); - } - - Future abort() async { - bool? response = await showAbortDialog(); - if (response != null && response) { - next(page: 1); - } - } - - Future showAbortDialog() async { - DialogResponse? response = await _dialogService.showDialog( - cancelTitle: 'No', - buttonTitle: 'Yes', - title: 'Abort Practice', - barrierDismissible: true, - cancelTitleColor: kcDarkGrey, - buttonTitleColor: kcPrimaryColor, - description: 'Are you sure to abort the practice ?', - ); - return response?.confirmed; - } - - bool isSelectedAnswer({required int question, required String answer}) { - return _selectedAnswers[question.toString()]?['option'] == answer; - } - - void setSelectedAnswer({required int question, required Option? option}) { - bool correct = false; - if (option?.isCorrect ?? false) { - correct = true; - } - - final data = { - question.toString(): { - 'correct': correct, - 'option': option?.optionText, - 'answer': _currentQuestion?.options - ?.firstWhere((e) => e.isCorrect ?? false) - .optionText - } - }; - - _selectedAnswers.addAll(data); - - rebuildUi(); - } - - // Navigation - void pop() => _navigationService.back(); - - // Remote api call - - // Question navigation - Future _nextQuestion(int id) async { - _currentQuestionIndex++; - - if (_currentQuestionIndex == _coursePracticeQuestions.length) { - next(); - } else { - if (await _statusChecker.checkConnection()) { - _currentQuestion = await _apiService.getCoursePracticeQuestion(id); - _pageController.jumpToPage(_currentQuestionIndex); - } - } - } - - Future nextQuestion(int id) async => - await runBusyFuture(_nextQuestion(id), - busyObject: StateObjects.coursePractice); - - // Course practice questions - Future getCoursePracticeQuestions(int id) async => - await runBusyFuture(_getCoursePracticeQuestions(id), - busyObject: StateObjects.coursePracticeQuestions); - - Future _getCoursePracticeQuestions(int id) async { - if (await _statusChecker.checkConnection()) { - _coursePracticeQuestions = - await _apiService.getCoursePracticeQuestions(id); - if (_coursePracticeQuestions.isNotEmpty) { - _currentQuestion = await _apiService - .getCoursePracticeQuestion(coursePracticeQuestions.first.id ?? 0); - } - } - } - - // Course practice question - Future getCoursePracticeQuestion(int id) async => - await runBusyFuture(_getCoursePracticeQuestion(id), - busyObject: StateObjects.coursePractice); - - Future _getCoursePracticeQuestion(int id) async { - if (await _statusChecker.checkConnection()) { - _currentQuestion = await _apiService.getCoursePracticeQuestion(id); - } - } -} diff --git a/lib/ui/views/course_practice_question/screens/practice_questions_screen.dart b/lib/ui/views/course_practice_question/screens/practice_questions_screen.dart deleted file mode 100644 index 049b66a..0000000 --- a/lib/ui/views/course_practice_question/screens/practice_questions_screen.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; -import 'package:yimaru_app/ui/common/app_colors.dart'; -import 'package:yimaru_app/ui/common/enmus.dart'; -import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_viewmodel.dart'; -import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; -import 'package:yimaru_app/ui/widgets/selectable_course_practice_question.dart'; -import 'package:yimaru_app/ui/widgets/writing_course_practice_question.dart'; - -import 'question_loading_screen.dart'; - -class PracticeQuestionsScreen - extends ViewModelWidget { - final int id; - final TextEditingController answerController; - - const PracticeQuestionsScreen( - {super.key, required this.id, required this.answerController}); - - @override - Widget build( - BuildContext context, CoursePracticeQuestionViewModel viewModel) => - _buildAssessmentScreens(viewModel); - - Widget _buildAssessmentScreens(CoursePracticeQuestionViewModel viewModel) => - viewModel.busy(StateObjects.coursePracticeQuestions) || - viewModel.busy(StateObjects.coursePracticeQuestion) || - viewModel.coursePracticeQuestions.isEmpty || - viewModel.currentQuestion == null - ? _buildPageLoadingIndicator(viewModel) - : _buildScaffoldWrapper(viewModel); - - Widget _buildPageLoadingIndicator( - CoursePracticeQuestionViewModel viewModel) => - QuestionLoadingScreen( - onPop: viewModel.coursePracticeQuestions.isEmpty || - viewModel.currentQuestion == null - ? viewModel.pop - : null, - isEmpty: viewModel.coursePracticeQuestions.isEmpty || - viewModel.currentQuestion == null - ? true - : false, - isLoading: viewModel.busy(StateObjects.coursePracticeQuestions) || - viewModel.busy(StateObjects.coursePracticeQuestion), - onTap: () async => await viewModel.getCoursePracticeQuestions(id), - ); - - Widget _buildScaffoldWrapper(CoursePracticeQuestionViewModel viewModel) => - Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), - ); - - Widget _buildScaffold(CoursePracticeQuestionViewModel viewModel) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildScaffoldChildren(viewModel), - ); - - List _buildScaffoldChildren( - CoursePracticeQuestionViewModel viewModel) => - [_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; - - Widget _buildAppBar(CoursePracticeQuestionViewModel viewModel) => LargeAppBar( - onClose: viewModel.abort, - showLanguageSelection: false, - onPop: viewModel.previousQuestion, - showBackButton: viewModel.currentQuestionIndex == 0 ? false : true, - ); - - Widget _buildExpandedBody(CoursePracticeQuestionViewModel viewModel) => - Expanded(child: _buildBodyWrapper(viewModel)); - - Widget _buildBodyWrapper(CoursePracticeQuestionViewModel viewModel) => - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildQuestion(viewModel), - ); - - Widget _buildQuestion(CoursePracticeQuestionViewModel viewModel) => - PageView.builder( - controller: viewModel.pageController, - physics: const NeverScrollableScrollPhysics(), - itemCount: viewModel.coursePracticeQuestions.length, - itemBuilder: (cotext, index) => - _buildQuestionType(index: index, viewModel: viewModel), - ); - - Widget _buildQuestionType( - {required int index, - required CoursePracticeQuestionViewModel viewModel}) => - viewModel.currentQuestion?.questionType == 'SHORT_ANSWER' - ? _buildWritingCoursePracticeQuestion(index) - : _buildCoursePracticeQuestionWrapper(index); - - Widget _buildCoursePracticeQuestionWrapper(int index) => - SingleChildScrollView( - child: _buildSelectableCoursePracticeQuestion(index), - ); - - Widget _buildSelectableCoursePracticeQuestion(int index) => - SelectableCoursePracticeQuestion(index: index); - - Widget _buildWritingCoursePracticeQuestion(int index) => - WritingCoursePracticeQuestion( - index: index, answerController: answerController); -} diff --git a/lib/ui/views/course_practice_question/screens/practice_result_screen.dart b/lib/ui/views/course_practice_question/screens/practice_result_screen.dart deleted file mode 100644 index ec1909a..0000000 --- a/lib/ui/views/course_practice_question/screens/practice_result_screen.dart +++ /dev/null @@ -1,142 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stacked/stacked.dart'; -import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_viewmodel.dart'; - -import '../../../common/app_colors.dart'; -import '../../../common/ui_helpers.dart'; -import '../../../widgets/custom_elevated_button.dart'; -import '../../../widgets/large_app_bar.dart'; - -class PracticeResultScreen - extends ViewModelWidget { - const PracticeResultScreen({super.key}); - - void _retake(CoursePracticeQuestionViewModel viewModel) { - viewModel.reset(); - viewModel.goTo(0); - } - - @override - Widget build( - BuildContext context, CoursePracticeQuestionViewModel viewModel) => - _buildScaffoldWrapper(viewModel); - - Widget _buildScaffoldWrapper(CoursePracticeQuestionViewModel viewModel) => - Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), - ); - - Widget _buildScaffold(CoursePracticeQuestionViewModel viewModel) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildScaffoldChildren(viewModel), - ); - - List _buildScaffoldChildren( - CoursePracticeQuestionViewModel viewModel) => - [ - _buildAppBar(viewModel), - verticalSpaceMedium, - _buildExpandedBody(viewModel) - ]; - - Widget _buildExpandedBody(CoursePracticeQuestionViewModel viewModel) => - Expanded(child: _buildBodyWrapper(viewModel)); - - Widget _buildBodyWrapper(CoursePracticeQuestionViewModel viewModel) => - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildBody(viewModel), - ); - - Widget _buildBody(CoursePracticeQuestionViewModel viewModel) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildBodyChildren(viewModel), - ); - - List _buildBodyChildren(CoursePracticeQuestionViewModel viewModel) => - [_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)]; - - Widget _buildUpperColumn(CoursePracticeQuestionViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildUpperColumnChildren(viewModel), - ); - - List _buildUpperColumnChildren( - CoursePracticeQuestionViewModel viewModel) => - [ - verticalSpaceLarge, - _buildIcon(), - verticalSpaceMedium, - _buildTitle(), - verticalSpaceSmall, - _buildSubtitle(), - ]; - - Widget _buildAppBar(CoursePracticeQuestionViewModel viewModel) => LargeAppBar( - showBackButton: true, - showLanguageSelection: false, - onPop: () => viewModel.pop(), - ); - - Widget _buildIcon() => SvgPicture.asset( - 'assets/icons/complete.svg', - ); - - Widget _buildTitle() => Text( - 'Practice Completed', - style: style25DG600, - textAlign: TextAlign.center, - ); - - Widget _buildSubtitle() => Text( - 'You’ve finished this practice. Great work!', - style: style14MG400, - textAlign: TextAlign.center, - ); - - Widget _buildLowerColumn(CoursePracticeQuestionViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - children: _buildLowerColumnChildren(viewModel), - ); - - List _buildLowerColumnChildren( - CoursePracticeQuestionViewModel viewModel) => - [ - _buildContinueButton(viewModel), - verticalSpaceSmall, - _buildSkipButtonWrapper(viewModel) - ]; - - Widget _buildContinueButton(CoursePracticeQuestionViewModel viewModel) => - CustomElevatedButton( - height: 55, - safe: false, - borderRadius: 12, - text: 'Practice Again', - foregroundColor: kcWhite, - onTap: () => _retake(viewModel), - backgroundColor: kcPrimaryColor, - ); - - Widget _buildSkipButtonWrapper(CoursePracticeQuestionViewModel viewModel) => - Padding( - padding: const EdgeInsets.only(bottom: 50), - child: _buildSkipButton(viewModel), - ); - - Widget _buildSkipButton(CoursePracticeQuestionViewModel viewModel) => - CustomElevatedButton( - height: 55, - text: 'Continue', - borderRadius: 12, - onTap: viewModel.pop, - backgroundColor: kcWhite, - borderColor: kcPrimaryColor, - foregroundColor: kcPrimaryColor, - ); -} diff --git a/lib/ui/views/course_practice_question/screens/question_loading_screen.dart b/lib/ui/views/course_practice_question/screens/question_loading_screen.dart deleted file mode 100644 index 49264cf..0000000 --- a/lib/ui/views/course_practice_question/screens/question_loading_screen.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart'; - -import '../../../common/app_colors.dart'; -import '../../../widgets/large_app_bar.dart'; -import '../../../widgets/refresh_button.dart'; - -class QuestionLoadingScreen extends StatelessWidget { - final bool isEmpty; - final bool isLoading; - final GestureTapCallback? onPop; - final GestureTapCallback? onTap; - const QuestionLoadingScreen( - {super.key, - this.onTap, - this.onPop, - required this.isEmpty, - required this.isLoading}); - - @override - Widget build(BuildContext context) => _buildScaffoldWrapper(); - - Widget _buildScaffoldWrapper() => Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffold(), - ); - - Widget _buildScaffold() => Stack( - children: [ - _buildColumn(), - if (isEmpty) _buildRefreshButton(), - if (isLoading) _buildPageIndicator() - ], - ); - - Widget _buildColumn() => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildColumnChildren(), - ); - - List _buildColumnChildren() => [_buildAppBar(), _buildBody()]; - - Widget _buildAppBar() => LargeAppBar( - onPop: onPop, - showBackButton: true, - showLanguageSelection: false, - ); - - Widget _buildBody() => Expanded(child: Container()); - - Widget _buildPageIndicator() => const PageLoadingIndicator(); - - Widget _buildRefreshButton() => RefreshButton(onTap: onTap); -} diff --git a/lib/ui/views/course_unit/course_unit_view.dart b/lib/ui/views/course_unit/course_unit_view.dart index 6beb031..1ba4f3a 100644 --- a/lib/ui/views/course_unit/course_unit_view.dart +++ b/lib/ui/views/course_unit/course_unit_view.dart @@ -39,7 +39,12 @@ class CourseUnitView extends StackedView { Widget _buildScaffoldWrapper(CourseUnitViewModel viewModel) => Scaffold( backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), + body: _buildScaffoldContainer(viewModel), + ); + + Widget _buildScaffoldContainer(CourseUnitViewModel viewModel) => Container( + decoration: bgDecoration, + child: _buildScaffold(viewModel), ); Widget _buildScaffold(CourseUnitViewModel viewModel) => @@ -94,7 +99,7 @@ class CourseUnitView extends StackedView { Widget _buildTitle() => Text( catalog.name ?? '', - style: style18P600, + style: style25DG600, ); Widget _buildCourseModuleBanner() => const CourseModuleBanner(); @@ -130,19 +135,20 @@ class CourseUnitView extends StackedView { index: index, unit: viewModel.units[index], onPracticeTap: () {}, - onLessonTap: () {}), + onViewTap: () {}), ); Widget _buildTile({ required int index, required CourseUnit unit, - required GestureTapCallback onLessonTap, + required GestureTapCallback onViewTap, required GestureTapCallback onPracticeTap, }) => CourseUnitTile( unit: unit, index: index, - onLessonTap: onLessonTap, + catalog: catalog, + onLessonTap: onViewTap, onPracticeTap: onPracticeTap, ); } diff --git a/lib/ui/views/course_unit/course_unit_viewmodel.dart b/lib/ui/views/course_unit/course_unit_viewmodel.dart index 4fabcfb..db9c067 100644 --- a/lib/ui/views/course_unit/course_unit_viewmodel.dart +++ b/lib/ui/views/course_unit/course_unit_viewmodel.dart @@ -1,7 +1,10 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; +import 'package:yimaru_app/app/app.router.dart'; +import 'package:yimaru_app/models/course_catalog.dart'; import '../../../app/app.locator.dart'; +import '../../../models/course_module.dart'; import '../../../models/course_unit.dart'; import '../../../services/course_service.dart'; import '../../../services/status_checker_service.dart'; @@ -26,6 +29,12 @@ class CourseUnitViewModel extends ReactiveViewModel { // Navigation void pop() => _navigationService.back(); + Future navigateToCourseModule( + {required CourseModule? module, + required CourseCatalog catalog}) async => + await _navigationService.navigateToCourseModuleView( + module: module, catalog: catalog); + // Remote api call // Course units @@ -39,15 +48,13 @@ class CourseUnitViewModel extends ReactiveViewModel { } } - Future getCourseUnitModules( - {required int id, required int index}) async => - await runBusyFuture(_getCourseUnitModules(id: id, index: index), - busyObject: StateObjects.courseModules); + Future getCourseModules({required int id, required int index}) async => + await runBusyFuture(_getCourseModules(id: id, index: index), + busyObject: index); - Future _getCourseUnitModules( - {required int id, required int index}) async { + Future _getCourseModules({required int id, required int index}) async { if (await _statusChecker.checkConnection()) { - await _courseService.getCourseUnitModule(id: id, index: index); + await _courseService.getCourseModules(id: id, index: index); } } } diff --git a/lib/ui/views/failure/failure_view.dart b/lib/ui/views/failure/failure_view.dart index 297cceb..d775e2d 100644 --- a/lib/ui/views/failure/failure_view.dart +++ b/lib/ui/views/failure/failure_view.dart @@ -40,7 +40,7 @@ class FailureView extends StackedView { ]; Widget _buildBackground() => Image.asset( - 'assets/images/onboarding_1.png', + 'assets/images/loading.png', fit: BoxFit.fill, width: double.maxFinite, height: double.maxFinite, diff --git a/lib/ui/views/home/home_view.dart b/lib/ui/views/home/home_view.dart index afc87cc..4cda9d8 100644 --- a/lib/ui/views/home/home_view.dart +++ b/lib/ui/views/home/home_view.dart @@ -1,6 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'; import 'package:yimaru_app/ui/views/profile/profile_view.dart'; @@ -44,18 +46,18 @@ class HomeView extends StackedView { ]; BottomNavigationBarItem _buildLearnItem() => BottomNavigationBarItem( - label: 'Learn', icon: _buildLearnIcon(), + label: LocaleKeys.learn.tr(), ); BottomNavigationBarItem _buildCourseItem() => BottomNavigationBarItem( - label: 'Course', icon: _buildCourseIcon(), + label: LocaleKeys.course.tr(), ); BottomNavigationBarItem _buildProfileItem() => BottomNavigationBarItem( - label: 'Profile', icon: _buildProfileIcon(), + label: LocaleKeys.profile.tr(), ); Widget _buildLearnIcon() => const Icon(Icons.school); diff --git a/lib/ui/views/landing/landing_view.dart b/lib/ui/views/landing/landing_view.dart index 25554ef..d136a1c 100644 --- a/lib/ui/views/landing/landing_view.dart +++ b/lib/ui/views/landing/landing_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/views/landing/screens/first_landing_screen.dart'; +import 'package:yimaru_app/ui/views/landing/screens/fourth_landing_screen.dart'; import 'package:yimaru_app/ui/views/landing/screens/second_landing_screen.dart'; import 'package:yimaru_app/ui/views/landing/screens/third_landing_screen.dart'; @@ -10,11 +11,10 @@ import 'landing_viewmodel.dart'; class LandingView extends StackedView { const LandingView({Key? key}) : super(key: key); - @override LandingViewModel viewModelBuilder( - BuildContext context, - ) => + BuildContext context, + ) => LandingViewModel(); @override @@ -22,25 +22,30 @@ class LandingView extends StackedView { BuildContext context, LandingViewModel viewModel, Widget? child, - )=> _buildLandingScreens(viewModel); + ) => + _buildLandingScreens(viewModel); Widget _buildLandingScreens(LandingViewModel viewModel) => FlutterCarousel( - options: FlutterCarouselOptions( - autoPlay: true, - viewportFraction: 1, - showIndicator: true, - indicatorMargin: 40, - height: double.maxFinite, - slideIndicator: CircularSlideIndicator( - slideIndicatorOptions: - const SlideIndicatorOptions(indicatorRadius: 2.5), - ), - ), - items: _buildScreens(), - ); + options: FlutterCarouselOptions( + autoPlay: true, + viewportFraction: 1, + showIndicator: true, + indicatorMargin: 40, + height: double.maxFinite, + slideIndicator: CircularSlideIndicator( + slideIndicatorOptions: + const SlideIndicatorOptions(indicatorRadius: 2.5), + ), + ), + items: _buildScreens(), + ); - List _buildScreens() => - [_buildFirstWelcome(), _buildSecondWelcome(), _buildThirdWelcome()]; + List _buildScreens() => [ + _buildFirstWelcome(), + _buildSecondWelcome(), + _buildThirdWelcome(), + _buildFourthWelcome() + ]; Widget _buildFirstWelcome() => const FirstLandingScreen(); @@ -48,5 +53,5 @@ class LandingView extends StackedView { Widget _buildThirdWelcome() => const ThirdLandingScreen(); - + Widget _buildFourthWelcome() => const FourthLandingScreen(); } diff --git a/lib/ui/views/landing/screens/first_landing_screen.dart b/lib/ui/views/landing/screens/first_landing_screen.dart index 8e0ab9b..7180f65 100644 --- a/lib/ui/views/landing/screens/first_landing_screen.dart +++ b/lib/ui/views/landing/screens/first_landing_screen.dart @@ -19,22 +19,20 @@ class FirstLandingScreen extends ViewModelWidget { backgroundColor: kcPrimaryColor, body: _buildScaffoldPadding(viewModel), ); - Widget _buildScaffoldPadding(LandingViewModel viewModel)=> Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildScaffold(viewModel),); + Widget _buildScaffoldPadding(LandingViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildScaffold(viewModel), + ); Widget _buildScaffold(LandingViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildScaffoldChildren(viewModel), + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildScaffoldChildren(viewModel), ); List _buildScaffoldChildren(LandingViewModel viewModel) => - [ _buildUpperColumn(),_buildLowerColumnWrapper(viewModel)]; - - - + [_buildUpperColumn(), _buildLowerColumnWrapper(viewModel)]; Widget _buildUpperColumn() => Column( mainAxisSize: MainAxisSize.min, @@ -42,43 +40,38 @@ class FirstLandingScreen extends ViewModelWidget { children: _buildUpperColumnChildren(), ); - List _buildUpperColumnChildren() => [ - verticalSpaceLarge, - _buildIconWrapper(), - verticalSpaceLarge + List _buildUpperColumnChildren() => + [verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge]; - ]; - - Widget _buildIconWrapper()=> Align(alignment: Alignment.topLeft,child: _buildIcon(),); + Widget _buildIconWrapper() => Align( + alignment: Alignment.topLeft, + child: _buildIcon(), + ); Widget _buildIcon() => SvgPicture.asset( 'assets/icons/logo.svg', height: 25, ); - - Widget _buildLowerColumnWrapper(LandingViewModel viewModel) => Expanded( - child: _buildLowerColumn(viewModel), - ); + child: _buildLowerColumn(viewModel), + ); Widget _buildLowerColumn(LandingViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildLowerColumnChildren(viewModel), - ); + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildLowerColumnChildren(viewModel), + ); List _buildLowerColumnChildren(LandingViewModel viewModel) => [ - _buildTitle(), - verticalSpaceMedium, - _buildImageWrapper(), - verticalSpaceMedium, - _buildSafeWrapper(viewModel) - ]; + _buildTitle(), + verticalSpaceMedium, + _buildImageWrapper(), + verticalSpaceMedium, + _buildSafeWrapper(viewModel) + ]; - Widget _buildTitle() => - - Text.rich( + Widget _buildTitle() => Text.rich( TextSpan( text: 'እንግሊዝኛ\n', style: style25W600, @@ -90,29 +83,26 @@ class FirstLandingScreen extends ViewModelWidget { TextSpan( text: ' ሰዓት ', style: style25W600, - ), - TextSpan( text: 'ይማሩ!', style: style25W400, - ), ], ), ); - Widget _buildImageWrapper()=> Expanded(child: _buildImageClipper()); + Widget _buildImageWrapper() => Expanded(child: _buildImageClipper()); - Widget _buildImageClipper()=> ClipRRect( - borderRadius: BorderRadius.circular(25), - child: _buildImage(), - ); - - - - Widget _buildImage()=> Image.asset('assets/images/profile.png',fit: BoxFit.cover,); + Widget _buildImageClipper() => ClipRRect( + borderRadius: BorderRadius.circular(25), + child: _buildImage(), + ); + Widget _buildImage() => Image.asset( + 'assets/images/landing_1.jpg', + fit: BoxFit.cover, + ); Widget _buildSafeWrapper(LandingViewModel viewModel) => SafeArea(child: _buildContinueButtonWrapper(viewModel)); diff --git a/lib/ui/views/landing/screens/fourth_landing_screen.dart b/lib/ui/views/landing/screens/fourth_landing_screen.dart new file mode 100644 index 0000000..659c76a --- /dev/null +++ b/lib/ui/views/landing/screens/fourth_landing_screen.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; + +import '../../../widgets/custom_circular_progress_indicator.dart'; +import '../landing_viewmodel.dart'; + +class FourthLandingScreen extends ViewModelWidget { + const FourthLandingScreen({super.key}); + + @override + Widget build(BuildContext context, LandingViewModel viewModel) => + _buildScaffoldWrapper(viewModel); + + Widget _buildScaffoldWrapper(LandingViewModel viewModel) => Scaffold( + backgroundColor: Colors.amber, + body: _buildScaffoldPadding(viewModel), + ); + Widget _buildScaffoldPadding(LandingViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildScaffold(viewModel), + ); + + Widget _buildScaffold(LandingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildScaffoldChildren(viewModel), + ); + + List _buildScaffoldChildren(LandingViewModel viewModel) => + [_buildUpperColumn(), _buildLowerColumnWrapper(viewModel)]; + + Widget _buildUpperColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); + + List _buildUpperColumnChildren() => + [verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge]; + + Widget _buildIconWrapper() => Align( + alignment: Alignment.topLeft, + child: _buildIcon(), + ); + + Widget _buildIcon() => SvgPicture.asset( + 'assets/icons/logo.svg', + color: kcPrimaryColor, + height: 25, + ); + + Widget _buildLowerColumnWrapper(LandingViewModel viewModel) => Expanded( + child: _buildLowerColumn(viewModel), + ); + + Widget _buildLowerColumn(LandingViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildLowerColumnChildren(viewModel), + ); + + List _buildLowerColumnChildren(LandingViewModel viewModel) => [ + _buildTitle(), + verticalSpaceMedium, + _buildImageWrapper(), + verticalSpaceMedium, + _buildSafeWrapper(viewModel) + ]; + + Widget _buildTitle() => Text.rich( + TextSpan( + text: 'እንግሊዝኛ\n', + style: style25P600, + children: [ + TextSpan( + text: 'በማንኛውም', + style: style25P400, + ), + TextSpan( + text: ' እድሜ ', + style: style25P600, + ), + TextSpan( + text: 'ይማሩ!', + style: style25P400, + ), + ], + ), + ); + + Widget _buildImageWrapper() => Expanded(child: _buildImageClipper()); + + Widget _buildImageClipper() => ClipRRect( + borderRadius: BorderRadius.circular(25), + child: _buildImage(), + ); + + Widget _buildImage() => Image.asset( + 'assets/images/landing_2.jpg', + fit: BoxFit.cover, + ); + + Widget _buildSafeWrapper(LandingViewModel viewModel) => + SafeArea(child: _buildContinueButtonWrapper(viewModel)); + + Widget _buildContinueButtonWrapper(LandingViewModel viewModel) => Align( + alignment: Alignment.bottomCenter, + child: _buildButtonContainer(viewModel), + ); + + Widget _buildButtonContainer(LandingViewModel viewModel) => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildContinueButtonState(viewModel), + ); + + Widget _buildContinueButtonState(LandingViewModel viewModel) => + viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel); + + Widget _buildIndicator() => + const CustomCircularProgressIndicator(color: kcWhite); + + Widget _buildContinueButton(LandingViewModel viewModel) => + CustomElevatedButton( + height: 55, + borderRadius: 25, + text: 'Get Started', + foregroundColor: kcWhite, + backgroundColor: kcPrimaryColor, + onTap: () async => await viewModel.setFirstTimeInstall(), + ); +} diff --git a/lib/ui/views/landing/screens/second_landing_screen.dart b/lib/ui/views/landing/screens/second_landing_screen.dart index be5a8e6..47ee511 100644 --- a/lib/ui/views/landing/screens/second_landing_screen.dart +++ b/lib/ui/views/landing/screens/second_landing_screen.dart @@ -16,70 +16,63 @@ class SecondLandingScreen extends ViewModelWidget { _buildScaffoldWrapper(viewModel); Widget _buildScaffoldWrapper(LandingViewModel viewModel) => Scaffold( - backgroundColor: Colors.amber, - body: _buildScaffoldPadding(viewModel), - ); - Widget _buildScaffoldPadding(LandingViewModel viewModel)=> Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildScaffold(viewModel),); + backgroundColor: Colors.amber, + body: _buildScaffoldPadding(viewModel), + ); + Widget _buildScaffoldPadding(LandingViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildScaffold(viewModel), + ); Widget _buildScaffold(LandingViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildScaffoldChildren(viewModel), - ); + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildScaffoldChildren(viewModel), + ); List _buildScaffoldChildren(LandingViewModel viewModel) => - [ _buildUpperColumn(),_buildLowerColumnWrapper(viewModel)]; - - - + [_buildUpperColumn(), _buildLowerColumnWrapper(viewModel)]; Widget _buildUpperColumn() => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildUpperColumnChildren(), - ); + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); - List _buildUpperColumnChildren() => [ - verticalSpaceLarge, - _buildIconWrapper(), - verticalSpaceLarge + List _buildUpperColumnChildren() => + [verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge]; - ]; - - Widget _buildIconWrapper()=> Align(alignment: Alignment.topLeft,child: _buildIcon(),); + Widget _buildIconWrapper() => Align( + alignment: Alignment.topLeft, + child: _buildIcon(), + ); Widget _buildIcon() => SvgPicture.asset( - 'assets/icons/logo.svg', - color: kcPrimaryColor, - height: 25, - ); - - + 'assets/icons/logo.svg', + color: kcPrimaryColor, + height: 25, + ); Widget _buildLowerColumnWrapper(LandingViewModel viewModel) => Expanded( - child: _buildLowerColumn(viewModel), - ); + child: _buildLowerColumn(viewModel), + ); Widget _buildLowerColumn(LandingViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildLowerColumnChildren(viewModel), - ); + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildLowerColumnChildren(viewModel), + ); List _buildLowerColumnChildren(LandingViewModel viewModel) => [ - _buildTitle(), - verticalSpaceMedium, - _buildImageWrapper(), - verticalSpaceMedium, - _buildSafeWrapper(viewModel) - ]; + _buildTitle(), + verticalSpaceMedium, + _buildImageWrapper(), + verticalSpaceMedium, + _buildSafeWrapper(viewModel) + ]; - Widget _buildTitle() => - - Text.rich( + Widget _buildTitle() => Text.rich( TextSpan( text: 'እንግሊዝኛ\n', style: style25P600, @@ -91,42 +84,39 @@ class SecondLandingScreen extends ViewModelWidget { TextSpan( text: ' እድሜ ', style: style25P600, - ), - TextSpan( text: 'ይማሩ!', style: style25P400, - ), ], ), ); - Widget _buildImageWrapper()=> Expanded(child: _buildImageClipper()); + Widget _buildImageWrapper() => Expanded(child: _buildImageClipper()); - Widget _buildImageClipper()=> ClipRRect( - borderRadius: BorderRadius.circular(25), - child: _buildImage(), - ); - - - - Widget _buildImage()=> Image.asset('assets/images/profile.png',fit: BoxFit.cover,); + Widget _buildImageClipper() => ClipRRect( + borderRadius: BorderRadius.circular(25), + child: _buildImage(), + ); + Widget _buildImage() => Image.asset( + 'assets/images/landing_2.jpg', + fit: BoxFit.cover, + ); Widget _buildSafeWrapper(LandingViewModel viewModel) => SafeArea(child: _buildContinueButtonWrapper(viewModel)); Widget _buildContinueButtonWrapper(LandingViewModel viewModel) => Align( - alignment: Alignment.bottomCenter, - child: _buildButtonContainer(viewModel), - ); + alignment: Alignment.bottomCenter, + child: _buildButtonContainer(viewModel), + ); Widget _buildButtonContainer(LandingViewModel viewModel) => Padding( - padding: const EdgeInsets.only(bottom: 50), - child: _buildContinueButtonState(viewModel), - ); + padding: const EdgeInsets.only(bottom: 50), + child: _buildContinueButtonState(viewModel), + ); Widget _buildContinueButtonState(LandingViewModel viewModel) => viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel); diff --git a/lib/ui/views/landing/screens/third_landing_screen.dart b/lib/ui/views/landing/screens/third_landing_screen.dart index eeb4717..5ea816a 100644 --- a/lib/ui/views/landing/screens/third_landing_screen.dart +++ b/lib/ui/views/landing/screens/third_landing_screen.dart @@ -16,70 +16,63 @@ class ThirdLandingScreen extends ViewModelWidget { _buildScaffoldWrapper(viewModel); Widget _buildScaffoldWrapper(LandingViewModel viewModel) => Scaffold( - backgroundColor: kcWhite, - body: _buildScaffoldPadding(viewModel), - ); - Widget _buildScaffoldPadding(LandingViewModel viewModel)=> Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildScaffold(viewModel),); + backgroundColor: kcWhite, + body: _buildScaffoldPadding(viewModel), + ); + Widget _buildScaffoldPadding(LandingViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildScaffold(viewModel), + ); Widget _buildScaffold(LandingViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildScaffoldChildren(viewModel), - ); + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildScaffoldChildren(viewModel), + ); List _buildScaffoldChildren(LandingViewModel viewModel) => - [ _buildUpperColumn(),_buildLowerColumnWrapper(viewModel)]; - - - + [_buildUpperColumn(), _buildLowerColumnWrapper(viewModel)]; Widget _buildUpperColumn() => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildUpperColumnChildren(), - ); + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); - List _buildUpperColumnChildren() => [ - verticalSpaceLarge, - _buildIconWrapper(), - verticalSpaceLarge + List _buildUpperColumnChildren() => + [verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge]; - ]; - - Widget _buildIconWrapper()=> Align(alignment: Alignment.topLeft,child: _buildIcon(),); + Widget _buildIconWrapper() => Align( + alignment: Alignment.topLeft, + child: _buildIcon(), + ); Widget _buildIcon() => SvgPicture.asset( - 'assets/icons/logo.svg', - color: kcPrimaryColor, - height: 25, - ); - - + 'assets/icons/logo.svg', + color: kcPrimaryColor, + height: 25, + ); Widget _buildLowerColumnWrapper(LandingViewModel viewModel) => Expanded( - child: _buildLowerColumn(viewModel), - ); + child: _buildLowerColumn(viewModel), + ); Widget _buildLowerColumn(LandingViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildLowerColumnChildren(viewModel), - ); + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildLowerColumnChildren(viewModel), + ); List _buildLowerColumnChildren(LandingViewModel viewModel) => [ - _buildTitle(), - verticalSpaceMedium, - _buildImageWrapper(), - verticalSpaceMedium, - _buildSafeWrapper(viewModel) - ]; + _buildTitle(), + verticalSpaceMedium, + _buildImageWrapper(), + verticalSpaceMedium, + _buildSafeWrapper(viewModel) + ]; - Widget _buildTitle() => - - Text.rich( + Widget _buildTitle() => Text.rich( TextSpan( text: 'እንግሊዝኛ\n', style: style25P600, @@ -89,44 +82,41 @@ class ThirdLandingScreen extends ViewModelWidget { style: style25P400, ), TextSpan( - text: ' እድሜ ', + text: ' ቦታ ', style: style25P600, - ), - TextSpan( text: 'ይማሩ!', style: style25P400, - ), ], ), ); - Widget _buildImageWrapper()=> Expanded(child: _buildImageClipper()); + Widget _buildImageWrapper() => Expanded(child: _buildImageClipper()); - Widget _buildImageClipper()=> ClipRRect( - borderRadius: BorderRadius.circular(25), - child: _buildImage(), - ); - - - - Widget _buildImage()=> Image.asset('assets/images/profile.png',fit: BoxFit.cover,); + Widget _buildImageClipper() => ClipRRect( + borderRadius: BorderRadius.circular(25), + child: _buildImage(), + ); + Widget _buildImage() => Image.asset( + 'assets/images/landing_3.jpg', + fit: BoxFit.cover, + ); Widget _buildSafeWrapper(LandingViewModel viewModel) => SafeArea(child: _buildContinueButtonWrapper(viewModel)); Widget _buildContinueButtonWrapper(LandingViewModel viewModel) => Align( - alignment: Alignment.bottomCenter, - child: _buildButtonContainer(viewModel), - ); + alignment: Alignment.bottomCenter, + child: _buildButtonContainer(viewModel), + ); Widget _buildButtonContainer(LandingViewModel viewModel) => Padding( - padding: const EdgeInsets.only(bottom: 50), - child: _buildContinueButtonState(viewModel), - ); + padding: const EdgeInsets.only(bottom: 50), + child: _buildContinueButtonState(viewModel), + ); Widget _buildContinueButtonState(LandingViewModel viewModel) => viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel); diff --git a/lib/ui/views/language/language_view.dart b/lib/ui/views/language/language_view.dart index 4449508..c37a1b7 100644 --- a/lib/ui/views/language/language_view.dart +++ b/lib/ui/views/language/language_view.dart @@ -1,7 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import '../../common/app_colors.dart'; +import '../../common/translations/locale_keys.g.dart'; import '../../common/ui_helpers.dart'; import '../../widgets/custom_small_radio_button.dart'; import '../../widgets/small_app_bar.dart'; @@ -106,16 +108,16 @@ class LanguageView extends StackedView { Widget _buildAppbar(LanguageViewModel viewModel) => SmallAppBar( showBackButton: true, onPop: viewModel.pop, - title: 'Language Preference', + title:LocaleKeys.language_preference.tr() , ); Widget _buildTitle() => Text( - 'Choose your language', + LocaleKeys.choose_your_language.tr(), style: style25DG600, ); Widget _buildSubtitle() => Text( - 'You can switch languages anytime', + LocaleKeys.switch_language_anytime.tr() , style: style14MG400, ); diff --git a/lib/ui/views/learn_module/learn_module_view.dart b/lib/ui/views/learn_module/learn_module_view.dart index e80592c..d78175f 100644 --- a/lib/ui/views/learn_module/learn_module_view.dart +++ b/lib/ui/views/learn_module/learn_module_view.dart @@ -122,7 +122,6 @@ class LearnModuleView extends StackedView { physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildTile( module: viewModel.modules[index], - onLockTap: () async => await viewModel.navigateToLearnSubscription(), onPracticeTap: () async => await viewModel.navigateToLearnPractice( id: viewModel.modules[index].id ?? 0, module: viewModel.modules[index].name ?? ''), @@ -133,13 +132,11 @@ class LearnModuleView extends StackedView { Widget _buildTile({ required LearnModule module, - required GestureTapCallback onLockTap, required GestureTapCallback onModuleTap, required GestureTapCallback onPracticeTap, }) => LearnModuleTile( module: module, - onLockTap: onLockTap, onModuleTap: onModuleTap, onPracticeTap: onPracticeTap); } diff --git a/lib/ui/views/learn_module/learn_module_viewmodel.dart b/lib/ui/views/learn_module/learn_module_viewmodel.dart index be18d00..45a782a 100644 --- a/lib/ui/views/learn_module/learn_module_viewmodel.dart +++ b/lib/ui/views/learn_module/learn_module_viewmodel.dart @@ -27,8 +27,7 @@ class LearnModuleViewModel extends ReactiveViewModel { // Navigation void pop() => _navigationService.back(); - Future navigateToLearnSubscription() async => - await _navigationService.navigateToLearnSubscriptionView(); + Future navigateToLearnLesson(LearnModule module) async => await _navigationService.navigateToLearnLessonView(module: module); diff --git a/lib/ui/views/learn_program/learn_program_view.dart b/lib/ui/views/learn_program/learn_program_view.dart index 38f985f..774e451 100644 --- a/lib/ui/views/learn_program/learn_program_view.dart +++ b/lib/ui/views/learn_program/learn_program_view.dart @@ -89,12 +89,14 @@ class LearnProgramView extends StackedView { program: viewModel.learnPrograms[index], onTap: () async => await viewModel .navigateToLearnCourse(viewModel.learnPrograms[index].id ?? 0), + onLockTap: () async => await viewModel.navigateToLearnSubscription(), ), ); Widget _buildTile({ required LearnProgram program, required GestureTapCallback onTap, + required GestureTapCallback onLockTap, }) => - LearnProgramTile(onTap: onTap, program: program); + LearnProgramTile(onTap: onTap, program: program,onLockTap: onLockTap,); } diff --git a/lib/ui/views/learn_program/learn_program_viewmodel.dart b/lib/ui/views/learn_program/learn_program_viewmodel.dart index 0c63a3a..3227879 100644 --- a/lib/ui/views/learn_program/learn_program_viewmodel.dart +++ b/lib/ui/views/learn_program/learn_program_viewmodel.dart @@ -39,6 +39,9 @@ class LearnProgramViewModel extends ReactiveViewModel { Future navigateToLearnCourse(int id) async => _navigationService.navigateToLearnCourseView(id: id); + Future navigateToLearnSubscription() async => + await _navigationService.navigateToLearnSubscriptionView(); + // Remote api call // Learn programs diff --git a/lib/ui/views/privacy_policy/privacy_policy_view.dart b/lib/ui/views/privacy_policy/privacy_policy_view.dart index f78a9bd..c417f81 100644 --- a/lib/ui/views/privacy_policy/privacy_policy_view.dart +++ b/lib/ui/views/privacy_policy/privacy_policy_view.dart @@ -1,8 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_strings.dart'; -import 'package:yimaru_app/ui/widgets/privacy_policy_tile.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import '../../common/app_colors.dart'; import '../../common/ui_helpers.dart'; @@ -129,7 +130,7 @@ class PrivacyPolicyView extends StackedView { Widget _buildAppbar(PrivacyPolicyViewModel viewModel) => SmallAppBar( onPop: viewModel.pop, showBackButton: true, - title: 'Privacy Policy', + title: LocaleKeys.privacy_policy.tr(), ); Widget _buildContentWrapper(PrivacyPolicyViewModel viewModel) => diff --git a/lib/ui/views/profile/profile_view.dart b/lib/ui/views/profile/profile_view.dart index 12c8e31..0d3e35f 100644 --- a/lib/ui/views/profile/profile_view.dart +++ b/lib/ui/views/profile/profile_view.dart @@ -1,7 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/enmus.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/widgets/profile_card.dart'; import 'package:yimaru_app/ui/widgets/profile_image.dart'; @@ -138,7 +140,7 @@ class ProfileView extends StackedView { ); Widget _buildProfileName(ProfileViewModel viewModel) => Text( - 'Hi, ${viewModel.user?.firstName ?? ''} 👋', + '${LocaleKeys.hello.tr()}, ${viewModel.user?.firstName ?? ''} 👋', style: style25DG600, ); @@ -169,31 +171,31 @@ class ProfileView extends StackedView { ); Widget _buildProgressCard(ProfileViewModel viewModel) => ProfileCard( - title: 'My Progress', icon: Icons.stacked_bar_chart, - subtitle: 'Track your achievements and learning streak', - onTap: () async => await viewModel.navigateToProgress(), + title: LocaleKeys.my_progress.tr(), + subtitle: LocaleKeys.track_your_achievement.tr(), + // onTap: () async => await viewModel.navigateToProgress(), ); Widget _buildAccountCard(ProfileViewModel viewModel) => ProfileCard( - title: 'Account & Privacy', icon: Icons.privacy_tip_outlined, - subtitle: 'Manage setting and app preference', + subtitle: LocaleKeys.manage_settings.tr(), + title: LocaleKeys.account_and_privacy.tr(), onTap: () async => await viewModel.navigateToAccountPrivacy(), ); Widget _buildSupportCard(ProfileViewModel viewModel) => ProfileCard( - title: 'Support', icon: Icons.headphones, - subtitle: 'Get help through phone or Telegram', + title: LocaleKeys.support.tr(), + subtitle: LocaleKeys.get_help.tr(), onTap: () async => await viewModel.navigateToSupport(), ); Widget _buildLogOutButton(ProfileViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Logout', borderRadius: 12, foregroundColor: kcRed, + text: LocaleKeys.logout.tr(), backgroundColor: kcRed.withOpacity(0.25), onTap: () async => await viewModel.logout(), ); diff --git a/lib/ui/views/profile_detail/profile_detail_view.dart b/lib/ui/views/profile_detail/profile_detail_view.dart index 63dec45..ab88376 100644 --- a/lib/ui/views/profile_detail/profile_detail_view.dart +++ b/lib/ui/views/profile_detail/profile_detail_view.dart @@ -1,8 +1,10 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked/stacked_annotations.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/widgets/custom_form_label.dart'; import 'package:yimaru_app/ui/widgets/small_app_bar.dart'; @@ -160,7 +162,7 @@ class ProfileDetailView extends StackedView Widget _buildAppbar(ProfileDetailViewModel viewModel) => SmallAppBar( onPop: viewModel.pop, showBackButton: true, - title: 'Edit Profile', + title: LocaleKeys.edit_profile.tr(), ); Widget _buildColumnWrapper( @@ -270,8 +272,8 @@ class ProfileDetailView extends StackedView ]; Widget _buildFirstNameLabel() => CustomFormLabel( - label: 'First Name', style: style16DG600, + label: LocaleKeys.first_name.tr(), ); Widget _buildFirstNameFormField(ProfileDetailViewModel viewModel) => @@ -317,8 +319,8 @@ class ProfileDetailView extends StackedView ]; Widget _buildLastNameLabel() => CustomFormLabel( - label: 'Last Name', style: style16DG600, + label: LocaleKeys.last_name.tr(), ); Widget _buildLastNameFormField(ProfileDetailViewModel viewModel) => @@ -356,8 +358,8 @@ class ProfileDetailView extends StackedView ]; Widget _buildGenderLabel() => CustomFormLabel( - label: 'Gender', style: style16DG600, + label: LocaleKeys.gender.tr(), ); Widget _buildRadioButtonWrapper(ProfileDetailViewModel viewModel) => Row( @@ -449,8 +451,8 @@ class ProfileDetailView extends StackedView ]; Widget _buildPhoneNumberLabel() => CustomFormLabel( - label: 'Phone Number', style: style16DG600, + label: LocaleKeys.phone_number.tr(), ); Widget _buildPhoneNumberFormField(ProfileDetailViewModel viewModel) => @@ -496,8 +498,8 @@ class ProfileDetailView extends StackedView ]; Widget _buildEmailLabel() => CustomFormLabel( - label: 'Email', style: style16DG600, + label: LocaleKeys.email.tr(), ); Widget _buildEmailFormField(ProfileDetailViewModel viewModel) => @@ -522,8 +524,8 @@ class ProfileDetailView extends StackedView ); Widget _buildCountryDropdownLabel() => CustomFormLabel( - label: 'Country', style: style16DG600, + label: LocaleKeys.country.tr(), ); Widget _buildCountryDropdown(ProfileDetailViewModel viewModel) => @@ -559,8 +561,8 @@ class ProfileDetailView extends StackedView ]; Widget _buildRegionFormFieldLabel() => CustomFormLabel( - label: 'Region', style: style16DG600, + label: LocaleKeys.region.tr(), ); Widget _buildRegionFormState(ProfileDetailViewModel viewModel) => @@ -570,8 +572,8 @@ class ProfileDetailView extends StackedView Widget _buildRegionDropDown(ProfileDetailViewModel viewModel) => CustomDropdownPicker( - hint: 'Select region', icon: _buildSearchIcon(), + hint:LocaleKeys.select_region.tr(), selectedItem: viewModel.selectedRegion, items: (value, props) => viewModel.getRegions(), onChanged: (value) => @@ -582,8 +584,8 @@ class ProfileDetailView extends StackedView controller: regionController, onTap: viewModel.setRegionFocus, decoration: inputDecoration( - hint: 'Enter Your City', focus: viewModel.focusRegion, + hint:LocaleKeys.enter_your_city.tr(), filled: regionController.text.isNotEmpty), ); @@ -614,14 +616,14 @@ class ProfileDetailView extends StackedView ]; Widget _buildOccupationDropdownLabel() => CustomFormLabel( - label: 'Occupation', style: style16DG600, + label: LocaleKeys.occupation.tr(), ); Widget _buildOccupationDropdown(ProfileDetailViewModel viewModel) => CustomDropdownPicker( - hint: 'Select occupation', icon: _buildSearchIcon(), + hint:LocaleKeys.select_occupation.tr(), selectedItem: viewModel.selectedOccupation, items: (value, props) => viewModel.getOccupations(), onChanged: (value) => viewModel.setSelectedOccupation( @@ -645,9 +647,9 @@ class ProfileDetailView extends StackedView CustomElevatedButton( height: 55, borderRadius: 12, - text: 'Save Changes', foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, + text: LocaleKeys.save_changes.tr(), onTap: () async => await _update(viewModel), ); @@ -659,10 +661,10 @@ class ProfileDetailView extends StackedView Widget _buildCancelButton(ProfileDetailViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Cancel', borderRadius: 12, onTap: viewModel.pop, backgroundColor: kcWhite, + text:LocaleKeys.cancel.tr(), borderColor: kcPrimaryColor, foregroundColor: kcPrimaryColor, ); diff --git a/lib/ui/views/startup/startup_view.dart b/lib/ui/views/startup/startup_view.dart index d403042..967e5fe 100644 --- a/lib/ui/views/startup/startup_view.dart +++ b/lib/ui/views/startup/startup_view.dart @@ -46,7 +46,7 @@ class StartupView extends StackedView { ]; Widget _buildBackground() => Image.asset( - 'assets/images/onboarding_1.png', + 'assets/images/loading.png', fit: BoxFit.fill, width: double.maxFinite, height: double.maxFinite, diff --git a/lib/ui/views/startup/startup_viewmodel.dart b/lib/ui/views/startup/startup_viewmodel.dart index ea0abf0..8cbfeaf 100644 --- a/lib/ui/views/startup/startup_viewmodel.dart +++ b/lib/ui/views/startup/startup_viewmodel.dart @@ -7,6 +7,7 @@ import '../../../app/app.router.dart'; import '../../../models/user.dart'; import '../../../services/api_service.dart'; import '../../../services/image_downloader_service.dart'; +import '../../../services/localization_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/enmus.dart'; @@ -15,6 +16,7 @@ class StartupViewModel extends ReactiveViewModel { final _apiService = locator(); final _statusChecker = locator(); final _navigationService = locator(); + final _localizationService = locator(); final _authenticationService = locator(); final _imageDownloaderService = locator(); @@ -29,6 +31,8 @@ class StartupViewModel extends ReactiveViewModel { // Main startup and navigation logic Future runStartupLogic() async { + await _localizationService.loadSelectedLanguage(); + final loggedIn = await _authenticationService.userLoggedIn(); final firstTimeInstall = await _authenticationService.isFirstTimeInstall(); diff --git a/lib/ui/views/support/support_view.dart b/lib/ui/views/support/support_view.dart index d7709a8..24674a4 100644 --- a/lib/ui/views/support/support_view.dart +++ b/lib/ui/views/support/support_view.dart @@ -1,5 +1,7 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/widgets/support_card.dart'; import '../../common/app_colors.dart'; @@ -51,10 +53,10 @@ class SupportView extends StackedView { ); Widget _buildAppbar(SupportViewModel viewModel) => SmallAppBar( - title: 'Need Help?', showBackButton: true, onPop: viewModel.pop, - ); + title:LocaleKeys.need_help.tr(), + ); Widget _buildContentWrapper(SupportViewModel viewModel) => Expanded(child: _buildContentColumnWrapper(viewModel)); @@ -85,16 +87,16 @@ class SupportView extends StackedView { Widget _buildCallSupport(SupportViewModel viewModel) => SupportCard( icon: Icons.call, color: kcPrimaryColor, - title: 'Call Support', - subtitle: 'Talk with our support team directly', + title:LocaleKeys.call_support.tr(), + subtitle: LocaleKeys.talk_with_support.tr(), onTap: () async => await viewModel.navigateToCallSupport(), ); Widget _buildTelegramSupport(SupportViewModel viewModel) => SupportCard( color: kcSkyBlue, icon: Icons.telegram, - title: 'Telegram Support', - subtitle: 'Chat Instantly via Telegram', + title: LocaleKeys.telegram_support.tr(), + subtitle: LocaleKeys.chat_via_telegram.tr(), onTap: () async => await viewModel.navigateToTelegramSupport(), ); } diff --git a/lib/ui/views/terms_and_conditions/terms_and_conditions_view.dart b/lib/ui/views/terms_and_conditions/terms_and_conditions_view.dart index 89357d7..16bc250 100644 --- a/lib/ui/views/terms_and_conditions/terms_and_conditions_view.dart +++ b/lib/ui/views/terms_and_conditions/terms_and_conditions_view.dart @@ -1,11 +1,12 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_strings.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import '../../common/app_colors.dart'; import '../../common/ui_helpers.dart'; -import '../../widgets/custom_elevated_button.dart'; import '../../widgets/small_app_bar.dart'; import 'terms_and_conditions_viewmodel.dart'; @@ -59,7 +60,7 @@ class TermsAndConditionsView extends StackedView { Widget _buildAppbar(TermsAndConditionsViewModel viewModel) => SmallAppBar( onPop: viewModel.pop, showBackButton: true, - title: 'Terms and Conditions', + title: LocaleKeys.terms_and_conditions.tr(), ); Widget _buildContentWrapper(TermsAndConditionsViewModel viewModel) => diff --git a/lib/ui/views/welcome/screens/first_welcome_screen.dart b/lib/ui/views/welcome/screens/first_welcome_screen.dart deleted file mode 100644 index d1d5feb..0000000 --- a/lib/ui/views/welcome/screens/first_welcome_screen.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stacked/stacked.dart'; -import 'package:yimaru_app/ui/common/app_colors.dart'; -import 'package:yimaru_app/ui/common/ui_helpers.dart'; -import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; - -import '../../../widgets/custom_circular_progress_indicator.dart'; -import '../welcome_viewmodel.dart'; - -class FirstWelcomeScreen extends ViewModelWidget { - const FirstWelcomeScreen({super.key}); - - @override - Widget build(BuildContext context, WelcomeViewModel viewModel) => - _buildScaffoldWrapper(viewModel); - - Widget _buildScaffoldWrapper(WelcomeViewModel viewModel) => Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), - ); - - Widget _buildScaffold(WelcomeViewModel viewModel) => Stack( - children: _buildScaffoldChildren(viewModel), - ); - - List _buildScaffoldChildren(WelcomeViewModel viewModel) => - [_buildBackground(), _buildColumnWrapper(), _buildSafeWrapper(viewModel)]; - - Widget _buildBackground() => Image.asset( - 'assets/images/onboarding_1.png', - fit: BoxFit.fill, - width: double.maxFinite, - height: double.maxFinite, - ); - - Widget _buildColumnWrapper() => Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildColumn(), - ); - - Widget _buildColumn() => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildUpperColumnChildren(), - ); - - List _buildUpperColumnChildren() => [ - verticalSpaceMassive, - _buildIcon(), - verticalSpaceMedium, - _buildTitle(), - ]; - - Widget _buildIcon() => SvgPicture.asset( - 'assets/icons/logo.svg', - height: 50, - ); - - Widget _buildTitle() => Text( - 'Small daily practice. Big lifelong results.', - style: style25W600, - textAlign: TextAlign.center, - ); - - Widget _buildSafeWrapper(WelcomeViewModel viewModel) => - SafeArea(child: _buildContinueButtonWrapper(viewModel)); - - Widget _buildContinueButtonWrapper(WelcomeViewModel viewModel) => Align( - alignment: Alignment.bottomCenter, - child: _buildButtonContainer(viewModel), - ); - - Widget _buildButtonContainer(WelcomeViewModel viewModel) => Padding( - padding: const EdgeInsets.only(bottom: 60, right: 50, left: 50), - child: _buildContinueButtonState(viewModel), - ); - - Widget _buildContinueButtonState(WelcomeViewModel viewModel) => - viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel); - - Widget _buildIndicator() => - const CustomCircularProgressIndicator(color: kcWhite); - - Widget _buildContinueButton(WelcomeViewModel viewModel) => - CustomElevatedButton( - height: 55, - borderRadius: 12, - text: 'Start Learning', - backgroundColor: kcWhite, - foregroundColor: kcPrimaryColor, - trailingIcon: Icons.arrow_forward, - onTap: () async => await viewModel.setFirstTimeInstall(), - ); -} diff --git a/lib/ui/views/welcome/screens/second_welcome_screen.dart b/lib/ui/views/welcome/screens/second_welcome_screen.dart deleted file mode 100644 index ab32b8e..0000000 --- a/lib/ui/views/welcome/screens/second_welcome_screen.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stacked/stacked.dart'; -import 'package:yimaru_app/ui/common/app_colors.dart'; -import 'package:yimaru_app/ui/common/ui_helpers.dart'; -import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; - -import '../../../widgets/custom_circular_progress_indicator.dart'; -import '../welcome_viewmodel.dart'; - -class SecondWelcomeScreen extends ViewModelWidget { - const SecondWelcomeScreen({super.key}); - - @override - Widget build(BuildContext context, WelcomeViewModel viewModel) => - _buildScaffoldWrapper(viewModel); - - Widget _buildScaffoldWrapper(WelcomeViewModel viewModel) => Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), - ); - - Widget _buildScaffold(WelcomeViewModel viewModel) => Stack( - children: _buildScaffoldChildren(viewModel), - ); - - List _buildScaffoldChildren(WelcomeViewModel viewModel) => - [_buildBackground(), _buildColumnWrapper(), _buildSafeWrapper(viewModel)]; - - Widget _buildBackground() => Image.asset( - 'assets/images/onboarding_2.png', - fit: BoxFit.fill, - width: double.maxFinite, - height: double.maxFinite, - ); - - Widget _buildColumnWrapper() => Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildColumn(), - ); - - Widget _buildColumn() => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildUpperColumnChildren(), - ); - - List _buildUpperColumnChildren() => [ - verticalSpaceMassive, - _buildIcon(), - verticalSpaceMedium, - _buildTitle(), - ]; - - Widget _buildIcon() => SvgPicture.asset( - 'assets/icons/logo.svg', - height: 50, - ); - - Widget _buildTitle() => const Text( - 'Start speaking, Confidence will follow.', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 30, - color: kcWhite, - fontWeight: FontWeight.w600, - ), - ); - - Widget _buildSafeWrapper(WelcomeViewModel viewModel) => - SafeArea(child: _buildContinueButtonWrapper(viewModel)); - - Widget _buildContinueButtonWrapper(WelcomeViewModel viewModel) => Align( - alignment: Alignment.bottomCenter, - child: _buildButtonContainer(viewModel), - ); - - Widget _buildButtonContainer(WelcomeViewModel viewModel) => Padding( - padding: const EdgeInsets.only(bottom: 60, right: 50, left: 50), - child: _buildContinueButtonState(viewModel), - ); - - Widget _buildContinueButtonState(WelcomeViewModel viewModel) => - viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel); - - Widget _buildIndicator() => - const CustomCircularProgressIndicator(color: kcWhite); - - Widget _buildContinueButton(WelcomeViewModel viewModel) => - CustomElevatedButton( - height: 55, - borderRadius: 12, - text: 'Start Learning', - backgroundColor: kcWhite, - foregroundColor: kcPrimaryColor, - trailingIcon: Icons.arrow_forward, - onTap: () async => await viewModel.setFirstTimeInstall(), - ); -} diff --git a/lib/ui/views/welcome/screens/third_welcome_screen.dart b/lib/ui/views/welcome/screens/third_welcome_screen.dart deleted file mode 100644 index 26dfff0..0000000 --- a/lib/ui/views/welcome/screens/third_welcome_screen.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stacked/stacked.dart'; -import 'package:yimaru_app/ui/common/app_colors.dart'; -import 'package:yimaru_app/ui/common/ui_helpers.dart'; -import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; - -import '../../../widgets/custom_circular_progress_indicator.dart'; -import '../welcome_viewmodel.dart'; - -class ThirdWelcomeScreen extends ViewModelWidget { - const ThirdWelcomeScreen({super.key}); - - @override - Widget build(BuildContext context, WelcomeViewModel viewModel) => - _buildScaffoldWrapper(viewModel); - - Widget _buildScaffoldWrapper(WelcomeViewModel viewModel) => Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), - ); - - Widget _buildScaffold(WelcomeViewModel viewModel) => Stack( - children: _buildScaffoldChildren(viewModel), - ); - - List _buildScaffoldChildren(WelcomeViewModel viewModel) => - [_buildBackground(), _buildColumnWrapper(), _buildSafeWrapper(viewModel)]; - - Widget _buildBackground() => Image.asset( - 'assets/images/onboarding_3.png', - fit: BoxFit.fill, - width: double.maxFinite, - height: double.maxFinite, - ); - - Widget _buildColumnWrapper() => Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildColumn(), - ); - - Widget _buildColumn() => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildUpperColumnChildren(), - ); - - List _buildUpperColumnChildren() => [ - verticalSpaceMassive, - _buildIcon(), - verticalSpaceMedium, - _buildTitle(), - ]; - - Widget _buildIcon() => SvgPicture.asset( - 'assets/icons/logo.svg', - height: 50, - ); - - Widget _buildTitle() => const Text( - 'Every conversation brings you closer to the life you want.', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 30, - color: kcWhite, - fontWeight: FontWeight.w600, - ), - ); - - Widget _buildSafeWrapper(WelcomeViewModel viewModel) => - SafeArea(child: _buildContinueButtonWrapper(viewModel)); - - Widget _buildContinueButtonWrapper(WelcomeViewModel viewModel) => Align( - alignment: Alignment.bottomCenter, - child: _buildButtonContainer(viewModel), - ); - - Widget _buildButtonContainer(WelcomeViewModel viewModel) => Padding( - padding: const EdgeInsets.only(bottom: 60, right: 50, left: 50), - child: _buildContinueButtonState(viewModel), - ); - - Widget _buildContinueButtonState(WelcomeViewModel viewModel) => - viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel); - - Widget _buildIndicator() => - const CustomCircularProgressIndicator(color: kcWhite); - - Widget _buildContinueButton(WelcomeViewModel viewModel) => - CustomElevatedButton( - height: 55, - borderRadius: 12, - text: 'Start Learning', - backgroundColor: kcWhite, - foregroundColor: kcPrimaryColor, - trailingIcon: Icons.arrow_forward, - onTap: () async => await viewModel.setFirstTimeInstall(), - ); -} diff --git a/lib/ui/views/welcome/welcome_view.dart b/lib/ui/views/welcome/welcome_view.dart deleted file mode 100644 index 840a1be..0000000 --- a/lib/ui/views/welcome/welcome_view.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; -import 'package:stacked/stacked.dart'; - -import 'screens/first_welcome_screen.dart'; -import 'screens/second_welcome_screen.dart'; -import 'screens/third_welcome_screen.dart'; -import 'welcome_viewmodel.dart'; - -class WelcomeView extends StackedView { - const WelcomeView({Key? key}) : super(key: key); - - @override - WelcomeViewModel viewModelBuilder(BuildContext context) => WelcomeViewModel(); - - @override - Widget builder( - BuildContext context, - WelcomeViewModel viewModel, - Widget? child, - ) => - _buildWelcomeScreens(viewModel); - - Widget _buildWelcomeScreens(WelcomeViewModel viewModel) => FlutterCarousel( - options: FlutterCarouselOptions( - autoPlay: true, - viewportFraction: 1, - showIndicator: true, - indicatorMargin: 40, - height: double.maxFinite, - slideIndicator: CircularSlideIndicator( - slideIndicatorOptions: - const SlideIndicatorOptions(indicatorRadius: 2.5), - ), - ), - items: _buildScreens(), - ); - - List _buildScreens() => - [_buildFirstWelcome(), _buildSecondWelcome(), _buildThirdWelcome()]; - - Widget _buildFirstWelcome() => const FirstWelcomeScreen(); - - Widget _buildSecondWelcome() => const SecondWelcomeScreen(); - - Widget _buildThirdWelcome() => const ThirdWelcomeScreen(); -} diff --git a/lib/ui/views/welcome/welcome_viewmodel.dart b/lib/ui/views/welcome/welcome_viewmodel.dart deleted file mode 100644 index 2e4e9e7..0000000 --- a/lib/ui/views/welcome/welcome_viewmodel.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:stacked/stacked.dart'; -import 'package:stacked_services/stacked_services.dart'; -import 'package:yimaru_app/app/app.router.dart'; -import 'package:yimaru_app/services/authentication_service.dart'; - -import '../../../app/app.locator.dart'; - -class WelcomeViewModel extends BaseViewModel { - // Dependency Injection - final _navigationService = locator(); - - final _authenticationService = locator(); - - // Navigation - Future navigateToLogin() async => - await _navigationService.replaceWithLoginView(); - - // Remote api call - - // First time install - Future setFirstTimeInstall() async { - await runBusyFuture(_setFirstTimeInstall()); - } - - Future _setFirstTimeInstall() async { - await _authenticationService.setFirstTimeInstall(false); - await navigateToLogin(); - } -} diff --git a/lib/ui/widgets/course_lesson_tile.dart b/lib/ui/widgets/course_lesson_tile.dart index e58e311..4b19bc6 100644 --- a/lib/ui/widgets/course_lesson_tile.dart +++ b/lib/ui/widgets/course_lesson_tile.dart @@ -1,14 +1,16 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/models/course_lesson.dart'; -import 'package:yimaru_app/ui/views/course_lesson/course_lesson_viewmodel.dart'; +import 'package:yimaru_app/ui/views/course_module/course_module_viewmodel.dart'; import '../common/app_colors.dart'; +import '../common/enmus.dart'; +import '../common/helper_functions.dart'; import '../common/ui_helpers.dart'; import 'custom_elevated_button.dart'; import 'mini_thumbnail.dart'; -class CourseLessonTile extends ViewModelWidget { +class CourseLessonTile extends ViewModelWidget { final CourseLesson lesson; final GestureTapCallback? onVideoTap; final GestureTapCallback? onPracticeTap; @@ -21,19 +23,16 @@ class CourseLessonTile extends ViewModelWidget { }); @override - Widget build(BuildContext context, CourseLessonViewModel viewModel) => + Widget build(BuildContext context, CourseModuleViewModel viewModel) => _buildExpansionTileCard(context: context, viewModel: viewModel); Widget _buildExpansionTileCard( {required BuildContext context, - required CourseLessonViewModel viewModel}) => + required CourseModuleViewModel viewModel}) => Container( margin: const EdgeInsets.only(bottom: 15), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), - border: Border.all( - color: kcPrimaryColor.withValues(alpha: 0.25), - ), ), child: _buildColumn(), ); @@ -44,7 +43,7 @@ class CourseLessonTile extends ViewModelWidget { ); List _buildColumnChildren() => [ - // _buildDivider(), + _buildDivider(), verticalSpaceMedium, _buildTile(), verticalSpaceMedium, @@ -52,11 +51,14 @@ class CourseLessonTile extends ViewModelWidget { verticalSpaceSmall, ]; + Widget _buildDivider() => const Divider(color: kcVeryLightGrey); + Widget _buildTile() => ListTile( minTileHeight: 0, title: _buildTitle(), subtitle: _buildSubtitle(), leading: _buildLeadingWrapper(), + trailing: _buildTrailingWrapper(), titleAlignment: ListTileTitleAlignment.top, contentPadding: const EdgeInsets.symmetric(horizontal: 15), ); @@ -67,12 +69,29 @@ class CourseLessonTile extends ViewModelWidget { ); Widget _buildSubtitle() => Text( - '${((lesson.duration ?? 0) / 50).toInt()} min', + '${((lesson.id ?? 0) / 50).toInt()} min', style: style14MG400, ); - Widget _buildLeadingWrapper() => - const MiniThumbnail(thumbnail: 'assets/images/image_1.png'); + Widget _buildLeadingWrapper() => MiniThumbnail( + thumbnail: + getReadableUrl(lesson.thumbnail ?? 'assets/images/image_1.png') ?? + 'assets/images/image_1.png'); + + Widget _buildTrailingWrapper() => + ProgressStatuses.completed != ProgressStatuses.completed + ? _buildCompletedTrailing() + : _buildPendingTrailing(); + + Widget _buildCompletedTrailing() => const Icon( + Icons.check_circle, + color: kcGreen, + ); + + Widget _buildPendingTrailing() => const Icon( + Icons.circle_outlined, + color: kcLightGrey, + ); Widget _buildActionButtonWrapper() => Container( height: 40, @@ -107,9 +126,9 @@ class CourseLessonTile extends ViewModelWidget { Widget _buildPracticeButton() => CustomElevatedButton( height: 15, - text: 'Practice', borderRadius: 12, onTap: onPracticeTap, + text: 'Practice Test', backgroundColor: kcWhite, borderColor: kcPrimaryColor, foregroundColor: kcPrimaryColor, diff --git a/lib/ui/widgets/course_module_tile_large.dart b/lib/ui/widgets/course_module_tile_large.dart new file mode 100644 index 0000000..a8c0c2f --- /dev/null +++ b/lib/ui/widgets/course_module_tile_large.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/models/course_lesson.dart'; +import 'package:yimaru_app/models/course_module.dart'; +import 'package:yimaru_app/ui/views/course_module/course_module_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/course_lesson_tile.dart'; +import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; +import 'package:yimaru_app/ui/widgets/finish_practice_sheet.dart'; + +import '../common/app_colors.dart'; +import '../common/ui_helpers.dart'; +import 'custom_elevated_button.dart'; + +class CourseModuleTileLarge extends ViewModelWidget { + final CourseModule? module; + final List lessons; + final GestureTapCallback? onContinueTap; + + const CourseModuleTileLarge( + {super.key, + this.onContinueTap, + required this.module, + required this.lessons}); + + Future _showSheet( + {required BuildContext context, + required CourseModuleViewModel viewModel}) async => + await showModalBottomSheet( + context: context, + backgroundColor: kcTransparent, + builder: (_) => _buildSheet(viewModel), + ); + + @override + Widget build(BuildContext context, CourseModuleViewModel viewModel) => + _buildExpansionTileCard(context: context, viewModel: viewModel); + + Widget _buildExpansionTileCard( + {required BuildContext context, + required CourseModuleViewModel viewModel}) => + Container( + margin: const EdgeInsets.only(bottom: 15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + border: Border.all(color: kcVeryLightGrey), + ), + child: _buildTileStack(context: context, viewModel: viewModel), + ); + + Widget _buildTileStack( + {required BuildContext context, + required CourseModuleViewModel viewModel}) => + Stack( + children: [ + _buildExpansionTile(context: context, viewModel: viewModel), + // _buildContainerShaderState() + ], + ); + + Widget _buildExpansionTile( + {required BuildContext context, + required CourseModuleViewModel viewModel}) => + ExpansionTile( + enabled: true, + title: _buildTitle(), + textColor: kcDarkGrey, + showTrailingIcon: true, + initiallyExpanded: true, + subtitle: _buildSubtitle(), + collapsedIconColor: kcDarkGrey, + collapsedTextColor: kcDarkGrey, + leading: _buildLeadingWrapper(), + backgroundColor: kcBackgroundColor, + shape: Border.all(color: kcTransparent), + expandedAlignment: Alignment.centerLeft, + collapsedBackgroundColor: kcBackgroundColor, + controlAffinity: ListTileControlAffinity.trailing, + expandedCrossAxisAlignment: CrossAxisAlignment.start, + tilePadding: const EdgeInsets.symmetric(horizontal: 15), + // enabled: status != ProgressStatuses.pending, + // showTrailingIcon: status != ProgressStatuses.pending ? true : false, + //initiallyExpanded: status == ProgressStatuses.started ? true : false, + children: + _buildExpansionTileChildren(context: context, viewModel: viewModel), + ); + + Widget _buildTitle() => Text( + module?.name ?? '', + maxLines: 1, + softWrap: false, + style: style16P600, + overflow: TextOverflow.ellipsis, + ); + + Widget _buildLeadingWrapper() => CircleAvatar( + backgroundColor: kcVeryLightGrey.withValues(alpha: 0.5), + child: _buildLeading(), + ); + + Widget _buildLeading() => const Icon( + Icons.book, + color: kcLightGrey, + ); + + Widget _buildSubtitle() => Text( + '0% completed', + style: style14DG500, + ); + + List _buildExpansionTileChildren( + {required BuildContext context, + required CourseModuleViewModel viewModel}) => + [_buildExpansionTileItem(context: context, viewModel: viewModel)]; + + Widget _buildExpansionTileItem( + {required BuildContext context, + required CourseModuleViewModel viewModel}) => + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildExpansionTileItemChildren( + context: context, viewModel: viewModel), + ); + + List _buildExpansionTileItemChildren( + {required BuildContext context, + required CourseModuleViewModel viewModel}) => + [ + _buildProgressRowWrapper(), + verticalSpaceSmall, + _buildActionButtonWrapper(context: context, viewModel: viewModel), + verticalSpaceMedium, + _buildCourseModules(viewModel) + ]; + + Widget _buildProgressRowWrapper() => Padding( + padding: const EdgeInsets.only(left: 75, right: 15), + child: _buildProgressRow(), + ); + + Widget _buildProgressRow() => Row( + mainAxisSize: MainAxisSize.min, + children: _buildProgressChildren(), + ); + + List _buildProgressChildren() => + [_buildProgressStatusWrapper(), horizontalSpaceSmall, _buildProgress()]; + + Widget _buildProgressStatusWrapper() => Expanded( + child: _buildProgressStatus(), + ); + + Widget _buildProgressStatus() => const CustomLinearProgressIndicator( + progress: 0, + activeColor: kcPrimaryColor, + backgroundColor: kcVeryLightGrey); + + Widget _buildProgress() => const Text( + '0/0', + style: TextStyle(color: kcDarkGrey), + ); + + Widget _buildActionButtonWrapper( + {required BuildContext context, + required CourseModuleViewModel viewModel}) => + Container( + width: 175, + height: 40, + margin: const EdgeInsets.only(left: 75, right: 15), + child: _buildContinueButton(viewModel), + ); + + Widget _buildContinueButton(CourseModuleViewModel viewModel) => + CustomElevatedButton( + height: 15, + borderRadius: 12, + onTap: onContinueTap, + text: 'Continue Module', + foregroundColor: kcWhite, + backgroundColor: kcPrimaryColor, + ); + + Widget _buildSheet(CourseModuleViewModel viewModel) => FinishPracticeSheet( + onTap: viewModel.pop, + ); + + Widget _buildCourseModules(CourseModuleViewModel viewModel) => + ListView.builder( + shrinkWrap: true, + itemCount: lessons.length, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => _buildCourseModuleCard( + lesson: lessons[index], + onVideoTap: () async => + await viewModel.navigateToCourseLessonDetail(lessons[index]), + onPracticeTap: () {}), + ); + + Widget _buildCourseModuleCard({ + required CourseLesson lesson, + required GestureTapCallback onVideoTap, + required GestureTapCallback onPracticeTap, + }) => + CourseLessonTile( + lesson: lesson, + onVideoTap: onVideoTap, + onPracticeTap: onPracticeTap, + ); +} diff --git a/lib/ui/widgets/course_module_tile_small.dart b/lib/ui/widgets/course_module_tile_small.dart index 514576b..054f100 100644 --- a/lib/ui/widgets/course_module_tile_small.dart +++ b/lib/ui/widgets/course_module_tile_small.dart @@ -1,19 +1,20 @@ import 'package:flutter/material.dart'; +import 'package:yimaru_app/models/course_module.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; class CourseModuleTileSmall extends StatelessWidget { - final String title; - final ProgressStatuses status; + final CourseModule? module; + final GestureTapCallback? onTap; - const CourseModuleTileSmall( - {super.key, required this.title, required this.status}); + const CourseModuleTileSmall({super.key, this.onTap, required this.module}); @override Widget build(BuildContext context) => _buildTile(); Widget _buildTile() => ListTile( + onTap: onTap, title: _buildTitle(), leading: _buildLeadingWrapper(), trailing: _buildTrailingWrapper(), @@ -27,7 +28,7 @@ class CourseModuleTileSmall extends StatelessWidget { ); Widget _buildTitle() => Text( - title, + module?.name ?? '', maxLines: 1, softWrap: false, style: style14DG600, @@ -43,9 +44,10 @@ class CourseModuleTileSmall extends StatelessWidget { color: kcLightGrey, ); - Widget _buildTrailingWrapper() => status == ProgressStatuses.completed - ? _buildCompletedTrailing() - : _buildPendingTrailing(); + Widget _buildTrailingWrapper() => + ProgressStatuses.completed != ProgressStatuses.completed + ? _buildCompletedTrailing() + : _buildPendingTrailing(); Widget _buildCompletedTrailing() => const Icon( Icons.check_circle, diff --git a/lib/ui/widgets/course_unit_tile.dart b/lib/ui/widgets/course_unit_tile.dart index 21351f3..70877d0 100644 --- a/lib/ui/widgets/course_unit_tile.dart +++ b/lib/ui/widgets/course_unit_tile.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/models/course_module.dart'; import 'package:yimaru_app/models/course_unit.dart'; -import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/widgets/course_module_tile_small.dart'; import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; import 'package:yimaru_app/ui/widgets/finish_practice_sheet.dart'; +import '../../models/course_catalog.dart'; import '../common/app_colors.dart'; import '../common/ui_helpers.dart'; import '../views/course_unit/course_unit_viewmodel.dart'; @@ -15,16 +16,18 @@ import 'custom_elevated_button.dart'; class CourseUnitTile extends ViewModelWidget { final int index; final CourseUnit unit; + final CourseCatalog catalog; + final GestureTapCallback? onLessonTap; final GestureTapCallback? onPracticeTap; - const CourseUnitTile({ - super.key, - this.onLessonTap, - this.onPracticeTap, - required this.unit, - required this.index, - }); + const CourseUnitTile( + {super.key, + this.onLessonTap, + this.onPracticeTap, + required this.unit, + required this.index, + required this.catalog}); Future _getCourseModules({ required bool expanded, @@ -35,7 +38,7 @@ class CourseUnitTile extends ViewModelWidget { // Prevent duplicate API calls if ((unit.modules?.isNotEmpty ?? false)) return; - await viewModel.getCourseUnitModules(index: index, id: unit.id ?? 0); + await viewModel.getCourseModules(index: index, id: unit.id ?? 0); } Future _showSheet( @@ -83,14 +86,12 @@ class CourseUnitTile extends ViewModelWidget { showTrailingIcon: true, initiallyExpanded: false, subtitle: _buildSubtitle(), - // key: Key(unit.id.toString()), collapsedIconColor: kcDarkGrey, collapsedTextColor: kcDarkGrey, backgroundColor: kcBackgroundColor, shape: Border.all(color: kcTransparent), expandedAlignment: Alignment.centerLeft, collapsedBackgroundColor: kcBackgroundColor, - controlAffinity: ListTileControlAffinity.trailing, expandedCrossAxisAlignment: CrossAxisAlignment.start, tilePadding: const EdgeInsets.symmetric(horizontal: 15), @@ -230,10 +231,15 @@ class CourseUnitTile extends ViewModelWidget { ); Widget _buildCourseModulesState(CourseUnitViewModel viewModel) => - viewModel.busy(StateObjects.courseModules) - ? _buildProgressIndicator() + viewModel.busy(index) + ? _buildProgressIndicatorWrapper() : _buildCourseModules(viewModel); + Widget _buildProgressIndicatorWrapper() => SizedBox( + height: 50, + width: double.maxFinite, + child: _buildProgressIndicator(), + ); Widget _buildProgressIndicator() => const Center( child: CustomCircularProgressIndicator(color: kcPrimaryColor), ); @@ -242,12 +248,15 @@ class CourseUnitTile extends ViewModelWidget { shrinkWrap: true, itemCount: unit.modules?.length, physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => - _buildCourseModuleCard(unit.modules?[index].name ?? ''), + itemBuilder: (context, index) => _buildCourseModuleCard( + module: unit.modules?[index], + onTap: () async => await viewModel.navigateToCourseModule( + catalog: catalog, module: unit.modules?[index])), ); - Widget _buildCourseModuleCard(String title) => - CourseModuleTileSmall(title: title, status: ProgressStatuses.completed); + Widget _buildCourseModuleCard( + {required CourseModule? module, required GestureTapCallback onTap}) => + CourseModuleTileSmall(onTap: onTap, module: module); // Widget _buildContainerShaderState() => status == ProgressStatuses.pending // ? _buildContainerShaderWrapper() diff --git a/lib/ui/widgets/learn_module_tile.dart b/lib/ui/widgets/learn_module_tile.dart index da011ee..e0189da 100644 --- a/lib/ui/widgets/learn_module_tile.dart +++ b/lib/ui/widgets/learn_module_tile.dart @@ -12,13 +12,11 @@ import 'custom_elevated_button.dart'; class LearnModuleTile extends ViewModelWidget { final LearnModule module; - final GestureTapCallback? onLockTap; final GestureTapCallback? onModuleTap; final GestureTapCallback? onPracticeTap; const LearnModuleTile( {super.key, - this.onLockTap, this.onModuleTap, this.onPracticeTap, required this.module}); @@ -34,15 +32,8 @@ class LearnModuleTile extends ViewModelWidget { @override Widget build(BuildContext context, LearnModuleViewModel viewModel) => - _buildExpansionTileWrapper(context: context, viewModel: viewModel); + _buildExpansionTileCard(context: context, viewModel: viewModel); - Widget _buildExpansionTileWrapper( - {required BuildContext context, - required LearnModuleViewModel viewModel}) => - GestureDetector( - onTap: !(module.access?.isAccessible ?? false) ? onLockTap : null, - child: _buildExpansionTileCard(context: context, viewModel: viewModel), - ); Widget _buildExpansionTileCard( {required BuildContext context, required LearnModuleViewModel viewModel}) => diff --git a/lib/ui/widgets/learn_program_tile.dart b/lib/ui/widgets/learn_program_tile.dart index d6b3dec..923386d 100644 --- a/lib/ui/widgets/learn_program_tile.dart +++ b/lib/ui/widgets/learn_program_tile.dart @@ -1,5 +1,7 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/widgets/progress_status.dart'; @@ -11,12 +13,20 @@ import 'custom_elevated_button.dart'; class LearnProgramTile extends ViewModelWidget { final LearnProgram program; final GestureTapCallback? onTap; + final GestureTapCallback? onLockTap; - const LearnProgramTile({super.key, this.onTap, required this.program}); + const LearnProgramTile( + {super.key, this.onTap, this.onLockTap, required this.program}); @override Widget build(BuildContext context, LearnProgramViewModel viewModel) => - _buildExpansionTileCard(viewModel); + _buildExpansionTileCardWrapper(viewModel); + + Widget _buildExpansionTileCardWrapper(LearnProgramViewModel viewModel) => + GestureDetector( + onTap: !(program.access?.isAccessible ?? false) ? onLockTap : null, + child: _buildExpansionTileCard(viewModel), + ); Widget _buildExpansionTileCard(LearnProgramViewModel viewModel) => Container( margin: const EdgeInsets.only(bottom: 15), @@ -99,8 +109,8 @@ class LearnProgramTile extends ViewModelWidget { Widget _buildProgressStatus() => ProgressStatus( color: kcPrimaryColor, status: (program.access?.isCompleted ?? false) - ? 'Completed' - : 'In Progress', + ?LocaleKeys.completed.tr() + : LocaleKeys.in_progress.tr(), ); Widget _buildContent() => Text( @@ -123,7 +133,7 @@ class LearnProgramTile extends ViewModelWidget { foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, text: program.access?.progressPercent == 0 - ? 'Start Learning' - : 'Continue Learning', + ? LocaleKeys.start_learning.tr() + :LocaleKeys.continue_learning.tr() , ); } diff --git a/lib/ui/widgets/profile_app_bar.dart b/lib/ui/widgets/profile_app_bar.dart index 15e2d92..3ab77b2 100644 --- a/lib/ui/widgets/profile_app_bar.dart +++ b/lib/ui/widgets/profile_app_bar.dart @@ -1,10 +1,12 @@ import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:yimaru_app/ui/common/app_constants.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import '../common/app_colors.dart'; +import '../common/translations/locale_keys.g.dart'; class ProfileAppBar extends StatelessWidget { final String? name; @@ -72,7 +74,7 @@ class ProfileAppBar extends StatelessWidget { [_buildGreetingTitle(), _buildSubtitle()]; Widget _buildGreetingTitle() => Text.rich( - TextSpan(text: 'Hello,', style: style14DG600, children: [ + TextSpan(text: '${LocaleKeys.hello.tr()},', style: style14DG600, children: [ TextSpan( text: ' $name!', style: style14P600, @@ -81,7 +83,7 @@ class ProfileAppBar extends StatelessWidget { ); Widget _buildSubtitle() => Text( - 'Ready to keep learning English today?', + LocaleKeys.ready_to_learn.tr(), textAlign: TextAlign.center, style: style14DG400, ); diff --git a/lib/ui/widgets/selectable_course_practice_question.dart b/lib/ui/widgets/selectable_course_practice_question.dart deleted file mode 100644 index 2775ed9..0000000 --- a/lib/ui/widgets/selectable_course_practice_question.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; -import 'package:yimaru_app/ui/common/enmus.dart'; -import 'package:yimaru_app/ui/widgets/custom_circular_progress_indicator.dart'; - -import '../common/app_colors.dart'; -import '../common/ui_helpers.dart'; -import '../views/course_practice_question/course_practice_question_viewmodel.dart'; -import 'custom_elevated_button.dart'; -import 'custom_small_radio_button.dart'; - -class SelectableCoursePracticeQuestion - extends ViewModelWidget { - final int index; - - const SelectableCoursePracticeQuestion({super.key, required this.index}); - - @override - Widget build( - BuildContext context, CoursePracticeQuestionViewModel viewModel) => - _buildBodyScroller(viewModel); - - Widget _buildBodyScroller(CoursePracticeQuestionViewModel viewModel) => - SingleChildScrollView( - child: _buildBody(viewModel), - ); - - Widget _buildBody(CoursePracticeQuestionViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildBodyChildren(viewModel), - ); - - List _buildBodyChildren(CoursePracticeQuestionViewModel viewModel) => - [ - verticalSpaceMedium, - _buildTitle(viewModel), - verticalSpaceMedium, - _buildAnswers(viewModel), - _buildContinueButtonWrapper(viewModel) - ]; - - Widget _buildTitle(CoursePracticeQuestionViewModel viewModel) => Text( - 'Q${index + 1}. ${viewModel.currentQuestion?.questionText} ', - style: style16DG600, - ); - - Widget _buildAnswers(CoursePracticeQuestionViewModel viewModel) => - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: viewModel.currentQuestion?.options?.length, - itemBuilder: (context, inner) => _buildAnswer( - title: viewModel.currentQuestion?.options?[inner].optionText ?? '', - selected: viewModel.isSelectedAnswer( - question: index + 1, - answer: - viewModel.currentQuestion?.options?[inner].optionText ?? ''), - onTap: () => viewModel.setSelectedAnswer( - question: index + 1, - option: viewModel.currentQuestion?.options?[inner]), - ), - ); - - Widget _buildAnswer( - {required String title, - required bool selected, - required GestureTapCallback onTap}) => - CustomSmallRadioButton( - title: title, - onTap: onTap, - selected: selected, - ); - - Widget _buildContinueButtonWrapper( - CoursePracticeQuestionViewModel viewModel) => - Padding( - padding: const EdgeInsets.only(bottom: 50), - child: _buildContinueButtonState(viewModel), - ); - - Widget _buildContinueButtonState(CoursePracticeQuestionViewModel viewModel) => - viewModel.busy(StateObjects.coursePracticeQuestion) - ? _buildProgressIndicator() - : _buildContinueButton(viewModel); - - Widget _buildProgressIndicator() => - const CustomCircularProgressIndicator(color: kcPrimaryColor); - - Widget _buildContinueButton(CoursePracticeQuestionViewModel viewModel) => - CustomElevatedButton( - height: 55, - borderRadius: 12, - foregroundColor: kcWhite, - text: viewModel.currentQuestionIndex == - viewModel.coursePracticeQuestions.length - 1 - ? 'Finish' - : 'Continue', - backgroundColor: - viewModel.selectedAnswers.containsKey((index + 1).toString()) - ? kcPrimaryColor - : kcPrimaryColor.withOpacity(0.1), - onTap: viewModel.selectedAnswers.containsKey((index + 1).toString()) - ? () async => await viewModel.nextQuestion(viewModel - .coursePracticeQuestions[ - index + 1 < viewModel.coursePracticeQuestions.length - ? index + 1 - : index] - .id ?? - 0) - : null, - ); -} diff --git a/lib/ui/widgets/view_profile_button.dart b/lib/ui/widgets/view_profile_button.dart index fe45130..4ce14eb 100644 --- a/lib/ui/widgets/view_profile_button.dart +++ b/lib/ui/widgets/view_profile_button.dart @@ -1,5 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; class ViewProfileButton extends StatelessWidget { final GestureTapCallback? onTap; @@ -21,10 +24,9 @@ class ViewProfileButton extends StatelessWidget { List _buildButtonRowChildren() => [_buildButtonText(), const SizedBox(width: 10), _buildButtonIcon()]; - Widget _buildButtonText() => const Text( - 'View Profile', - style: TextStyle( - color: kcPrimaryColor, fontSize: 16, fontWeight: FontWeight.w900), + Widget _buildButtonText() => Text( + LocaleKeys.view_profile.tr(), + style: style16P900, ); Widget _buildButtonIcon() => const Icon( diff --git a/lib/ui/widgets/writing_course_practice_question.dart b/lib/ui/widgets/writing_course_practice_question.dart deleted file mode 100644 index ba3aa08..0000000 --- a/lib/ui/widgets/writing_course_practice_question.dart +++ /dev/null @@ -1,124 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; - -import '../common/app_colors.dart'; -import '../common/enmus.dart'; -import '../common/ui_helpers.dart'; -import '../views/course_practice_question/course_practice_question_view.form.dart'; -import '../views/course_practice_question/course_practice_question_viewmodel.dart'; -import 'custom_circular_progress_indicator.dart'; -import 'custom_elevated_button.dart'; - -class WritingCoursePracticeQuestion - extends ViewModelWidget { - final int index; - final TextEditingController answerController; - - const WritingCoursePracticeQuestion( - {super.key, required this.index, required this.answerController}); - - @override - Widget build( - BuildContext context, CoursePracticeQuestionViewModel viewModel) => - _buildBody(viewModel); - - Widget _buildBody(CoursePracticeQuestionViewModel viewModel) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildBodyChildren(viewModel), - ); - - List _buildBodyChildren(CoursePracticeQuestionViewModel viewModel) => - [_buildColumnScroller(viewModel), _buildContinueButtonWrapper(viewModel)]; - - Widget _buildColumnScroller(CoursePracticeQuestionViewModel viewModel) => - SingleChildScrollView( - child: _buildUpperColumn(viewModel), - ); - - Widget _buildUpperColumn(CoursePracticeQuestionViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildUpperColumnChildren(viewModel), - ); - - List _buildUpperColumnChildren( - CoursePracticeQuestionViewModel viewModel) => - [ - verticalSpaceMedium, - _buildTitle(viewModel), - verticalSpaceLarge, - _buildQuestionFormField(viewModel), - if (viewModel.hasAnswerValidationMessage && viewModel.focusAnswer) - verticalSpaceTiny, - if (viewModel.hasAnswerValidationMessage && viewModel.focusAnswer) - _buildQuestionValidatorWrapper(viewModel), - ]; - - Widget _buildTitle(CoursePracticeQuestionViewModel viewModel) => Text( - 'Q${index + 1}. ${viewModel.coursePracticeQuestions[index].questionText} ', - style: style16DG600, - ); - - Widget _buildQuestionFormField(CoursePracticeQuestionViewModel viewModel) => - TextFormField( - maxLines: 3, - controller: answerController, - onTap: viewModel.setAnswerFocus, - decoration: inputDecoration( - hint: 'Enter Your Answer', - focus: viewModel.focusAnswer, - filled: answerController.text.isNotEmpty), - ); - - Widget _buildQuestionValidatorWrapper( - CoursePracticeQuestionViewModel viewModel) => - viewModel.hasAnswerValidationMessage - ? _buildQuestionValidator(viewModel) - : Container(); - - Widget _buildQuestionValidator(CoursePracticeQuestionViewModel viewModel) => - Text( - viewModel.answerValidationMessage!, - style: const TextStyle( - fontSize: 12, - color: Colors.red, - fontWeight: FontWeight.w700, - ), - ); - - Widget _buildContinueButtonWrapper( - CoursePracticeQuestionViewModel viewModel) => - Padding( - padding: const EdgeInsets.only(bottom: 50), - child: _buildContinueButtonState(viewModel), - ); - - Widget _buildContinueButtonState(CoursePracticeQuestionViewModel viewModel) => - viewModel.busy(StateObjects.coursePracticeQuestion) - ? _buildProgressIndicator() - : _buildContinueButton(viewModel); - - Widget _buildProgressIndicator() => - const CustomCircularProgressIndicator(color: kcPrimaryColor); - - Widget _buildContinueButton(CoursePracticeQuestionViewModel viewModel) => - CustomElevatedButton( - height: 55, - borderRadius: 12, - foregroundColor: kcWhite, - backgroundColor: answerController.text.isNotEmpty - ? kcPrimaryColor - : kcPrimaryColor.withOpacity(0.1), - onTap: answerController.text.isNotEmpty - ? () async => await viewModel.nextQuestion( - index + 1 < viewModel.coursePracticeQuestions.length - ? index + 1 - : index) - : null, - text: viewModel.currentQuestionIndex == - viewModel.coursePracticeQuestions.length - 1 - ? 'Finish' - : 'Continue', - ); -} diff --git a/pubspec.yaml b/pubspec.yaml index e8b045c..6b23de3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: yimaru_app -version: 0.1.17+19 +version: 0.1.18+20 publish_to: 'none' description: A new Flutter project. diff --git a/test/viewmodels/course_lesson_viewmodel_test.dart b/test/viewmodels/course_module_viewmodel_test.dart similarity index 83% rename from test/viewmodels/course_lesson_viewmodel_test.dart rename to test/viewmodels/course_module_viewmodel_test.dart index 5fb8e7b..f56d043 100644 --- a/test/viewmodels/course_lesson_viewmodel_test.dart +++ b/test/viewmodels/course_module_viewmodel_test.dart @@ -4,7 +4,7 @@ import 'package:yimaru_app/app/app.locator.dart'; import '../helpers/test_helpers.dart'; void main() { - group('CourseLessonViewModel Tests -', () { + group('CourseModuleViewModel Tests -', () { setUp(() => registerServices()); tearDown(() => locator.reset()); }); diff --git a/test/viewmodels/course_practice_question_viewmodel_test.dart b/test/viewmodels/course_practice_question_viewmodel_test.dart deleted file mode 100644 index 8400e76..0000000 --- a/test/viewmodels/course_practice_question_viewmodel_test.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:yimaru_app/app/app.locator.dart'; - -import '../helpers/test_helpers.dart'; - -void main() { - group('CoursePracticeQuestionViewModel Tests -', () { - setUp(() => registerServices()); - tearDown(() => locator.reset()); - }); -} diff --git a/test/viewmodels/course_practice_viewmodel_test.dart b/test/viewmodels/course_practice_viewmodel_test.dart deleted file mode 100644 index 7068e34..0000000 --- a/test/viewmodels/course_practice_viewmodel_test.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:yimaru_app/app/app.locator.dart'; - -import '../helpers/test_helpers.dart'; - -void main() { - group('CoursePracticeViewModel Tests -', () { - setUp(() => registerServices()); - tearDown(() => locator.reset()); - }); -} diff --git a/test/viewmodels/welcome_viewmodel_test.dart b/test/viewmodels/welcome_viewmodel_test.dart deleted file mode 100644 index 4b59f78..0000000 --- a/test/viewmodels/welcome_viewmodel_test.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:yimaru_app/app/app.locator.dart'; - -import '../helpers/test_helpers.dart'; - -void main() { - group('WelcomeViewModel Tests -', () { - setUp(() => registerServices()); - tearDown(() => locator.reset()); - }); -}