fix: Apply fix on onboarding issue

This commit is contained in:
BisratHailu 2026-05-26 16:16:12 +03:00
parent 4cf063dce0
commit f957d36e14
95 changed files with 1314 additions and 1186 deletions

View File

@ -2,7 +2,7 @@
"loading": "በመጫን ላይ", "loading": "በመጫን ላይ",
"welcome_back": "እንኳን በደህና ተመለሱ", "welcome_back": "እንኳን በደህና ተመለሱ",
"checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ", "checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ",
"dont_have_account": "መለያ የለዎትም? ይመዝገቡ", "dont_have_account": "መለያ የለዎትም?",
"email": "ኢሜይል", "email": "ኢሜይል",
"password": "የይለፍ ቃል", "password": "የይለፍ ቃል",
"forgot_password": "የይለፍ ቃል ረሱ?", "forgot_password": "የይለፍ ቃል ረሱ?",
@ -43,14 +43,14 @@
"continue_learning": "መማርን ይቀጥሉ", "continue_learning": "መማርን ይቀጥሉ",
"start_learning": "ትምህርትን ይጀምሩ", "start_learning": "ትምህርትን ይጀምሩ",
"completed": "ተጠናቋል", "completed": "ተጠናቋል",
"take_practice": " ልምምድ ያድርጉ", "take_practice": "ልምምድ ያድርጉ",
"your_current_level": "የአሁኑ ደረጃዎ", "your_current_level": "የአሁኑ ደረጃዎ",
"overall_progress": "አጠቃላይ እድገት", "overall_progress": "አጠቃላይ እድገት",
"great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው", "great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው",
"view_module": "ሞጁሉን ይመልከቱ", "view_module": "ሞጁሉን ይመልከቱ",
"progress": "እድገት", "progress": "እድገት",
"keep_going": " ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ", "keep_going": "ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ",
"lessons_in_module": " በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ", "lessons_in_module": "በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ",
"practice": "ልምምድ", "practice": "ልምምድ",
"start": "ጀምር", "start": "ጀምር",
"in_progress": "በሂደት ላይ", "in_progress": "በሂደት ላይ",
@ -134,7 +134,7 @@
"ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።", "ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።",
"begin_level_practice": "የደረጃ ልምምድን ጀምር", "begin_level_practice": "የደረጃ ልምምድን ጀምር",
"lets_practice_course": "የኮርሱን ልምምድ እንለማመድ", "lets_practice_course": "የኮርሱን ልምምድ እንለማመድ",
"lets_quick_practice": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!", "lets_quick_review": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!",
"speaking": "እየተናገረ ነው", "speaking": "እየተናገረ ነው",
"you_have_finished_practice": "ልምምድህን አጠናቀቅህ", "you_have_finished_practice": "ልምምድህን አጠናቀቅህ",
"view_results": "ውጤቶቼን እይ", "view_results": "ውጤቶቼን እይ",
@ -153,15 +153,36 @@
"what_should_we_call_you": "ምን ብለን እንጠራህ?", "what_should_we_call_you": "ምን ብለን እንጠራህ?",
"name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።", "name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።",
"choose_your_gender": "ጾታህን ምረጥ", "choose_your_gender": "ጾታህን ምረጥ",
"gender_for_personalization": "በጾታህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።" "gender_for_personalization": "በጾታህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"age_range": "በየትኛው የእድሜ ክልል ውስጥ ነህ?",
"age_for_personalization": "በእድሜህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"educational_background": "አሁን ያለህ የትምህርት ደረጃ ምንድን ነው?",
"education_for_personalization": "ይህ ትምህርቶችን ከልምድህ ጋር እንዲስማሙ ለማድረግ ይረዳናል።",
"your_occupation": "ስራህ ምንድን ነው?",
"occupation_for_personalization": "በስራህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"location": "ከየት ነህ?",
"select_country_region": "አገርህን እና ክልልህን ከተቆልቋይ ዝርዝሩ ምረጥ",
"select_country": "አገር ምረጥ",
"learning_goal": "የመማር ዓላማህን ምረጥ",
"language_goal": "እንግሊዝኛህን ለማሻሻል ዋና ዓላማህ ምንድን ነው?",
"your_goal": "ዓላማህ የመማር ጉዞህን እንዲስማማ ለማድረግ ይረዳናል።",
"write_your_goal": "ዓላማህን ጻፍ…",
"challenge_you_face": "What challenge do you face most with English?",
"evey_one_has_strugle": "ሁሉም ሰው ችግሮች አሉት፣ የአንተን እንጀምር እንፍታ",
"write_your_challenge": "ችግርህን ጻፍ…",
"topic_interest": "በጣም የሚስቡህ ርዕሶች የትኞቹ ናቸው?",
"favourite_topic": "የምትወዳቸው ርዕሶች አስደሳች እና ከሕይወትህ ጋር የተዛመዱ ትምህርቶችን ለመፍጠር ይረዱናል።",
"your_interest": "ፍላጎትህን ጻፍ…",
"want_quick_assessment": "የእንግሊዝኛ ደረጃህን ለማወቅ ፈጣን ግምገማ ትፈልጋለህ?",
"answer_quick_questions": "የእንግሊዝኛ ችሎታህን ለመረዳት ጥቂት ፈጣን ጥያቄዎችን መልስ።",
"skip": "ዝለል",
"finish_level": "ደረጃውን አጠናቅቅ",
"likely_speaker": "አንተ ምናልባት ተናጋሪ ነህ",
"great_job": "በጣም ጥሩ ስራ! ለመሻሻል ቀጣዩ ደረጃህ ይኸው ነው።",
"lets_start_practice": "ልምምድህን እንጀምር",
"welcome_abroad": "እንኳን ደህና መጣህ",
"ready_to_explore": "የግል ትምህርቶችህን ለማሰስ ዝግጁ ነህ።",
"finish": "አጠናቅቅ"

View File

@ -2,7 +2,7 @@
{"loading": "Loading", {"loading": "Loading",
"welcome_back": "Welcome back", "welcome_back": "Welcome back",
"checking_user_info": "Checking user info", "checking_user_info": "Checking user info",
"dont_have_account": "Don't have an account? Register", "dont_have_account": "Don't have an account?",
"email": "Email", "email": "Email",
"password": "Password", "password": "Password",
"forgot_password": "Forgot password?", "forgot_password": "Forgot password?",
@ -153,7 +153,34 @@
"what_should_we_call_you": "What should we call you?", "what_should_we_call_you": "What should we call you?",
"name_for_personalization": "Well use your name to personalize your learning journey.", "name_for_personalization": "Well use your name to personalize your learning journey.",
"choose_your_gender": "Choose your gender?", "choose_your_gender": "Choose your gender?",
"gender_for_personalization": "Well personalize your learning experience based on your gender." "gender_for_personalization": "Well personalize your learning experience based on your gender.",
"age_range": "Which age range are you in?",
"age_for_personalization": "Well personalize your learning experience based on your age.",
"educational_background": "Whats your current educational level?",
"education_for_personalization": "This helps us tailor your lessons to your experience.",
"your_occupation": "Whats your occupation?",
"occupation_for_personalization": "Well personalize your learning experience based on your occupation.",
"location": "Where are you from?",
"select_country_region": "Select your country and region from the dropdown",
"select_country": "Select country",
"learning_goal": "Choose your learning goal.",
"language_goal": "Whats your main goal for improving your English?",
"your_goal": "Your goal helps us tailor your learning journey.",
"write_your_goal": "Write your goal…",
"challenge_you_face": "What challenge do you face most with English?",
"evey_one_has_strugle": "Everyone has struggles, lets start fixing yours",
"write_your_challenge": "Write your challenge…",
"topic_interest": "Which topics interest you most?",
"favourite_topic": "Your favorite topics help us create fun, relatable lessons.",
"your_interest": "Write your interest…",
"want_quick_assessment": "Want a quick assessment to know your English level?",
"answer_quick_questions": "Answer a few quick questions to help us understand your English proficiency.",
"skip": "Skip",
"finish_level": "Finish Level",
"likely_speaker": "Youre likely speaker of",
"great_job": "Great Job! Heres your next step to keep improving.",
"lets_start_practice": "Let's start your practice",
"welcome_abroad": "Welcome aboard",
"ready_to_explore": "Youre ready to explore your personalized lessons.",
"finish": "Finish"
} }

View File

@ -44,7 +44,6 @@ import 'package:yimaru_app/services/audio_player_service.dart';
import 'package:yimaru_app/services/voice_recorder_service.dart'; import 'package:yimaru_app/services/voice_recorder_service.dart';
import 'package:yimaru_app/services/in_app_update_service.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_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart';
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart'; import 'package:yimaru_app/ui/views/assessment/assessment_view.dart';
import 'package:yimaru_app/services/vimeo_service.dart'; import 'package:yimaru_app/services/vimeo_service.dart';
import 'package:yimaru_app/services/url_launcher_service.dart'; import 'package:yimaru_app/services/url_launcher_service.dart';
@ -57,6 +56,8 @@ import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart';
import 'package:yimaru_app/services/localization_service.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/landing/landing_view.dart';
import 'package:yimaru_app/ui/views/course_module/course_module_view.dart'; import 'package:yimaru_app/ui/views/course_module/course_module_view.dart';
import 'package:yimaru_app/services/onboarding_service.dart';
import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart';
// @stacked-import // @stacked-import
@StackedApp( @StackedApp(
@ -88,7 +89,6 @@ import 'package:yimaru_app/ui/views/course_module/course_module_view.dart';
MaterialRoute(page: DuolingoView), MaterialRoute(page: DuolingoView),
MaterialRoute(page: CourseView), MaterialRoute(page: CourseView),
MaterialRoute(page: LearnProgramView), MaterialRoute(page: LearnProgramView),
MaterialRoute(page: LearnCourseView),
MaterialRoute(page: AssessmentView), MaterialRoute(page: AssessmentView),
MaterialRoute(page: LearnSubscriptionView), MaterialRoute(page: LearnSubscriptionView),
MaterialRoute(page: ArifPayView), MaterialRoute(page: ArifPayView),
@ -123,6 +123,7 @@ import 'package:yimaru_app/ui/views/course_module/course_module_view.dart';
LazySingleton(classType: PhoneCallerService), LazySingleton(classType: PhoneCallerService),
LazySingleton(classType: LearnService), LazySingleton(classType: LearnService),
LazySingleton(classType: LocalizationService), LazySingleton(classType: LocalizationService),
LazySingleton(classType: OnboardingService),
// @stacked-service // @stacked-service
], ],
bottomsheets: [ bottomsheets: [

View File

@ -24,6 +24,7 @@ import '../services/in_app_update_service.dart';
import '../services/learn_service.dart'; import '../services/learn_service.dart';
import '../services/localization_service.dart'; import '../services/localization_service.dart';
import '../services/notification_service.dart'; import '../services/notification_service.dart';
import '../services/onboarding_service.dart';
import '../services/permission_handler_service.dart'; import '../services/permission_handler_service.dart';
import '../services/phone_caller_service.dart'; import '../services/phone_caller_service.dart';
import '../services/secure_storage_service.dart'; import '../services/secure_storage_service.dart';
@ -65,4 +66,5 @@ Future<void> setupLocator(
locator.registerLazySingleton(() => PhoneCallerService()); locator.registerLazySingleton(() => PhoneCallerService());
locator.registerLazySingleton(() => LearnService()); locator.registerLazySingleton(() => LearnService());
locator.registerLazySingleton(() => LocalizationService()); locator.registerLazySingleton(() => LocalizationService());
locator.registerLazySingleton(() => OnboardingService());
} }

View File

@ -6,8 +6,8 @@
// ************************************************************************** // **************************************************************************
// ignore_for_file: no_leading_underscores_for_library_prefixes // 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';
import 'package:flutter/material.dart' as _i37;
import 'package:stacked/stacked.dart' as _i1; import 'package:stacked/stacked.dart' as _i1;
import 'package:stacked_services/stacked_services.dart' as _i46; import 'package:stacked_services/stacked_services.dart' as _i46;
import 'package:yimaru_app/models/course.dart' as _i42; import 'package:yimaru_app/models/course.dart' as _i42;
@ -20,30 +20,30 @@ 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/common/enmus.dart' as _i41;
import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart' import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart'
as _i9; as _i9;
import 'package:yimaru_app/ui/views/arif_pay/arif_pay_view.dart' as _i32; import 'package:yimaru_app/ui/views/arif_pay/arif_pay_view.dart' as _i31;
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i30; import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i29;
import 'package:yimaru_app/ui/views/call_support/call_support_view.dart' import 'package:yimaru_app/ui/views/call_support/call_support_view.dart'
as _i12; as _i12;
import 'package:yimaru_app/ui/views/course/course_view.dart' as _i27; import 'package:yimaru_app/ui/views/course/course_view.dart' as _i27;
import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart' import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart'
as _i33; as _i32;
import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart' import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart'
as _i25; as _i25;
import 'package:yimaru_app/ui/views/course_module/course_module_view.dart' import 'package:yimaru_app/ui/views/course_module/course_module_view.dart'
as _i36; as _i35;
import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart' import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart'
as _i23; as _i23;
import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart' as _i34; import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart' as _i33;
import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7; import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7;
import 'package:yimaru_app/ui/views/duolingo/duolingo_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/failure/failure_view.dart' as _i24;
import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart' import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart'
as _i20; as _i20;
import 'package:yimaru_app/ui/views/home/home_view.dart' as _i2; import 'package:yimaru_app/ui/views/home/home_view.dart' as _i2;
import 'package:yimaru_app/ui/views/landing/landing_view.dart' as _i35; import 'package:yimaru_app/ui/views/landing/landing_view.dart' as _i34;
import 'package:yimaru_app/ui/views/language/language_view.dart' as _i13; import 'package:yimaru_app/ui/views/language/language_view.dart' as _i13;
import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart' import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart'
as _i29; as _i36;
import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart' import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart'
as _i19; as _i19;
import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart' import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart'
@ -55,7 +55,7 @@ import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart'
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart' import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'
as _i28; as _i28;
import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_view.dart' import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_view.dart'
as _i31; as _i30;
import 'package:yimaru_app/ui/views/login/login_view.dart' as _i17; 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/onboarding/onboarding_view.dart' as _i3;
import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart' import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart'
@ -127,8 +127,6 @@ class Routes {
static const learnProgramView = '/learn-program-view'; static const learnProgramView = '/learn-program-view';
static const learnCourseView = '/learn-course-view';
static const assessmentView = '/assessment-view'; static const assessmentView = '/assessment-view';
static const learnSubscriptionView = '/learn-subscription-view'; static const learnSubscriptionView = '/learn-subscription-view';
@ -143,6 +141,7 @@ class Routes {
static const courseModuleView = '/course-module-view'; static const courseModuleView = '/course-module-view';
static const learnCourseView = '/learn-course-view';
static const all = <String>{ static const all = <String>{
homeView, homeView,
@ -172,7 +171,6 @@ class Routes {
duolingoView, duolingoView,
courseView, courseView,
learnProgramView, learnProgramView,
learnCourseView,
assessmentView, assessmentView,
learnSubscriptionView, learnSubscriptionView,
arifPayView, arifPayView,
@ -180,6 +178,7 @@ class Routes {
courseUnitView, courseUnitView,
landingView, landingView,
courseModuleView, courseModuleView,
learnCourseView,
}; };
} }
@ -293,41 +292,37 @@ class StackedRouter extends _i1.RouterBase {
Routes.learnProgramView, Routes.learnProgramView,
page: _i28.LearnProgramView, page: _i28.LearnProgramView,
), ),
_i1.RouteDef(
Routes.learnCourseView,
page: _i29.LearnCourseView,
),
_i1.RouteDef( _i1.RouteDef(
Routes.assessmentView, Routes.assessmentView,
page: _i30.AssessmentView, page: _i29.AssessmentView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.learnSubscriptionView, Routes.learnSubscriptionView,
page: _i31.LearnSubscriptionView, page: _i30.LearnSubscriptionView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.arifPayView, Routes.arifPayView,
page: _i32.ArifPayView, page: _i31.ArifPayView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.courseCatalogView, Routes.courseCatalogView,
page: _i33.CourseCatalogView, page: _i32.CourseCatalogView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.courseUnitView, Routes.courseUnitView,
page: _i34.CourseUnitView, page: _i33.CourseUnitView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.landingView, Routes.landingView,
page: _i35.LandingView, page: _i34.LandingView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.courseModuleView, Routes.courseModuleView,
page: _i36.CourseModuleView, page: _i35.CourseModuleView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.learnCourseView, Routes.learnCourseView,
page: _i29.LearnCourseView, page: _i36.LearnCourseView,
), ),
]; ];
@ -577,72 +572,72 @@ class StackedRouter extends _i1.RouterBase {
settings: data, settings: data,
); );
}, },
_i29.LearnCourseView: (data) { _i29.AssessmentView: (data) {
final args = data.getArgs<LearnCourseViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i29.LearnCourseView(key: args.key, id: args.id),
settings: data,
);
},
_i30.AssessmentView: (data) {
final args = data.getArgs<AssessmentViewArguments>(nullOk: false); final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i30.AssessmentView(key: args.key, data: args.data), _i29.AssessmentView(key: args.key, data: args.data),
settings: data, settings: data,
); );
}, },
_i31.LearnSubscriptionView: (data) { _i30.LearnSubscriptionView: (data) {
final args = data.getArgs<LearnSubscriptionViewArguments>( final args = data.getArgs<LearnSubscriptionViewArguments>(
orElse: () => const LearnSubscriptionViewArguments(), orElse: () => const LearnSubscriptionViewArguments(),
); );
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i31.LearnSubscriptionView(key: args.key), builder: (context) => _i30.LearnSubscriptionView(key: args.key),
settings: data, settings: data,
); );
}, },
_i32.ArifPayView: (data) { _i31.ArifPayView: (data) {
final args = data.getArgs<ArifPayViewArguments>(nullOk: false); final args = data.getArgs<ArifPayViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i32.ArifPayView(key: args.key, phone: args.phone), _i31.ArifPayView(key: args.key, phone: args.phone),
settings: data, settings: data,
); );
}, },
_i33.CourseCatalogView: (data) { _i32.CourseCatalogView: (data) {
final args = data.getArgs<CourseCatalogViewArguments>( final args = data.getArgs<CourseCatalogViewArguments>(
orElse: () => const CourseCatalogViewArguments(), orElse: () => const CourseCatalogViewArguments(),
); );
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i33.CourseCatalogView(key: args.key), builder: (context) => _i32.CourseCatalogView(key: args.key),
settings: data, settings: data,
); );
}, },
_i34.CourseUnitView: (data) { _i33.CourseUnitView: (data) {
final args = data.getArgs<CourseUnitViewArguments>(nullOk: false); final args = data.getArgs<CourseUnitViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i34.CourseUnitView(key: args.key, catalog: args.catalog), _i33.CourseUnitView(key: args.key, catalog: args.catalog),
settings: data, settings: data,
); );
}, },
_i35.LandingView: (data) { _i34.LandingView: (data) {
final args = data.getArgs<LandingViewArguments>( final args = data.getArgs<LandingViewArguments>(
orElse: () => const LandingViewArguments(), orElse: () => const LandingViewArguments(),
); );
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i35.LandingView(key: args.key), builder: (context) => _i34.LandingView(key: args.key),
settings: data, settings: data,
); );
}, },
_i36.CourseModuleView: (data) { _i35.CourseModuleView: (data) {
final args = data.getArgs<CourseModuleViewArguments>(nullOk: false); final args = data.getArgs<CourseModuleViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i36.CourseModuleView( builder: (context) => _i35.CourseModuleView(
key: args.key, module: args.module, catalog: args.catalog), key: args.key, module: args.module, catalog: args.catalog),
settings: data, settings: data,
); );
}, },
_i36.LearnCourseView: (data) {
final args = data.getArgs<LearnCourseViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i36.LearnCourseView(key: args.key, id: args.id),
settings: data,
);
},
}; };
@override @override
@ -1328,33 +1323,6 @@ class LearnProgramViewArguments {
} }
} }
class LearnCourseViewArguments {
const LearnCourseViewArguments({
this.key,
required this.id,
});
final _i37.Key? key;
final int id;
@override
String toString() {
return '{"key": "$key", "id": "$id"}';
}
@override
bool operator ==(covariant LearnCourseViewArguments other) {
if (identical(this, other)) return true;
return other.key == key && other.id == id;
}
@override
int get hashCode {
return key.hashCode ^ id.hashCode;
}
}
class AssessmentViewArguments { class AssessmentViewArguments {
const AssessmentViewArguments({ const AssessmentViewArguments({
this.key, this.key,
@ -1534,6 +1502,33 @@ class CourseModuleViewArguments {
} }
} }
class LearnCourseViewArguments {
const LearnCourseViewArguments({
this.key,
required this.id,
});
final _i37.Key? key;
final int id;
@override
String toString() {
return '{"key": "$key", "id": "$id"}';
}
@override
bool operator ==(covariant LearnCourseViewArguments other) {
if (identical(this, other)) return true;
return other.key == key && other.id == id;
}
@override
int get hashCode {
return key.hashCode ^ id.hashCode;
}
}
extension NavigatorStateExtension on _i46.NavigationService { extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> navigateToHomeView({ Future<dynamic> navigateToHomeView({
_i37.Key? key, _i37.Key? key,
@ -1991,23 +1986,6 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> navigateToLearnCourseView({
_i37.Key? key,
required int id,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return navigateTo<dynamic>(Routes.learnCourseView,
arguments: LearnCourseViewArguments(key: key, id: id),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> navigateToAssessmentView({ Future<dynamic> navigateToAssessmentView({
_i37.Key? key, _i37.Key? key,
required Map<String, dynamic> data, required Map<String, dynamic> data,
@ -2126,7 +2104,22 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> navigateToLearnCourseView({
_i37.Key? key,
required int id,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return navigateTo<dynamic>(Routes.learnCourseView,
arguments: LearnCourseViewArguments(key: key, id: id),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithHomeView({ Future<dynamic> replaceWithHomeView({
_i37.Key? key, _i37.Key? key,
@ -2584,23 +2577,6 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> replaceWithLearnCourseView({
_i37.Key? key,
required int id,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return replaceWith<dynamic>(Routes.learnCourseView,
arguments: LearnCourseViewArguments(key: key, id: id),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithAssessmentView({ Future<dynamic> replaceWithAssessmentView({
_i37.Key? key, _i37.Key? key,
required Map<String, dynamic> data, required Map<String, dynamic> data,
@ -2719,5 +2695,20 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> replaceWithLearnCourseView({
_i37.Key? key,
required int id,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return replaceWith<dynamic>(Routes.learnCourseView,
arguments: LearnCourseViewArguments(key: key, id: id),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
} }

View File

@ -23,7 +23,9 @@ Future<void> main() async {
EasyLocalization( EasyLocalization(
supportedLocales: const [ supportedLocales: const [
Locale('en'), Locale('en'),
Locale('አማ'), Locale(
'am',
),
], ],
path: 'assets/translations', path: 'assets/translations',
startLocale: const Locale('en'), startLocale: const Locale('en'),

View File

@ -0,0 +1,19 @@
import 'package:json_annotation/json_annotation.dart';
part 'app_update.g.dart';
@JsonSerializable()
class AppUpdate {
@JsonKey(name: 'force_update')
final bool? forceUpdate;
@JsonKey(name: 'update_available')
final bool? updateAvailable;
const AppUpdate({this.forceUpdate, this.updateAvailable});
factory AppUpdate.fromJson(Map<String, dynamic> json) =>
_$AppUpdateFromJson(json);
Map<String, dynamic> toJson() => _$AppUpdateToJson(this);
}

View File

@ -0,0 +1,17 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'app_update.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AppUpdate _$AppUpdateFromJson(Map<String, dynamic> json) => AppUpdate(
forceUpdate: json['force_update'] as bool?,
updateAvailable: json['update_available'] as bool?,
);
Map<String, dynamic> _$AppUpdateToJson(AppUpdate instance) => <String, dynamic>{
'force_update': instance.forceUpdate,
'update_available': instance.updateAvailable,
};

View File

@ -0,0 +1,17 @@
import 'package:json_annotation/json_annotation.dart';
part 'field_option.g.dart';
@JsonSerializable()
class FieldOption {
final String? code;
final String? label;
const FieldOption({this.code, this.label});
factory FieldOption.fromJson(Map<String, dynamic> json) =>
_$FieldOptionFromJson(json);
Map<String, dynamic> toJson() => _$FieldOptionToJson(this);
}

View File

@ -0,0 +1,18 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'field_option.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
FieldOption _$FieldOptionFromJson(Map<String, dynamic> json) => FieldOption(
code: json['code'] as String?,
label: json['label'] as String?,
);
Map<String, dynamic> _$FieldOptionToJson(FieldOption instance) =>
<String, dynamic>{
'code': instance.code,
'label': instance.label,
};

View File

@ -1,4 +1,5 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:yimaru_app/models/app_update.dart';
import 'package:yimaru_app/models/learn_lesson.dart'; import 'package:yimaru_app/models/learn_lesson.dart';
import 'package:yimaru_app/models/learn_practice.dart'; import 'package:yimaru_app/models/learn_practice.dart';
import 'package:yimaru_app/models/learn_program.dart'; import 'package:yimaru_app/models/learn_program.dart';
@ -12,6 +13,7 @@ import 'package:yimaru_app/ui/common/app_constants.dart';
import '../app/app.locator.dart'; import '../app/app.locator.dart';
import '../models/course_module.dart'; import '../models/course_module.dart';
import '../models/course_unit.dart'; import '../models/course_unit.dart';
import '../models/field_option.dart';
import '../models/learn_course.dart'; import '../models/learn_course.dart';
import '../models/learn_module.dart'; import '../models/learn_module.dart';
import '../models/learn_question.dart'; import '../models/learn_question.dart';
@ -379,6 +381,222 @@ class ApiService {
} }
} }
// Get educational levels
Future<List<FieldOption>> getEducationalLevels() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=education_level');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['education_level'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get ethiopia regions
Future<List<FieldOption>> getEthiopiaRegions() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=ethiopia_regions');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['ethiopia_regions'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get language challenges
Future<List<FieldOption>> getLanguageChallenges() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=language_challange');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['language_challange'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get occupations
Future<List<FieldOption>> getOccupations() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=occupation');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['occupation'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get age group
Future<List<FieldOption>> getAgeGroups() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=age_group');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['age_group'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get countries
Future<List<FieldOption>> getCountries() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=country');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['country'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get topics
Future<List<FieldOption>> getTopics() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=favourite_topic');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['favourite_topic'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get topics
Future<List<FieldOption>> getLanguageGoals() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=language_goal');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['language_goal'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get learning goal
Future<List<FieldOption>> getLearningGoals() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=learning_goal');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['learning_goal'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get assessment questions // Get assessment questions
Future<List<AssessmentQuestion>> getAssessmentQuestions(int id) async { Future<List<AssessmentQuestion>> getAssessmentQuestions(int id) async {
try { try {
@ -795,4 +1013,29 @@ class ApiService {
return []; return [];
} }
} }
// Check update
Future<Map<String, dynamic>> checkUpdate(Map<String, dynamic> data) async {
try {
Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kAppUrl/$kApiVersionUrl/$kCheckUrl',
data: data,
);
if (response.statusCode == 200) {
return {
'status': ResponseStatus.success,
'data': AppUpdate.fromJson(response.data['data']),
};
} else {
return {
'status': ResponseStatus.failure,
};
}
} on DioException {
return {
'status': ResponseStatus.failure,
};
}
}
} }

View File

@ -1,62 +1,51 @@
import 'package:battery_plus/battery_plus.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:in_app_update/in_app_update.dart'; import 'package:in_app_update/in_app_update.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import 'package:storage_info/storage_info.dart';
import 'package:yimaru_app/app/app.locator.dart'; import 'package:yimaru_app/app/app.locator.dart';
import 'package:yimaru_app/models/app_update.dart';
import 'package:yimaru_app/services/status_checker_service.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../ui/common/ui_helpers.dart'; import '../ui/common/ui_helpers.dart';
import 'api_service.dart';
class InAppUpdateService { class InAppUpdateService {
// Dependency Injection
final _apiService = locator<ApiService>();
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
Future<int> getBatteryLevel() async { final _statusCheckerService = locator<StatusCheckerService>();
final battery = Battery();
final batteryLevel = await battery.batteryLevel;
return batteryLevel;
}
Future<int> getAvailableStorage() async {
try {
final availableStorage =
await StorageInfo().getStorageFreeSpace(SpaceUnit.Bytes);
return availableStorage.toInt(); // Convert GB to bytes
} catch (e) {
return 0;
}
}
Future<void> checkForUpdate() async { Future<void> checkForUpdate() async {
const requiredStorage = 500 * 1024 * 1024;
final batteryLevel =
await getBatteryLevel(); // Implement getBatteryLevel function
final int storageAvailable =
await getAvailableStorage(); // Implement getAvailableStorage
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
// KewedeConst().showErrorToast(
// 'Unable to update app, please charge your phone & free up space.');
} else if (batteryLevel < 20) {
// KewedeConst()
// .showErrorToast('Unable to update app, please charge your phone.');
} else if (storageAvailable < requiredStorage) {
// KewedeConst()
// .showErrorToast('Unable to update app, please free up space.');
}
// Show user-friendly message explaining why update failed and suggesting solutions (e.g., charge device, free up space)
}
try { try {
final info = await InAppUpdate.checkForUpdate(); final info = await InAppUpdate.checkForUpdate();
if (info.updateAvailability == UpdateAvailability.updateAvailable) { final String version = await _statusCheckerService.getAppVersion();
AppUpdateResult result = await InAppUpdate.performImmediateUpdate();
if (version != info.availableVersionCode.toString()) {
Map<String, dynamic> data = {
'platform': 'ANDROID',
'version_code': info.availableVersionCode
};
Map<String, dynamic> response = await _apiService.checkUpdate(data);
if (response['status'] == ResponseStatus.success) {
AppUpdate update = response['data'] as AppUpdate;
if (update.updateAvailable ?? false) {
if (update.forceUpdate ?? false) {
AppUpdateResult result =
await InAppUpdate.performImmediateUpdate();
if (result == AppUpdateResult.userDeniedUpdate) { if (result == AppUpdateResult.userDeniedUpdate) {
showErrorToast('An update is required to continue using this app.'); showErrorToast(
'An update is required to continue using this app.');
_navigationService.back(); _navigationService.back();
} }
} else {
await InAppUpdate.startFlexibleUpdate();
}
}
}
} }
// ... rest of your update logic ...
} on PlatformException { } on PlatformException {
// Handle specific error code for better user experience and potentially different error messages for each issue // Handle specific error code for better user experience and potentially different error messages for each issue
} }

View File

@ -23,7 +23,7 @@ class LocalizationService with ListenableServiceMixin {
Map<String, dynamic> get selectedLanguage => _selectedLanguage; Map<String, dynamic> get selectedLanguage => _selectedLanguage;
final List<Map<String, dynamic>> _languages = [ final List<Map<String, dynamic>> _languages = [
{'code': 'አማ', 'language': 'አማርኛ'}, {'code': 'am', 'language': 'አማርኛ'},
{'code': 'en', 'language': 'English'}, {'code': 'en', 'language': 'English'},
]; ];
@ -37,7 +37,7 @@ class LocalizationService with ListenableServiceMixin {
required Map<String, dynamic> title}) async { required Map<String, dynamic> title}) async {
_selectedLanguage = title; _selectedLanguage = title;
if (title['code'] == 'አማ') { if (title['code'] == 'am') {
await setAmharicLanguage(context); await setAmharicLanguage(context);
} else { } else {
await setEnglishLanguage(context); await setEnglishLanguage(context);
@ -50,17 +50,15 @@ class LocalizationService with ListenableServiceMixin {
if (language == 'en') { if (language == 'en') {
_selectedLanguage = {'code': 'en', 'language': 'English'}; _selectedLanguage = {'code': 'en', 'language': 'English'};
} else { } else {
_selectedLanguage = {'code': 'አማ', 'language': 'አማርኛ'}; _selectedLanguage = {'code': 'am', 'language': 'አማርኛ'};
} }
notifyListeners(); notifyListeners();
print('SELECTED LANGUAGE: $language $_selectedLanguage');
} }
Future<void> setAmharicLanguage(BuildContext context) async { Future<void> setAmharicLanguage(BuildContext context) async {
await context.setLocale(const Locale('አማ')); await context.setLocale(const Locale('am'));
await _secureService.setString('language', 'አማ'); await _secureService.setString('language', 'am');
notifyListeners(); notifyListeners();
} }

View File

@ -0,0 +1,104 @@
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/field_option.dart';
import '../app/app.locator.dart';
import 'api_service.dart';
class OnboardingService with ListenableServiceMixin {
// Dependency injection
final _apiService = locator<ApiService>();
// Initialization
learnService() {
listenToReactiveValues([
_topics,
_regions,
_ageGroups,
_countries,
_challenges,
_occupations,
_learningGoals,
_languageGoals,
_educationalBackgrounds
]);
}
// Topics
List<FieldOption> _topics = [];
List<FieldOption> get topics => _topics;
// Regions
List<FieldOption> _regions = [];
List<FieldOption> get regions => _regions;
// Age groups
List<FieldOption> _ageGroups = [];
List<FieldOption> get ageGroups => _ageGroups;
// Countries
List<FieldOption> _countries = [];
List<FieldOption> get countries => _countries;
// Challenges
List<FieldOption> _challenges = [];
List<FieldOption> get challenges => _challenges;
// Occupations
List<FieldOption> _occupations = [];
List<FieldOption> get occupations => _occupations;
// Learning goals
List<FieldOption> _learningGoals = [];
List<FieldOption> get learningGoals => _learningGoals;
// Language goals
List<FieldOption> _languageGoals = [];
List<FieldOption> get languageGoals => _languageGoals;
// Educational backgrounds
List<FieldOption> _educationalBackgrounds = [];
List<FieldOption> get educationalBackgrounds => _educationalBackgrounds;
// Onboarding fields
Future<bool> getOnboardingFields() async {
_topics = await _apiService.getTopics();
_ageGroups = await _apiService.getAgeGroups();
_countries = await _apiService.getCountries();
_occupations = await _apiService.getOccupations();
_regions = await _apiService.getEthiopiaRegions();
_learningGoals = await _apiService.getLearningGoals();
_languageGoals = await _apiService.getLanguageGoals();
_challenges = await _apiService.getLanguageChallenges();
_educationalBackgrounds = await _apiService.getEducationalLevels();
notifyListeners();
if (_topics.isNotEmpty &&
_ageGroups.isNotEmpty &&
_countries.isNotEmpty &&
_occupations.isNotEmpty &&
_regions.isNotEmpty &&
_learningGoals.isNotEmpty &&
_challenges.isNotEmpty &&
_educationalBackgrounds.isNotEmpty) {
return true;
}
return false;
}
}

View File

@ -1,4 +1,5 @@
import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart'; import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:yimaru_app/services/secure_storage_service.dart'; import 'package:yimaru_app/services/secure_storage_service.dart';
import '../app/app.locator.dart'; import '../app/app.locator.dart';
@ -25,4 +26,16 @@ class StatusCheckerService {
return false; return false;
} }
} }
// Get app version
Future<String> getAppVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String version = packageInfo.version; // e.g. 1.0.0
String buildNumber = packageInfo.buildNumber; // version code
print("Version: $version");
print("Build Number: $buildNumber");
return buildNumber;
}
} }

View File

@ -1,10 +1,14 @@
// Endpoints // Endpoints
String kBaseUrl = 'https://api.yimaruacademy.com'; String kBaseUrl = 'https://api.yimaruacademy.com';
String kAppUrl = 'app';
String kApiUrl = 'api'; String kApiUrl = 'api';
String kUnitsUrl = 'units'; String kUnitsUrl = 'units';
String kCheckUrl = 'check';
String kApiVersionUrl = 'v1'; String kApiVersionUrl = 'v1';
String kLevelsUrl = 'levels'; String kLevelsUrl = 'levels';
@ -15,6 +19,8 @@ String kModulesUrl = 'modules';
String kLessonsUrl = 'lessons'; String kLessonsUrl = 'lessons';
String kVersionUrl = 'version';
String kProgramsUrl = 'programs'; String kProgramsUrl = 'programs';
String kRegisterUrl = 'register'; String kRegisterUrl = 'register';
@ -47,6 +53,8 @@ String kSubmodulesUrl = 'sub-modules';
String kSubcoursesUrl = 'sub-courses'; String kSubcoursesUrl = 'sub-courses';
String kFieldOptions = 'field-options';
String kResetPassword = 'resetPassword'; String kResetPassword = 'resetPassword';
String kQuestionSetsUrl = 'question-sets'; String kQuestionSetsUrl = 'question-sets';

View File

@ -18,7 +18,7 @@ class CodegenLoader extends AssetLoader{
"loading": "በመጫን ላይ", "loading": "በመጫን ላይ",
"welcome_back": "እንኳን በደህና ተመለሱ", "welcome_back": "እንኳን በደህና ተመለሱ",
"checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ", "checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ",
"dont_have_account": "መለያ የለዎትም? ይመዝገቡ", "dont_have_account": "መለያ የለዎትም?",
"email": "ኢሜይል", "email": "ኢሜይል",
"password": "የይለፍ ቃል", "password": "የይለፍ ቃል",
"forgot_password": "የይለፍ ቃል ረሱ?", "forgot_password": "የይለፍ ቃል ረሱ?",
@ -59,14 +59,14 @@ class CodegenLoader extends AssetLoader{
"continue_learning": "መማርን ይቀጥሉ", "continue_learning": "መማርን ይቀጥሉ",
"start_learning": "ትምህርትን ይጀምሩ", "start_learning": "ትምህርትን ይጀምሩ",
"completed": "ተጠናቋል", "completed": "ተጠናቋል",
"take_practice": " ልምምድ ያድርጉ", "take_practice": "ልምምድ ያድርጉ",
"your_current_level": "የአሁኑ ደረጃዎ", "your_current_level": "የአሁኑ ደረጃዎ",
"overall_progress": "አጠቃላይ እድገት", "overall_progress": "አጠቃላይ እድገት",
"great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው", "great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው",
"view_module": "ሞጁሉን ይመልከቱ", "view_module": "ሞጁሉን ይመልከቱ",
"progress": "እድገት", "progress": "እድገት",
"keep_going": " ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ", "keep_going": "ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ",
"lessons_in_module": " በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ", "lessons_in_module": "በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ",
"practice": "ልምምድ", "practice": "ልምምድ",
"start": "ጀምር", "start": "ጀምር",
"in_progress": "በሂደት ላይ", "in_progress": "በሂደት ላይ",
@ -150,7 +150,7 @@ class CodegenLoader extends AssetLoader{
"ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።", "ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።",
"begin_level_practice": "የደረጃ ልምምድን ጀምር", "begin_level_practice": "የደረጃ ልምምድን ጀምር",
"lets_practice_course": "የኮርሱን ልምምድ እንለማመድ", "lets_practice_course": "የኮርሱን ልምምድ እንለማመድ",
"lets_quick_practice": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!", "lets_quick_review": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!",
"speaking": "እየተናገረ ነው", "speaking": "እየተናገረ ነው",
"you_have_finished_practice": "ልምምድህን አጠናቀቅህ", "you_have_finished_practice": "ልምምድህን አጠናቀቅህ",
"view_results": "ውጤቶቼን እይ", "view_results": "ውጤቶቼን እይ",
@ -169,13 +169,42 @@ class CodegenLoader extends AssetLoader{
"what_should_we_call_you": "ምን ብለን እንጠራህ?", "what_should_we_call_you": "ምን ብለን እንጠራህ?",
"name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።", "name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።",
"choose_your_gender": "ጾታህን ምረጥ", "choose_your_gender": "ጾታህን ምረጥ",
"gender_for_personalization": "በጾታህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።" "gender_for_personalization": "በጾታህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"age_range": "በየትኛው የእድሜ ክልል ውስጥ ነህ?",
"age_for_personalization": "በእድሜህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"educational_background": "አሁን ያለህ የትምህርት ደረጃ ምንድን ነው?",
"education_for_personalization": "ይህ ትምህርቶችን ከልምድህ ጋር እንዲስማሙ ለማድረግ ይረዳናል።",
"your_occupation": "ስራህ ምንድን ነው?",
"occupation_for_personalization": "በስራህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"location": "ከየት ነህ?",
"select_country_region": "አገርህን እና ክልልህን ከተቆልቋይ ዝርዝሩ ምረጥ",
"select_country": "አገር ምረጥ",
"learning_goal": "የመማር ዓላማህን ምረጥ",
"language_goal": "እንግሊዝኛህን ለማሻሻል ዋና ዓላማህ ምንድን ነው?",
"your_goal": "ዓላማህ የመማር ጉዞህን እንዲስማማ ለማድረግ ይረዳናል።",
"write_your_goal": "ዓላማህን ጻፍ…",
"challenge_you_face": "What challenge do you face most with English?",
"evey_one_has_strugle": "ሁሉም ሰው ችግሮች አሉት፣ የአንተን እንጀምር እንፍታ",
"write_your_challenge": "ችግርህን ጻፍ…",
"topic_interest": "በጣም የሚስቡህ ርዕሶች የትኞቹ ናቸው?",
"favourite_topic": "የምትወዳቸው ርዕሶች አስደሳች እና ከሕይወትህ ጋር የተዛመዱ ትምህርቶችን ለመፍጠር ይረዱናል።",
"your_interest": "ፍላጎትህን ጻፍ…",
"want_quick_assessment": "የእንግሊዝኛ ደረጃህን ለማወቅ ፈጣን ግምገማ ትፈልጋለህ?",
"answer_quick_questions": "የእንግሊዝኛ ችሎታህን ለመረዳት ጥቂት ፈጣን ጥያቄዎችን መልስ።",
"skip": "ዝለል",
"finish_level": "ደረጃውን አጠናቅቅ",
"likely_speaker": "አንተ ምናልባት ተናጋሪ ነህ",
"great_job": "በጣም ጥሩ ስራ! ለመሻሻል ቀጣዩ ደረጃህ ይኸው ነው።",
"lets_start_practice": "ልምምድህን እንጀምር",
"welcome_abroad": "እንኳን ደህና መጣህ",
"ready_to_explore": "የግል ትምህርቶችህን ለማሰስ ዝግጁ ነህ።",
"finish": "አጠናቅቅ"
}; };
static const Map<String,dynamic> _en = { static const Map<String,dynamic> _en = {
"loading": "Loading", "loading": "Loading",
"welcome_back": "Welcome back", "welcome_back": "Welcome back",
"checking_user_info": "Checking user info", "checking_user_info": "Checking user info",
"dont_have_account": "Don't have an account? Register", "dont_have_account": "Don't have an account?",
"email": "Email", "email": "Email",
"password": "Password", "password": "Password",
"forgot_password": "Forgot password?", "forgot_password": "Forgot password?",
@ -326,7 +355,36 @@ static const Map<String,dynamic> _en = {
"what_should_we_call_you": "What should we call you?", "what_should_we_call_you": "What should we call you?",
"name_for_personalization": "Well use your name to personalize your learning journey.", "name_for_personalization": "Well use your name to personalize your learning journey.",
"choose_your_gender": "Choose your gender?", "choose_your_gender": "Choose your gender?",
"gender_for_personalization": "Well personalize your learning experience based on your gender." "gender_for_personalization": "Well personalize your learning experience based on your gender.",
"age_range": "Which age range are you in?",
"age_for_personalization": "Well personalize your learning experience based on your age.",
"educational_background": "Whats your current educational level?",
"education_for_personalization": "This helps us tailor your lessons to your experience.",
"your_occupation": "Whats your occupation?",
"occupation_for_personalization": "Well personalize your learning experience based on your occupation.",
"location": "Where are you from?",
"select_country_region": "Select your country and region from the dropdown",
"select_country": "Select country",
"learning_goal": "Choose your learning goal.",
"language_goal": "Whats your main goal for improving your English?",
"your_goal": "Your goal helps us tailor your learning journey.",
"write_your_goal": "Write your goal…",
"challenge_you_face": "What challenge do you face most with English?",
"evey_one_has_strugle": "Everyone has struggles, lets start fixing yours",
"write_your_challenge": "Write your challenge…",
"topic_interest": "Which topics interest you most?",
"favourite_topic": "Your favorite topics help us create fun, relatable lessons.",
"your_interest": "Write your interest…",
"want_quick_assessment": "Want a quick assessment to know your English level?",
"answer_quick_questions": "Answer a few quick questions to help us understand your English proficiency.",
"skip": "Skip",
"finish_level": "Finish Level",
"likely_speaker": "Youre likely speaker of",
"great_job": "Great Job! Heres your next step to keep improving.",
"lets_start_practice": "Let's start your practice",
"welcome_abroad": "Welcome aboard",
"ready_to_explore": "Youre ready to explore your personalized lessons.",
"finish": "Finish"
}; };
static const Map<String, Map<String,dynamic>> mapLocales = {"am": _am, "en": _en}; static const Map<String, Map<String,dynamic>> mapLocales = {"am": _am, "en": _en};
} }

View File

@ -136,7 +136,7 @@ abstract class LocaleKeys {
static const ask_you_few_actions = 'ask_you_few_actions'; static const ask_you_few_actions = 'ask_you_few_actions';
static const begin_level_practice = 'begin_level_practice'; static const begin_level_practice = 'begin_level_practice';
static const lets_practice_course = 'lets_practice_course'; static const lets_practice_course = 'lets_practice_course';
static const lets_quick_practice = 'lets_quick_practice'; static const lets_quick_review = 'lets_quick_review';
static const speaking = 'speaking'; static const speaking = 'speaking';
static const you_have_finished_practice = 'you_have_finished_practice'; static const you_have_finished_practice = 'you_have_finished_practice';
static const view_results = 'view_results'; static const view_results = 'view_results';
@ -156,5 +156,34 @@ abstract class LocaleKeys {
static const name_for_personalization = 'name_for_personalization'; static const name_for_personalization = 'name_for_personalization';
static const choose_your_gender = 'choose_your_gender'; static const choose_your_gender = 'choose_your_gender';
static const gender_for_personalization = 'gender_for_personalization'; static const gender_for_personalization = 'gender_for_personalization';
static const age_range = 'age_range';
static const age_for_personalization = 'age_for_personalization';
static const educational_background = 'educational_background';
static const education_for_personalization = 'education_for_personalization';
static const your_occupation = 'your_occupation';
static const occupation_for_personalization = 'occupation_for_personalization';
static const location = 'location';
static const select_country_region = 'select_country_region';
static const select_country = 'select_country';
static const learning_goal = 'learning_goal';
static const language_goal = 'language_goal';
static const your_goal = 'your_goal';
static const write_your_goal = 'write_your_goal';
static const challenge_you_face = 'challenge_you_face';
static const evey_one_has_strugle = 'evey_one_has_strugle';
static const write_your_challenge = 'write_your_challenge';
static const topic_interest = 'topic_interest';
static const favourite_topic = 'favourite_topic';
static const your_interest = 'your_interest';
static const want_quick_assessment = 'want_quick_assessment';
static const answer_quick_questions = 'answer_quick_questions';
static const skip = 'skip';
static const finish_level = 'finish_level';
static const likely_speaker = 'likely_speaker';
static const great_job = 'great_job';
static const lets_start_practice = 'lets_start_practice';
static const welcome_abroad = 'welcome_abroad';
static const ready_to_explore = 'ready_to_explore';
static const finish = 'finish';
} }

View File

@ -12,8 +12,7 @@ class AccountPrivacyViewModel extends ReactiveViewModel {
final _localizationService = locator<LocalizationService>(); final _localizationService = locator<LocalizationService>();
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices => [_localizationService];
[ _localizationService];
// Languages // Languages
Map<String, dynamic> get _selectedLanguage => Map<String, dynamic> get _selectedLanguage =>

View File

@ -1,6 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
@ -67,17 +69,19 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
showBackButton: true, showBackButton: true,
onPop: viewModel.goBack, onPop: viewModel.goBack,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'Want a quick assessment to know your English level?', LocaleKeys.want_quick_assessment.tr(),
style: style25DG600, style: style25DG600,
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
'Answer a few quick questions to help us understand your English proficiency.', LocaleKeys.answer_quick_questions.tr(),
style: style14MG400, style: style14MG400,
); );
@ -96,9 +100,9 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
safe: false, safe: false,
text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: LocaleKeys.cont.tr(),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await _next(viewModel), onTap: () async => await _next(viewModel),
); );
@ -111,9 +115,9 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildSkipButton(AssessmentViewModel viewModel) => Widget _buildSkipButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Skip',
borderRadius: 12, borderRadius: 12,
backgroundColor: kcWhite, backgroundColor: kcWhite,
text: LocaleKeys.skip.tr(),
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
onTap: () => viewModel.next(page: 3), onTap: () => viewModel.next(page: 3),

View File

@ -1,6 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart'; import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart';
@ -134,8 +136,8 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: viewModel.currentQuestionIndex == text: viewModel.currentQuestionIndex ==
viewModel.assessmentQuestions.length - 1 viewModel.assessmentQuestions.length - 1
? 'Finish Level' ? LocaleKeys.finish_level.tr()
: 'Continue', : LocaleKeys.cont.tr(),
backgroundColor: viewModel.selectedAnswers backgroundColor: viewModel.selectedAnswers
.containsKey('${viewModel.currentQuestionIndex + 1}') .containsKey('${viewModel.currentQuestionIndex + 1}')
? kcPrimaryColor ? kcPrimaryColor

View File

@ -1,7 +1,9 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
@ -35,8 +37,10 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
showBackButton: true, showBackButton: true,
onPop: viewModel.goBack, onPop: viewModel.goBack,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody(AssessmentViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
@ -75,13 +79,13 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
]; ];
Widget _buildTitle(AssessmentViewModel viewModel) => Text( Widget _buildTitle(AssessmentViewModel viewModel) => Text(
'Youre likely a ${viewModel.proficiencyLevel?.toUpperCase()} speaker!', '${LocaleKeys.likely_speaker.tr()} ${viewModel.proficiencyLevel?.toUpperCase()}',
style: style25DG600, style: style25DG600,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildPrimarySubtitle() => Text( Widget _buildPrimarySubtitle() => Text(
'Great Job! Heres your next step to keep improving.', LocaleKeys.great_job.tr(),
style: style14MG400, style: style14MG400,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
@ -93,7 +97,7 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
'assets/icons/${viewModel.proficiencyLevel?.substring(0, 1).toLowerCase()}_${viewModel.proficiencyLevel?.substring(1).toLowerCase()}.svg'); 'assets/icons/${viewModel.proficiencyLevel?.substring(0, 1).toLowerCase()}_${viewModel.proficiencyLevel?.substring(1).toLowerCase()}.svg');
Widget _buildSecondarySubtitle() => Text( Widget _buildSecondarySubtitle() => Text(
'Let\'s start your practice', LocaleKeys.lets_start_practice.tr(),
style: style14DG400, style: style14DG400,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
@ -106,9 +110,9 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildContinueButton(AssessmentViewModel viewModel) => Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: LocaleKeys.cont.tr(),
onTap: () => viewModel.next(), onTap: () => viewModel.next(),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
); );

View File

@ -1,7 +1,9 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
@ -52,8 +54,10 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
showBackButton: true, showBackButton: true,
onPop: viewModel.goBack, onPop: viewModel.goBack,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody(AssessmentViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
@ -92,7 +96,7 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildTitle(AssessmentViewModel viewModel) => Text.rich( Widget _buildTitle(AssessmentViewModel viewModel) => Text.rich(
TextSpan( TextSpan(
text: 'Welcome aboard', text: LocaleKeys.welcome_abroad.tr(),
style: style25DG600, style: style25DG600,
children: [ children: [
TextSpan( TextSpan(
@ -104,7 +108,7 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
'Youre ready to explore your personalized lessons.', LocaleKeys.ready_to_explore.tr(),
style: style14MG400, style: style14MG400,
); );
@ -116,9 +120,9 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildContinueButton(AssessmentViewModel viewModel) => Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Finish',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: LocaleKeys.finish.tr(),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await _start(viewModel), onTap: () async => await _start(viewModel),
); );

View File

@ -116,7 +116,7 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
leadingIcon: Icons.call, leadingIcon: Icons.call,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
text:LocaleKeys.tap_to_call.tr(), text: LocaleKeys.tap_to_call.tr(),
onTap: () async => await viewModel.callSupport(), onTap: () async => await viewModel.callSupport(),
); );
} }

View File

@ -96,8 +96,10 @@ class RequestCodeScreen extends ViewModelWidget<ForgetPasswordViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _inAppPop(viewModel), onPop: () => _inAppPop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody( Widget _buildExpandedBody(

View File

@ -96,8 +96,10 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _inAppPop(viewModel), onPop: () => _inAppPop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody(ForgetPasswordViewModel viewModel) => Widget _buildExpandedBody(ForgetPasswordViewModel viewModel) =>

View File

@ -7,7 +7,6 @@ import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/profile/profile_view.dart'; import 'package:yimaru_app/ui/views/profile/profile_view.dart';
import '../../widgets/coming_soon.dart'; import '../../widgets/coming_soon.dart';
import '../course/course_view.dart';
import 'home_viewmodel.dart'; import 'home_viewmodel.dart';
class HomeView extends StackedView<HomeViewModel> { class HomeView extends StackedView<HomeViewModel> {

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/views/landing/screens/first_landing_screen.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/fourth_landing_screen.dart';
import 'package:yimaru_app/ui/views/landing/screens/second_landing_screen.dart'; import 'package:yimaru_app/ui/views/landing/screens/second_landing_screen.dart';
@ -23,19 +24,21 @@ class LandingView extends StackedView<LandingViewModel> {
LandingViewModel viewModel, LandingViewModel viewModel,
Widget? child, Widget? child,
) => ) =>
_buildLandingScreens(viewModel); _buildLandingScreensWrapper(viewModel);
Widget _buildLandingScreensWrapper(LandingViewModel viewModel) => Scaffold(
backgroundColor: kcPrimaryColor,
body: _buildLandingScreens(viewModel),
);
Widget _buildLandingScreens(LandingViewModel viewModel) => FlutterCarousel( Widget _buildLandingScreens(LandingViewModel viewModel) => FlutterCarousel(
options: FlutterCarouselOptions( options: FlutterCarouselOptions(
autoPlay: true, autoPlay: true,
viewportFraction: 1, viewportFraction: 1,
showIndicator: true,
indicatorMargin: 40, indicatorMargin: 40,
showIndicator: false,
height: double.maxFinite, height: double.maxFinite,
slideIndicator: CircularSlideIndicator( controller: viewModel.controller,
slideIndicatorOptions:
const SlideIndicatorOptions(indicatorRadius: 2.5),
),
), ),
items: _buildScreens(), items: _buildScreens(),
); );

View File

@ -1,3 +1,4 @@
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
@ -11,6 +12,16 @@ class LandingViewModel extends BaseViewModel {
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
// Controller
final FlutterCarouselController _controller = FlutterCarouselController();
FlutterCarouselController get controller => _controller;
// In-app navigation
void next() {
_controller.nextPage();
}
// Navigation // Navigation
Future<void> navigateToLogin() async => Future<void> navigateToLogin() async =>
await _navigationService.replaceWithLoginView(); await _navigationService.replaceWithLoginView();
@ -18,6 +29,7 @@ class LandingViewModel extends BaseViewModel {
// Remote api call // Remote api call
// First time install // First time install
Future<void> setFirstTimeInstall() async { Future<void> setFirstTimeInstall() async {
await runBusyFuture(_setFirstTimeInstall()); await runBusyFuture(_setFirstTimeInstall());
} }

View File

@ -126,10 +126,10 @@ class FirstLandingScreen extends ViewModelWidget<LandingViewModel> {
Widget _buildContinueButton(LandingViewModel viewModel) => Widget _buildContinueButton(LandingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Next',
borderRadius: 25, borderRadius: 25,
text: 'Get Started', onTap: viewModel.next,
backgroundColor: kcWhite, backgroundColor: kcWhite,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
onTap: () async => await viewModel.setFirstTimeInstall(),
); );
} }

View File

@ -127,10 +127,10 @@ class SecondLandingScreen extends ViewModelWidget<LandingViewModel> {
Widget _buildContinueButton(LandingViewModel viewModel) => Widget _buildContinueButton(LandingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Next',
borderRadius: 25, borderRadius: 25,
text: 'Get Started', onTap: viewModel.next,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.setFirstTimeInstall(),
); );
} }

View File

@ -122,15 +122,15 @@ class ThirdLandingScreen extends ViewModelWidget<LandingViewModel> {
viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel); viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel);
Widget _buildIndicator() => Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite); const CustomCircularProgressIndicator(color: kcPrimaryColor);
Widget _buildContinueButton(LandingViewModel viewModel) => Widget _buildContinueButton(LandingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Next',
borderRadius: 25, borderRadius: 25,
text: 'Get Started', onTap: viewModel.next,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.setFirstTimeInstall(),
); );
} }

View File

@ -108,7 +108,7 @@ class LanguageView extends StackedView<LanguageViewModel> {
Widget _buildAppbar(LanguageViewModel viewModel) => SmallAppBar( Widget _buildAppbar(LanguageViewModel viewModel) => SmallAppBar(
showBackButton: true, showBackButton: true,
onPop: viewModel.pop, onPop: viewModel.pop,
title:LocaleKeys.language_preference.tr() , title: LocaleKeys.language_preference.tr(),
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
@ -117,7 +117,7 @@ class LanguageView extends StackedView<LanguageViewModel> {
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
LocaleKeys.switch_language_anytime.tr() , LocaleKeys.switch_language_anytime.tr(),
style: style14MG400, style: style14MG400,
); );

View File

@ -89,6 +89,7 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
course: viewModel.courses[index], course: viewModel.courses[index],
onViewTap: () async => onViewTap: () async =>
await viewModel.navigateToLearnModule(viewModel.courses[index]), await viewModel.navigateToLearnModule(viewModel.courses[index]),
onLockTap: () async => await viewModel.navigateToLearnSubscription(),
onPracticeTap: () async => await viewModel.navigateToLearnPractice( onPracticeTap: () async => await viewModel.navigateToLearnPractice(
id: viewModel.courses[index].id ?? 0, id: viewModel.courses[index].id ?? 0,
level: viewModel.courses[index].name ?? ''), level: viewModel.courses[index].name ?? ''),
@ -99,11 +100,13 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
Widget _buildTile({ Widget _buildTile({
required LearnCourse course, required LearnCourse course,
required GestureTapCallback onViewTap, required GestureTapCallback onViewTap,
required GestureTapCallback onLockTap,
required GestureTapCallback onPracticeTap, required GestureTapCallback onPracticeTap,
}) => }) =>
LearnCourseTile( LearnCourseTile(
course: course, course: course,
onViewTap: onViewTap, onViewTap: onViewTap,
onLockTap: onLockTap,
onPracticeTap: onPracticeTap, onPracticeTap: onPracticeTap,
); );
} }

View File

@ -29,6 +29,9 @@ class LearnCourseViewModel extends ReactiveViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnSubscription() async =>
await _navigationService.navigateToLearnSubscriptionView();
Future<void> navigateToLearnModule(LearnCourse course) async => Future<void> navigateToLearnModule(LearnCourse course) async =>
_navigationService.navigateToLearnModuleView(course: course); _navigationService.navigateToLearnModuleView(course: course);
@ -39,7 +42,7 @@ class LearnCourseViewModel extends ReactiveViewModel {
level: level, level: level,
practice: LearnPractices.course, practice: LearnPractices.course,
label: LocaleKeys.begin_level_practice.tr(), label: LocaleKeys.begin_level_practice.tr(),
subtitle: LocaleKeys.lets_quick_practice.tr(), subtitle: LocaleKeys.lets_quick_review.tr(),
title: '${LocaleKeys.lets_practice_course.tr()} $level', title: '${LocaleKeys.lets_practice_course.tr()} $level',
); );

View File

@ -34,9 +34,9 @@ class LearnLessonViewModel extends ReactiveViewModel {
await _navigationService.navigateToLearnPracticeView( await _navigationService.navigateToLearnPracticeView(
id: id, id: id,
practice: LearnPractices.lesson, practice: LearnPractices.lesson,
label:LocaleKeys.start_practice.tr(), label: LocaleKeys.start_practice.tr(),
title: LocaleKeys.lets_practice_module.tr(), title: LocaleKeys.lets_practice_module.tr(),
subtitle:LocaleKeys.ask_you_few_actions.tr(), subtitle: LocaleKeys.ask_you_few_actions.tr(),
); );
Future<void> navigateToLearnLessonDetail( Future<void> navigateToLearnLessonDetail(

View File

@ -192,7 +192,7 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
text:LocaleKeys.take_practice.tr() , text: LocaleKeys.take_practice.tr(),
onTap: () async => await _navigate(viewModel), onTap: () async => await _navigate(viewModel),
); );
} }

View File

@ -29,8 +29,6 @@ class LearnModuleViewModel extends ReactiveViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnLesson(LearnModule module) async => Future<void> navigateToLearnLesson(LearnModule module) async =>
await _navigationService.navigateToLearnLessonView(module: module); await _navigationService.navigateToLearnLessonView(module: module);

View File

@ -127,7 +127,8 @@ class InteractLearnPracticeScreen
showBackButton: true, showBackButton: true,
onPop: () async => onPop: () async =>
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
title: '${LocaleKeys.practice_speaking.tr()} ($index/${viewModel.questions.length})'); title:
'${LocaleKeys.practice_speaking.tr()} ($index/${viewModel.questions.length})');
Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) => Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) =>
Column( Column(
@ -160,7 +161,7 @@ class InteractLearnPracticeScreen
); );
Widget _buildSpeakingLabel() => Text( Widget _buildSpeakingLabel() => Text(
'${ LocaleKeys.you_are_speaking.tr()}...', '${LocaleKeys.you_are_speaking.tr()}...',
style: style14P400, style: style14P400,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
@ -359,7 +360,7 @@ class InteractLearnPracticeScreen
CustomColumnButton( CustomColumnButton(
color: kcRed, color: kcRed,
icon: Icons.close, icon: Icons.close,
label:LocaleKeys.cancel.tr() , label: LocaleKeys.cancel.tr(),
onTap: () async => onTap: () async =>
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
); );

View File

@ -158,6 +158,5 @@ class LearnPracticeAppreciationScreen
onTap: () => viewModel.goTo(4), onTap: () => viewModel.goTo(4),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
text: LocaleKeys.view_results.tr(), text: LocaleKeys.view_results.tr(),
); );
} }

View File

@ -180,6 +180,6 @@ class LearnPracticeDescriptionScreen
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: () => viewModel.goTo(2), onTap: () => viewModel.goTo(2),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
text: LocaleKeys.start_practice.tr() , text: LocaleKeys.start_practice.tr(),
); );
} }

View File

@ -61,7 +61,7 @@ class LearnPracticeFinishScreen
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
showBackButton: true, showBackButton: true,
onPop: viewModel.goBack, onPop: viewModel.goBack,
title:LocaleKeys.practice_speaking.tr(), title: LocaleKeys.practice_speaking.tr(),
); );
Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) => Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) =>
@ -87,7 +87,7 @@ class LearnPracticeFinishScreen
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
LocaleKeys.sound_confident.tr() , LocaleKeys.sound_confident.tr(),
style: style14DG400, style: style14DG400,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
@ -120,7 +120,7 @@ class LearnPracticeFinishScreen
onTap: viewModel.pop, onTap: viewModel.pop,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
text: LocaleKeys.continue_practice.tr() , text: LocaleKeys.continue_practice.tr(),
); );
Widget _buildPracticeAgainButton(LearnPracticeViewModel viewModel) => Widget _buildPracticeAgainButton(LearnPracticeViewModel viewModel) =>

View File

@ -115,7 +115,7 @@ class LearnPracticeResultScreen
required LearnPracticeViewModel viewModel}) => required LearnPracticeViewModel viewModel}) =>
SmallAppBar( SmallAppBar(
showBackButton: true, showBackButton: true,
title:LocaleKeys.result.tr(), title: LocaleKeys.result.tr(),
onPop: () async => onPop: () async =>
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
); );
@ -176,7 +176,7 @@ class LearnPracticeResultScreen
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text:LocaleKeys.cont.tr(), text: LocaleKeys.cont.tr(),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await _navigate(viewModel), onTap: () async => await _navigate(viewModel),
); );

View File

@ -196,7 +196,7 @@ class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
]; ];
Widget _buildActionLabel() => Text( Widget _buildActionLabel() => Text(
LocaleKeys.tap_start_to_listen.tr() , LocaleKeys.tap_start_to_listen.tr(),
style: style14DG400, style: style14DG400,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
@ -216,7 +216,7 @@ class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
Widget _buildReplyButtonWrapper() => Expanded(child: _buildReplyButton()); Widget _buildReplyButtonWrapper() => Expanded(child: _buildReplyButton());
Widget _buildReplyButton() => CustomColumnButton( Widget _buildReplyButton() => CustomColumnButton(
icon: Icons.replay, label:LocaleKeys.reply.tr() , color: kcLightGrey); icon: Icons.replay, label: LocaleKeys.reply.tr(), color: kcLightGrey);
Widget _buildMicButtonWrapper(LearnPracticeViewModel viewModel) => Widget _buildMicButtonWrapper(LearnPracticeViewModel viewModel) =>
Expanded(child: _buildMicButton(viewModel)); Expanded(child: _buildMicButton(viewModel));

View File

@ -89,14 +89,12 @@ class LearnProgramView extends StackedView<LearnProgramViewModel> {
program: viewModel.learnPrograms[index], program: viewModel.learnPrograms[index],
onTap: () async => await viewModel onTap: () async => await viewModel
.navigateToLearnCourse(viewModel.learnPrograms[index].id ?? 0), .navigateToLearnCourse(viewModel.learnPrograms[index].id ?? 0),
onLockTap: () async => await viewModel.navigateToLearnSubscription(),
), ),
); );
Widget _buildTile({ Widget _buildTile({
required LearnProgram program, required LearnProgram program,
required GestureTapCallback onTap, required GestureTapCallback onTap,
required GestureTapCallback onLockTap,
}) => }) =>
LearnProgramTile(onTap: onTap, program: program,onLockTap: onLockTap,); LearnProgramTile(onTap: onTap, program: program);
} }

View File

@ -39,9 +39,6 @@ class LearnProgramViewModel extends ReactiveViewModel {
Future<void> navigateToLearnCourse(int id) async => Future<void> navigateToLearnCourse(int id) async =>
_navigationService.navigateToLearnCourseView(id: id); _navigationService.navigateToLearnCourseView(id: id);
Future<void> navigateToLearnSubscription() async =>
await _navigationService.navigateToLearnSubscriptionView();
// Remote api call // Remote api call
// Learn programs // Learn programs

View File

@ -92,8 +92,10 @@ class LoginOtpScreen extends ViewModelWidget<LoginViewModel> {
Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar( Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody( Widget _buildExpandedBody(

View File

@ -88,8 +88,10 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar( Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody( Widget _buildExpandedBody(

View File

@ -85,8 +85,10 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => viewModel.goTo(0), onPop: () => viewModel.goTo(0),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody( Widget _buildExpandedBody(

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked/stacked_annotations.dart'; import 'package:stacked/stacked_annotations.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/age_group_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/age_group_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/challenge_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/challenge_form_screen.dart';
@ -12,18 +13,16 @@ import 'package:yimaru_app/ui/views/onboarding/screens/language_goal_form_screen
import 'package:yimaru_app/ui/views/onboarding/screens/learning_goal_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/learning_goal_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/occupation_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/occupation_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/topic_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/topic_form_screen.dart';
import 'package:yimaru_app/ui/views/startup/startup_view.dart';
import '../../common/validators/form_validator.dart'; import '../../common/validators/form_validator.dart';
import 'onboarding_viewmodel.dart'; import 'onboarding_viewmodel.dart';
import 'onboarding_view.form.dart'; import 'onboarding_view.form.dart';
@FormView(fields: [ @FormView(fields: [
FormTextField(name: 'topic', validator: FormValidator.validateForm),
FormTextField( FormTextField(
name: 'fullName', validator: FormValidator.validateFullNameForm), name: 'fullName', validator: FormValidator.validateFullNameForm),
FormTextField(name: 'region', validator: FormValidator.validateForm), FormTextField(name: 'region', validator: FormValidator.validateForm),
FormTextField(name: 'challenge', validator: FormValidator.validateForm),
FormTextField(name: 'languageGoal', validator: FormValidator.validateForm),
]) ])
class OnboardingView extends StackedView<OnboardingViewModel> class OnboardingView extends StackedView<OnboardingViewModel>
with $OnboardingView { with $OnboardingView {
@ -34,11 +33,8 @@ class OnboardingView extends StackedView<OnboardingViewModel>
} }
void _initClearData() { void _initClearData() {
topicController.clear();
regionController.clear(); regionController.clear();
fullNameController.clear(); fullNameController.clear();
challengeController.clear();
languageGoalController.clear();
} }
void _clearDataOnNavigation(OnboardingViewModel viewModel) { void _clearDataOnNavigation(OnboardingViewModel viewModel) {
@ -54,17 +50,15 @@ class OnboardingView extends StackedView<OnboardingViewModel>
} else if (viewModel.currentPage == 4) { } else if (viewModel.currentPage == 4) {
viewModel.resetOccupationFormScreen(); viewModel.resetOccupationFormScreen();
} else if (viewModel.currentPage == 5) { } else if (viewModel.currentPage == 5) {
regionController.clear();
viewModel.resetCountryRegionFormScreen(); viewModel.resetCountryRegionFormScreen();
} else if (viewModel.currentPage == 6) { } else if (viewModel.currentPage == 6) {
viewModel.resetLearningGoalFormScreen(); viewModel.resetLearningGoalFormScreen();
} else if (viewModel.currentPage == 7) { } else if (viewModel.currentPage == 7) {
languageGoalController.clear();
viewModel.resetLanguageGoalFormScreen(); viewModel.resetLanguageGoalFormScreen();
} else if (viewModel.currentPage == 8) { } else if (viewModel.currentPage == 8) {
challengeController.clear();
viewModel.resetChallengeFormScreen(); viewModel.resetChallengeFormScreen();
} else if (viewModel.currentPage == 9) { } else if (viewModel.currentPage == 9) {
topicController.clear();
viewModel.resetTopicFormScreen(); viewModel.resetTopicFormScreen();
} }
} }
@ -77,7 +71,7 @@ class OnboardingView extends StackedView<OnboardingViewModel>
} }
@override @override
void onViewModelReady(OnboardingViewModel viewModel) { void onViewModelReady(OnboardingViewModel viewModel) async {
_initClearData(); _initClearData();
_initUserData(viewModel); _initUserData(viewModel);
syncFormWithViewModel(viewModel); syncFormWithViewModel(viewModel);
@ -134,17 +128,13 @@ class OnboardingView extends StackedView<OnboardingViewModel>
Widget _buildOccupationForm() => const OccupationFormScreen(); Widget _buildOccupationForm() => const OccupationFormScreen();
Widget _buildCountryRegionForm() => CountryRegionFormScreen( Widget _buildCountryRegionForm() => CountryRegionFormScreen(regionController: regionController);
regionController: regionController,
);
Widget _buildLearningGoalForm() => const LearningGoalFormScreen(); Widget _buildLearningGoalForm() => const LearningGoalFormScreen();
Widget _buildLanguageGoalForm() => Widget _buildLanguageGoalForm() => const LanguageGoalFormScreen();
LanguageGoalFormScreen(languageGoalController: languageGoalController);
Widget _buildChallengeForm() => Widget _buildChallengeForm() => const ChallengeFormScreen();
ChallengeFormScreen(challengeController: challengeController);
Widget _buildTopicForm() => TopicFormScreen(topicController: topicController); Widget _buildTopicForm() => const TopicFormScreen();
} }

View File

@ -13,11 +13,8 @@ import 'package:yimaru_app/ui/common/validators/form_validator.dart';
const bool _autoTextFieldValidation = true; const bool _autoTextFieldValidation = true;
const String TopicValueKey = 'topic';
const String FullNameValueKey = 'fullName'; const String FullNameValueKey = 'fullName';
const String RegionValueKey = 'region'; const String RegionValueKey = 'region';
const String ChallengeValueKey = 'challenge';
const String LanguageGoalValueKey = 'languageGoal';
final Map<String, TextEditingController> _OnboardingViewTextEditingControllers = final Map<String, TextEditingController> _OnboardingViewTextEditingControllers =
{}; {};
@ -25,31 +22,18 @@ final Map<String, TextEditingController> _OnboardingViewTextEditingControllers =
final Map<String, FocusNode> _OnboardingViewFocusNodes = {}; final Map<String, FocusNode> _OnboardingViewFocusNodes = {};
final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = { final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = {
TopicValueKey: FormValidator.validateForm,
FullNameValueKey: FormValidator.validateFullNameForm, FullNameValueKey: FormValidator.validateFullNameForm,
RegionValueKey: FormValidator.validateForm, RegionValueKey: FormValidator.validateForm,
ChallengeValueKey: FormValidator.validateForm,
LanguageGoalValueKey: FormValidator.validateForm,
}; };
mixin $OnboardingView { mixin $OnboardingView {
TextEditingController get topicController =>
_getFormTextEditingController(TopicValueKey);
TextEditingController get fullNameController => TextEditingController get fullNameController =>
_getFormTextEditingController(FullNameValueKey); _getFormTextEditingController(FullNameValueKey);
TextEditingController get regionController => TextEditingController get regionController =>
_getFormTextEditingController(RegionValueKey); _getFormTextEditingController(RegionValueKey);
TextEditingController get challengeController =>
_getFormTextEditingController(ChallengeValueKey);
TextEditingController get languageGoalController =>
_getFormTextEditingController(LanguageGoalValueKey);
FocusNode get topicFocusNode => _getFormFocusNode(TopicValueKey);
FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey); FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey);
FocusNode get regionFocusNode => _getFormFocusNode(RegionValueKey); FocusNode get regionFocusNode => _getFormFocusNode(RegionValueKey);
FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey);
FocusNode get languageGoalFocusNode =>
_getFormFocusNode(LanguageGoalValueKey);
TextEditingController _getFormTextEditingController( TextEditingController _getFormTextEditingController(
String key, { String key, {
@ -75,11 +59,8 @@ mixin $OnboardingView {
/// Registers a listener on every generated controller that calls [model.setData()] /// Registers a listener on every generated controller that calls [model.setData()]
/// with the latest textController values /// with the latest textController values
void syncFormWithViewModel(FormStateHelper model) { void syncFormWithViewModel(FormStateHelper model) {
topicController.addListener(() => _updateFormData(model));
fullNameController.addListener(() => _updateFormData(model)); fullNameController.addListener(() => _updateFormData(model));
regionController.addListener(() => _updateFormData(model)); regionController.addListener(() => _updateFormData(model));
challengeController.addListener(() => _updateFormData(model));
languageGoalController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation); _updateFormData(model, forceValidate: _autoTextFieldValidation);
} }
@ -91,11 +72,8 @@ mixin $OnboardingView {
'This feature was deprecated after 3.1.0.', 'This feature was deprecated after 3.1.0.',
) )
void listenToFormUpdated(FormViewModel model) { void listenToFormUpdated(FormViewModel model) {
topicController.addListener(() => _updateFormData(model));
fullNameController.addListener(() => _updateFormData(model)); fullNameController.addListener(() => _updateFormData(model));
regionController.addListener(() => _updateFormData(model)); regionController.addListener(() => _updateFormData(model));
challengeController.addListener(() => _updateFormData(model));
languageGoalController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation); _updateFormData(model, forceValidate: _autoTextFieldValidation);
} }
@ -105,11 +83,8 @@ mixin $OnboardingView {
model.setData( model.setData(
model.formValueMap model.formValueMap
..addAll({ ..addAll({
TopicValueKey: topicController.text,
FullNameValueKey: fullNameController.text, FullNameValueKey: fullNameController.text,
RegionValueKey: regionController.text, RegionValueKey: regionController.text,
ChallengeValueKey: challengeController.text,
LanguageGoalValueKey: languageGoalController.text,
}), }),
); );
@ -151,22 +126,8 @@ extension ValueProperties on FormStateHelper {
return !hasAnyValidationMessage; return !hasAnyValidationMessage;
} }
String? get topicValue => this.formValueMap[TopicValueKey] as String?;
String? get fullNameValue => this.formValueMap[FullNameValueKey] as String?; String? get fullNameValue => this.formValueMap[FullNameValueKey] as String?;
String? get regionValue => this.formValueMap[RegionValueKey] as String?; String? get regionValue => this.formValueMap[RegionValueKey] as String?;
String? get challengeValue => this.formValueMap[ChallengeValueKey] as String?;
String? get languageGoalValue =>
this.formValueMap[LanguageGoalValueKey] as String?;
set topicValue(String? value) {
this.setData(
this.formValueMap..addAll({TopicValueKey: value}),
);
if (_OnboardingViewTextEditingControllers.containsKey(TopicValueKey)) {
_OnboardingViewTextEditingControllers[TopicValueKey]?.text = value ?? '';
}
}
set fullNameValue(String? value) { set fullNameValue(String? value) {
this.setData( this.setData(
@ -189,97 +150,41 @@ extension ValueProperties on FormStateHelper {
} }
} }
set challengeValue(String? value) {
this.setData(
this.formValueMap..addAll({ChallengeValueKey: value}),
);
if (_OnboardingViewTextEditingControllers.containsKey(ChallengeValueKey)) {
_OnboardingViewTextEditingControllers[ChallengeValueKey]?.text =
value ?? '';
}
}
set languageGoalValue(String? value) {
this.setData(
this.formValueMap..addAll({LanguageGoalValueKey: value}),
);
if (_OnboardingViewTextEditingControllers.containsKey(
LanguageGoalValueKey)) {
_OnboardingViewTextEditingControllers[LanguageGoalValueKey]?.text =
value ?? '';
}
}
bool get hasTopic =>
this.formValueMap.containsKey(TopicValueKey) &&
(topicValue?.isNotEmpty ?? false);
bool get hasFullName => bool get hasFullName =>
this.formValueMap.containsKey(FullNameValueKey) && this.formValueMap.containsKey(FullNameValueKey) &&
(fullNameValue?.isNotEmpty ?? false); (fullNameValue?.isNotEmpty ?? false);
bool get hasRegion => bool get hasRegion =>
this.formValueMap.containsKey(RegionValueKey) && this.formValueMap.containsKey(RegionValueKey) &&
(regionValue?.isNotEmpty ?? false); (regionValue?.isNotEmpty ?? false);
bool get hasChallenge =>
this.formValueMap.containsKey(ChallengeValueKey) &&
(challengeValue?.isNotEmpty ?? false);
bool get hasLanguageGoal =>
this.formValueMap.containsKey(LanguageGoalValueKey) &&
(languageGoalValue?.isNotEmpty ?? false);
bool get hasTopicValidationMessage =>
this.fieldsValidationMessages[TopicValueKey]?.isNotEmpty ?? false;
bool get hasFullNameValidationMessage => bool get hasFullNameValidationMessage =>
this.fieldsValidationMessages[FullNameValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[FullNameValueKey]?.isNotEmpty ?? false;
bool get hasRegionValidationMessage => bool get hasRegionValidationMessage =>
this.fieldsValidationMessages[RegionValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[RegionValueKey]?.isNotEmpty ?? false;
bool get hasChallengeValidationMessage =>
this.fieldsValidationMessages[ChallengeValueKey]?.isNotEmpty ?? false;
bool get hasLanguageGoalValidationMessage =>
this.fieldsValidationMessages[LanguageGoalValueKey]?.isNotEmpty ?? false;
String? get topicValidationMessage =>
this.fieldsValidationMessages[TopicValueKey];
String? get fullNameValidationMessage => String? get fullNameValidationMessage =>
this.fieldsValidationMessages[FullNameValueKey]; this.fieldsValidationMessages[FullNameValueKey];
String? get regionValidationMessage => String? get regionValidationMessage =>
this.fieldsValidationMessages[RegionValueKey]; this.fieldsValidationMessages[RegionValueKey];
String? get challengeValidationMessage =>
this.fieldsValidationMessages[ChallengeValueKey];
String? get languageGoalValidationMessage =>
this.fieldsValidationMessages[LanguageGoalValueKey];
} }
extension Methods on FormStateHelper { extension Methods on FormStateHelper {
void setTopicValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[TopicValueKey] = validationMessage;
void setFullNameValidationMessage(String? validationMessage) => void setFullNameValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[FullNameValueKey] = validationMessage; this.fieldsValidationMessages[FullNameValueKey] = validationMessage;
void setRegionValidationMessage(String? validationMessage) => void setRegionValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[RegionValueKey] = validationMessage; this.fieldsValidationMessages[RegionValueKey] = validationMessage;
void setChallengeValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[ChallengeValueKey] = validationMessage;
void setLanguageGoalValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[LanguageGoalValueKey] = validationMessage;
/// Clears text input fields on the Form /// Clears text input fields on the Form
void clearForm() { void clearForm() {
topicValue = '';
fullNameValue = ''; fullNameValue = '';
regionValue = ''; regionValue = '';
challengeValue = '';
languageGoalValue = '';
} }
/// Validates text input fields on the Form /// Validates text input fields on the Form
void validateForm() { void validateForm() {
this.setValidationMessages({ this.setValidationMessages({
TopicValueKey: getValidationMessage(TopicValueKey),
FullNameValueKey: getValidationMessage(FullNameValueKey), FullNameValueKey: getValidationMessage(FullNameValueKey),
RegionValueKey: getValidationMessage(RegionValueKey), RegionValueKey: getValidationMessage(RegionValueKey),
ChallengeValueKey: getValidationMessage(ChallengeValueKey),
LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey),
}); });
} }
} }
@ -299,9 +204,6 @@ String? getValidationMessage(String key) {
/// Updates the fieldsValidationMessages on the FormViewModel /// Updates the fieldsValidationMessages on the FormViewModel
void updateValidationData(FormStateHelper model) => void updateValidationData(FormStateHelper model) =>
model.setValidationMessages({ model.setValidationMessages({
TopicValueKey: getValidationMessage(TopicValueKey),
FullNameValueKey: getValidationMessage(FullNameValueKey), FullNameValueKey: getValidationMessage(FullNameValueKey),
RegionValueKey: getValidationMessage(RegionValueKey), RegionValueKey: getValidationMessage(RegionValueKey),
ChallengeValueKey: getValidationMessage(ChallengeValueKey),
LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey),
}); });

View File

@ -3,8 +3,13 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.dart'; import 'package:yimaru_app/app/app.router.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../models/field_option.dart';
import '../../../services/api_service.dart';
import '../../../services/google_auth_service.dart'; import '../../../services/google_auth_service.dart';
import '../../../services/localization_service.dart'; import '../../../services/localization_service.dart';
import '../../../services/onboarding_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart';
class OnboardingViewModel extends ReactiveViewModel class OnboardingViewModel extends ReactiveViewModel
with FormStateHelper with FormStateHelper
@ -15,6 +20,8 @@ class OnboardingViewModel extends ReactiveViewModel
final _googleAuthService = locator<GoogleAuthService>(); final _googleAuthService = locator<GoogleAuthService>();
final _onboardingService = locator<OnboardingService>();
final _localizationService = locator<LocalizationService>(); final _localizationService = locator<LocalizationService>();
@override @override
@ -47,19 +54,15 @@ class OnboardingViewModel extends ReactiveViewModel
bool get focusFullName => _focusFullName; bool get focusFullName => _focusFullName;
// Educational background // Educational background
final List<String> _educationalBackgrounds = [ List<FieldOption> get _educationalBackgrounds =>
'No formal education', _onboardingService.educationalBackgrounds;
'Primary school',
'Secondary /High school',
'College / Diploma',
'Bachelors and above',
];
List<String> get educationalBackgrounds => _educationalBackgrounds; List<FieldOption> get educationalBackgrounds => _educationalBackgrounds;
String? _selectedEducationalBackground; FieldOption? _selectedEducationalBackground;
String? get selectedEducationalBackground => _selectedEducationalBackground; FieldOption? get selectedEducationalBackground =>
_selectedEducationalBackground;
// Gender // Gender
final List<String> _gendersEn = [ final List<String> _gendersEn = [
@ -81,150 +84,73 @@ class OnboardingViewModel extends ReactiveViewModel
String? get selectedGender => _selectedGender; String? get selectedGender => _selectedGender;
// Age group // Age group
final List<Map<String, dynamic>> _ageGroups = [ List<FieldOption> get _ageGroups => _onboardingService.ageGroups;
{
'Under 13': 'UNDER_13',
},
{
'13-17': '13_17',
},
{
'18-24': '18_24',
},
{
'25-34': '25_34',
},
{
'35-44': '35_44',
},
{
'45-54': '45_54',
},
{'55-Plus': '55_PLUS'},
];
List<Map<String, dynamic>> get ageGroups => _ageGroups; List<FieldOption> get ageGroups => _ageGroups;
Map<String, dynamic>? _selectedAgeGroup; FieldOption? _selectedAgeGroup;
Map<String, dynamic>? get selectedAgeGroup => _selectedAgeGroup; FieldOption? get selectedAgeGroup => _selectedAgeGroup;
// Occupation // Occupation
String _selectedOccupation = 'Students (High school & University)'; FieldOption? _selectedOccupation;
String get selectedOccupation => _selectedOccupation; FieldOption? get selectedOccupation => _selectedOccupation;
// Country // Country
String _selectedCountry = 'Ethiopia'; FieldOption? _selectedCountry;
String get selectedCountry => _selectedCountry; FieldOption? get selectedCountry => _selectedCountry;
// Region // Region
bool _focusRegion = false; bool _focusRegion = false;
bool get focusRegion => _focusRegion; bool get focusRegion => _focusRegion;
bool _dropdownRegion = true; bool _dropdownRegion = false;
bool get dropdownRegion => _dropdownRegion; bool get dropdownRegion => _dropdownRegion;
String _selectedRegion = 'Addis Ababa'; FieldOption? _selectedRegion;
String get selectedRegion => _selectedRegion; FieldOption? get selectedRegion => _selectedRegion;
// Learning goal // Learning goal
String? _selectedLearningGoal; FieldOption? _selectedLearningGoal;
String? get selectedLearningGoal => _selectedLearningGoal; FieldOption? get selectedLearningGoal => _selectedLearningGoal;
final List<Map<String, dynamic>> _learningGoals = [ List<FieldOption> get _learningGoals => _onboardingService.learningGoals;
{
'icon': 0,
'title': 'Learn to Speak English',
'subtitle': 'I know some English, but i want to learn to speak it',
},
{
'icon': 1,
'title': 'Practice Speaking English',
'subtitle': 'I already speak English, but I want more practice.',
},
{
'icon': 2,
'title': 'Skill-based Courses',
'subtitle': 'I want courses for IELTS, TOEFL, Duolingo, or work.',
},
];
List<Map<String, dynamic>> get learningGoals => _learningGoals; List<FieldOption> get learningGoals => _learningGoals;
// Learning reason // Learning reason
bool _showLanguageGoalTextBox = false;
bool get showLanguageGoalTextBox => _showLanguageGoalTextBox; FieldOption? _selectedLanguageGoal;
bool _focusLanguageGoal = false; FieldOption? get selectedLanguageGoal => _selectedLanguageGoal;
bool get focusLanguageGoal => _focusLanguageGoal; List<FieldOption> get _languageGoals => _onboardingService.languageGoals;
String? _selectedLanguageGoal; List<FieldOption> get languageGoals => _languageGoals;
String? get selectedLanguageGoal => _selectedLanguageGoal;
final List<String> _languageGoals = [
'Speak confidently at work or school',
'Travel or handle daily situations',
'Connect with family or friends',
'General skills expansion',
'Other'
];
List<String> get languageGoals => _languageGoals;
// Challenges // Challenges
bool _showChallengeTextBox = false; FieldOption? _selectedChallenge;
bool get showChallengeTextBox => _showChallengeTextBox; FieldOption? get selectedChallenge => _selectedChallenge;
bool _focusChallenge = false; List<FieldOption> get _challenges => _onboardingService.challenges;
bool get focusChallenge => _focusChallenge; List<FieldOption> get challenges => _challenges;
String? _selectedChallenge;
String? get selectedChallenge => _selectedChallenge;
final List<String> _challenges = [
'Pronunciation',
'Finding words or grammar quickly',
'Feeling nervous or lacking confidence',
'Understanding accents or fast speech',
'Other'
];
List<String> get challenges => _challenges;
// Topic // Topic
bool _showTopicTextBox = false; FieldOption? _selectedTopic;
bool get showTopicTextBox => _showTopicTextBox; FieldOption? get selectedTopic => _selectedTopic;
bool _focusTopic = false; List<FieldOption> get _topics => _onboardingService.topics;
bool get focusTopic => _focusTopic; List<FieldOption> get topics => _topics;
String? _selectedTopic;
String? get selectedTopic => _selectedTopic;
final List<String> _topics = [
'Food & Cooking',
'Hobbies, Sports, Music',
'Tech, News, Business',
'Travel, Places, Culture',
'Other'
];
List<String> get topics => _topics;
// User data // User data
final Map<String, dynamic> _userData = {}; final Map<String, dynamic> _userData = {};
@ -238,12 +164,12 @@ class OnboardingViewModel extends ReactiveViewModel
} }
// Education background // Education background
void setSelectedEducationalBackground(String value) { void setSelectedEducationalBackground(FieldOption value) {
_selectedEducationalBackground = value; _selectedEducationalBackground = value;
rebuildUi(); rebuildUi();
} }
bool isSelectedEducationalBackground(String value) => bool isSelectedEducationalBackground(FieldOption value) =>
_selectedEducationalBackground == value; _selectedEducationalBackground == value;
// Gender // Gender
@ -255,192 +181,35 @@ class OnboardingViewModel extends ReactiveViewModel
bool isSelectedGender(String value) => _selectedGender == value; bool isSelectedGender(String value) => _selectedGender == value;
// Age group // Age group
void setSelectedAgeGroup(Map<String, dynamic> value) { void setSelectedAgeGroup(FieldOption value) {
_selectedAgeGroup = value; _selectedAgeGroup = value;
rebuildUi(); rebuildUi();
} }
bool isSelectedAgeGroup(Map<String, dynamic> value) => bool isSelectedAgeGroup(FieldOption value) => _selectedAgeGroup == value;
_selectedAgeGroup == value;
// Occupation // Occupation
List<String> getOccupations() => [ List<FieldOption> get _occupations => _onboardingService.occupations;
'Students (High school & University)',
'Job Seekers / Fresh Graduates',
'Working Professionals (Corporate/Office)',
'Government & NGO Workers',
'Entrepreneurs & Small Business Owners',
'Hospitality & Tourism Workers',
'Freelancers / Remote Workers (Digital Economy)'
];
void setSelectedOccupation(String value) { List<FieldOption> get occupations => _occupations;
void setSelectedOccupation(FieldOption? value) {
_selectedOccupation = value; _selectedOccupation = value;
rebuildUi(); rebuildUi();
} }
// Country // Country
List<String> getCountries() => [ List<FieldOption> get _countries => _onboardingService.countries;
"Afghanistan",
"Albania",
"Algeria",
"Andorra",
"Angola",
"Argentina",
"Armenia",
"Australia",
"Austria",
"Azerbaijan",
"Bahrain",
"Bangladesh",
"Belarus",
"Belgium",
"Belize",
"Benin",
"Bhutan",
"Bolivia",
"Bosnia and Herzegovina",
"Botswana",
"Brazil",
"Brunei",
"Bulgaria",
"Burkina Faso",
"Burundi",
"Cambodia",
"Cameroon",
"Canada",
"Chad",
"Chile",
"China",
"Colombia",
"Comoros",
"Congo",
"Costa Rica",
"Croatia",
"Cuba",
"Cyprus",
"Czech Republic",
"Denmark",
"Djibouti",
"Dominican Republic",
"Ecuador",
"Egypt",
"El Salvador",
"Eritrea",
"Estonia",
"Eswatini",
"Ethiopia",
"Finland",
"France",
"Gabon",
"Gambia",
"Georgia",
"Germany",
"Ghana",
"Greece",
"Guatemala",
"Guinea",
"Haiti",
"Honduras",
"Hungary",
"Iceland",
"India",
"Indonesia",
"Iran",
"Iraq",
"Ireland",
"Israel",
"Italy",
"Jamaica",
"Japan",
"Jordan",
"Kazakhstan",
"Kenya",
"Kuwait",
"Kyrgyzstan",
"Laos",
"Latvia",
"Lebanon",
"Liberia",
"Libya",
"Lithuania",
"Luxembourg",
"Madagascar",
"Malawi",
"Malaysia",
"Maldives",
"Mali",
"Malta",
"Mexico",
"Moldova",
"Monaco",
"Mongolia",
"Morocco",
"Mozambique",
"Myanmar",
"Namibia",
"Nepal",
"Netherlands",
"New Zealand",
"Nicaragua",
"Niger",
"Nigeria",
"North Korea",
"Norway",
"Oman",
"Pakistan",
"Panama",
"Paraguay",
"Peru",
"Philippines",
"Poland",
"Portugal",
"Qatar",
"Romania",
"Russia",
"Rwanda",
"Saudi Arabia",
"Senegal",
"Serbia",
"Singapore",
"Slovakia",
"Slovenia",
"Somalia",
"South Africa",
"South Korea",
"Spain",
"Sri Lanka",
"Sudan",
"Sweden",
"Switzerland",
"Syria",
"Taiwan",
"Tajikistan",
"Tanzania",
"Thailand",
"Tunisia",
"Turkey",
"Uganda",
"Ukraine",
"United Arab Emirates",
"United Kingdom",
"United States",
"Uruguay",
"Uzbekistan",
"Venezuela",
"Vietnam",
"Yemen",
"Zambia",
"Zimbabwe"
];
void setSelectedCountry(String value) { List<FieldOption> get countries => _countries;
void setSelectedCountry(FieldOption? value) {
_selectedCountry = value; _selectedCountry = value;
if (value?.label?.toLowerCase().trim() == 'ethiopia') {
if (value == 'Ethiopia') {
_dropdownRegion = true; _dropdownRegion = true;
_selectedRegion = 'Addis Ababa'; _selectedRegion = _regions
.firstWhere((e) => e.label?.toLowerCase().trim() == 'addis ababa');
} else { } else {
_dropdownRegion = false; _dropdownRegion = false;
} }
@ -449,24 +218,11 @@ class OnboardingViewModel extends ReactiveViewModel
} }
// Region // Region
List<String> getRegions() => [ List<FieldOption> get _regions => _onboardingService.regions;
'Addis Ababa',
'Afar',
'Amhara',
'Benishangul-Gumuz',
'Central Ethiopia',
'Dire Dawa',
'Gambela',
'Harari',
'Oromia',
'Sidama',
'Somali',
'South Ethiopia',
'South West Ethiopia Peoples',
'Tigray',
];
void setSelectedRegion(String value) { List<FieldOption> get regions => _regions;
void setSelectedRegion(FieldOption? value) {
_selectedRegion = value; _selectedRegion = value;
rebuildUi(); rebuildUi();
} }
@ -482,75 +238,42 @@ class OnboardingViewModel extends ReactiveViewModel
} }
// Learning goal // Learning goal
void setSelectedLearningGoal(String value) { void setSelectedLearningGoal(FieldOption value) {
_selectedLearningGoal = value; _selectedLearningGoal = value;
rebuildUi(); rebuildUi();
} }
bool isSelectedLearningGoal(String value) => _selectedLearningGoal == value; bool isSelectedLearningGoal(FieldOption value) =>
_selectedLearningGoal == value;
// Learning reason // Learning reason
void setLanguageGoalFocus() {
_focusLanguageGoal = true;
rebuildUi();
}
void setSelectedLanguageGoal(String value) { void setSelectedLanguageGoal(FieldOption value) {
_selectedLanguageGoal = value; _selectedLanguageGoal = value;
if (value.toLowerCase() == 'other') {
_showLanguageGoalTextBox = true;
} else {
if (_showLanguageGoalTextBox) {
_showLanguageGoalTextBox = false;
_focusLanguageGoal = false;
}
}
rebuildUi(); rebuildUi();
} }
bool isSelectedLanguageGoal(String value) => _selectedLanguageGoal == value; bool isSelectedLanguageGoal(FieldOption value) =>
_selectedLanguageGoal == value;
// Challenges // Challenges
void setChallengesFocus() {
_focusChallenge = true;
rebuildUi();
}
void setSelectedChallenge(String value) { void setSelectedChallenge(FieldOption value) {
_selectedChallenge = value; _selectedChallenge = value;
if (value.toLowerCase() == 'other') {
_showChallengeTextBox = true;
} else {
if (_showChallengeTextBox) {
_showChallengeTextBox = false;
_focusChallenge = false;
}
}
rebuildUi(); rebuildUi();
} }
bool isSelectedChallenge(String value) => _selectedChallenge == value; bool isSelectedChallenge(FieldOption value) => _selectedChallenge == value;
// Topics // Topics
void setTopicsFocus() {
_focusTopic = true;
rebuildUi();
}
void setSelectedTopic(String value) { void setSelectedTopic(FieldOption value) {
_selectedTopic = value; _selectedTopic = value;
if (value.toLowerCase() == 'other') {
_showTopicTextBox = true;
} else {
if (_showTopicTextBox) {
_showTopicTextBox = false;
_focusTopic = false;
}
}
rebuildUi(); rebuildUi();
} }
bool isSelectedTopic(String value) => _selectedTopic == value; bool isSelectedTopic(FieldOption value) => _selectedTopic == value;
// Add user data // Add user data
void addUserData(Map<String, dynamic> data) { void addUserData(Map<String, dynamic> data) {
@ -589,15 +312,15 @@ class OnboardingViewModel extends ReactiveViewModel
// Reset occupation form screen // Reset occupation form screen
void resetOccupationFormScreen() { void resetOccupationFormScreen() {
_selectedOccupation = 'Students (High school & University)'; _selectedOccupation = null;
rebuildUi(); rebuildUi();
} }
// Reset country region form screen // Reset country region form screen
void resetCountryRegionFormScreen() { void resetCountryRegionFormScreen() {
_focusRegion = false; _focusRegion = false;
_selectedCountry = 'Ethiopia'; _selectedRegion = null;
_selectedRegion = 'Addis Ababa'; _selectedCountry = null;
rebuildUi(); rebuildUi();
} }
@ -609,26 +332,20 @@ class OnboardingViewModel extends ReactiveViewModel
// Reset language goal form screen // Reset language goal form screen
void resetLanguageGoalFormScreen() { void resetLanguageGoalFormScreen() {
_focusLanguageGoal = false;
_selectedLanguageGoal = null; _selectedLanguageGoal = null;
_showLanguageGoalTextBox = false;
rebuildUi(); rebuildUi();
} }
// Reset challenge form screen // Reset challenge form screen
void resetChallengeFormScreen() { void resetChallengeFormScreen() {
_focusChallenge = false;
_selectedChallenge = null; _selectedChallenge = null;
_showChallengeTextBox = false;
rebuildUi(); rebuildUi();
} }
// Reset topic form screen // Reset topic form screen
void resetTopicFormScreen() { void resetTopicFormScreen() {
_focusTopic = false;
_selectedTopic = null; _selectedTopic = null;
_showTopicTextBox = false;
rebuildUi(); rebuildUi();
} }

View File

@ -1,6 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
@ -19,9 +21,7 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
Future<void> _next(OnboardingViewModel viewModel) async { Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = { Map<String, dynamic> data = {'age_group': viewModel.selectedAgeGroup?.code};
'age_group': viewModel.selectedAgeGroup?.values.first
};
viewModel.addUserData(data); viewModel.addUserData(data);
viewModel.next(); viewModel.next();
@ -88,17 +88,19 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _pop(viewModel), onPop: () => _pop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'Which age range are you in?', LocaleKeys.age_range.tr(),
style: style25DG600, style: style25DG600,
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
'Well personalize your learning experience based on your age.', LocaleKeys.age_for_personalization.tr(),
style: style14DG400, style: style14DG400,
); );
@ -107,7 +109,7 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
itemCount: viewModel.ageGroups.length, itemCount: viewModel.ageGroups.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildAgeGroup( itemBuilder: (context, index) => _buildAgeGroup(
title: viewModel.ageGroups[index].keys.first, title: viewModel.ageGroups[index].label ?? '',
selected: viewModel.isSelectedAgeGroup(viewModel.ageGroups[index]), selected: viewModel.isSelectedAgeGroup(viewModel.ageGroups[index]),
onTap: () => onTap: () =>
viewModel.setSelectedAgeGroup(viewModel.ageGroups[index]), viewModel.setSelectedAgeGroup(viewModel.ageGroups[index]),
@ -132,9 +134,9 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: LocaleKeys.cont.tr(),
backgroundColor: viewModel.selectedAgeGroup != null backgroundColor: viewModel.selectedAgeGroup != null
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),

View File

@ -1,6 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_view.form.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_view.form.dart';
@ -9,12 +11,9 @@ import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> { class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
final TextEditingController challengeController; const ChallengeFormScreen({super.key});
const ChallengeFormScreen({super.key, required this.challengeController});
void _pop(OnboardingViewModel viewModel) { void _pop(OnboardingViewModel viewModel) {
challengeController.clear();
viewModel.resetChallengeFormScreen(); viewModel.resetChallengeFormScreen();
viewModel.goBack(); viewModel.goBack();
} }
@ -23,8 +22,7 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = { Map<String, dynamic> data = {
'language_challange': 'language_challange': viewModel.selectedChallenge?.code,
viewModel.selectedChallenge ?? challengeController.text,
}; };
viewModel.addUserData(data); viewModel.addUserData(data);
@ -86,15 +84,6 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
_buildSubtitle(), _buildSubtitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildChallenges(viewModel), _buildChallenges(viewModel),
if (viewModel.showChallengeTextBox) _buildChallengeFormField(viewModel),
if (viewModel.showChallengeTextBox &&
viewModel.hasChallengeValidationMessage &&
viewModel.focusChallenge)
verticalSpaceTiny,
if (viewModel.showChallengeTextBox &&
viewModel.hasChallengeValidationMessage &&
viewModel.focusChallenge)
_buildChallengeValidatorWrapper(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
]; ];
@ -102,17 +91,19 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _pop(viewModel), onPop: () => _pop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'What challenge do you face most with English?', LocaleKeys.challenge_you_face.tr(),
style: style25DG600, style: style25DG600,
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
'Everyone has struggles, lets start fixing yours 😊', '${LocaleKeys.evey_one_has_strugle.tr()} 😊',
style: style14MG400, style: style14MG400,
); );
@ -122,7 +113,7 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
itemCount: viewModel.challenges.length, itemCount: viewModel.challenges.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildChallenge( itemBuilder: (context, index) => _buildChallenge(
title: viewModel.challenges[index], title: viewModel.challenges[index].label ?? '',
onTap: () => onTap: () =>
viewModel.setSelectedChallenge(viewModel.challenges[index]), viewModel.setSelectedChallenge(viewModel.challenges[index]),
selected: viewModel.isSelectedChallenge(viewModel.challenges[index]), selected: viewModel.isSelectedChallenge(viewModel.challenges[index]),
@ -139,27 +130,6 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
selected: selected, selected: selected,
); );
Widget _buildChallengeFormField(OnboardingViewModel viewModel) =>
TextFormField(
maxLines: 3,
controller: challengeController,
onTap: viewModel.setChallengesFocus,
decoration: inputDecoration(
focus: true,
hint: 'Write your challenge…',
filled: challengeController.text.isNotEmpty),
);
Widget _buildChallengeValidatorWrapper(OnboardingViewModel viewModel) =>
viewModel.hasChallengeValidationMessage
? _buildChallengeValidator(viewModel)
: Container();
Widget _buildChallengeValidator(OnboardingViewModel viewModel) => Text(
viewModel.challengeValidationMessage!,
style: style12R700,
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
@ -168,21 +138,13 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: LocaleKeys.cont.tr(),
onTap: viewModel.selectedChallenge != null onTap: viewModel.selectedChallenge != null
? viewModel.selectedChallenge?.toLowerCase() == 'other'
? challengeController.text.isNotEmpty
? () => _next(viewModel) ? () => _next(viewModel)
: null
: () => _next(viewModel)
: null, : null,
backgroundColor: viewModel.selectedChallenge != null backgroundColor: viewModel.selectedChallenge != null
? viewModel.selectedChallenge?.toLowerCase() == 'other'
? challengeController.text.isNotEmpty
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1)
: kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1)); : kcPrimaryColor.withOpacity(0.1));
} }

View File

@ -1,11 +1,14 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_dropdown.dart'; import 'package:yimaru_app/ui/widgets/custom_dropdown.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
import '../../../../models/field_option.dart';
import '../onboarding_view.form.dart'; import '../onboarding_view.form.dart';
class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> { class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
@ -13,10 +16,10 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
const CountryRegionFormScreen({super.key, required this.regionController}); const CountryRegionFormScreen({super.key, required this.regionController});
void _setSelectedCountry( void _setSelectedCountry(
{String? value, required OnboardingViewModel viewModel}) { {FieldOption? value, required OnboardingViewModel viewModel}) {
viewModel.setSelectedCountry(value ?? 'Ethiopia'); viewModel.setSelectedCountry(value);
if (viewModel.selectedCountry != 'Ethiopia') { if (viewModel.selectedCountry?.label?.toLowerCase().tr() != 'ethiopia') {
regionController.clear(); regionController.clear();
viewModel.unsetRegionFocus(); viewModel.unsetRegionFocus();
} }
@ -32,9 +35,9 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
Map<String, dynamic> data = { Map<String, dynamic> data = {
'region': viewModel.dropdownRegion 'region': viewModel.dropdownRegion
? viewModel.selectedRegion ? viewModel.selectedRegion?.code
: regionController.text, : regionController.text,
'country': viewModel.selectedCountry, 'country': viewModel.selectedCountry?.code,
}; };
viewModel.addUserData(data); viewModel.addUserData(data);
@ -112,26 +115,28 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _pop(viewModel), onPop: () => _pop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'Where are you from?', LocaleKeys.location.tr(),
style: style25DG600, style: style25DG600,
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
'Select your country and region from the dropdown', LocaleKeys.select_country_region.tr(),
style: style14MG400, style: style14MG400,
); );
Widget _buildCountryDropDown(OnboardingViewModel viewModel) => Widget _buildCountryDropDown(OnboardingViewModel viewModel) =>
CustomDropdownPicker( CustomDropdownPicker(
hint: 'Select country',
icon: _buildSearchIcon(), icon: _buildSearchIcon(),
hint: LocaleKeys.select_country.tr(),
selectedItem: viewModel.selectedCountry, selectedItem: viewModel.selectedCountry,
items: (value, props) => viewModel.getCountries(), items: (value, props) => viewModel.countries,
onChanged: (value) => onChanged: (value) =>
_setSelectedCountry(value: value, viewModel: viewModel)); _setSelectedCountry(value: value, viewModel: viewModel));
@ -142,19 +147,18 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildRegionDropDown(OnboardingViewModel viewModel) => Widget _buildRegionDropDown(OnboardingViewModel viewModel) =>
CustomDropdownPicker( CustomDropdownPicker(
hint: 'Select region',
icon: _buildSearchIcon(), icon: _buildSearchIcon(),
hint: LocaleKeys.select_region.tr(),
selectedItem: viewModel.selectedRegion, selectedItem: viewModel.selectedRegion,
items: (value, props) => viewModel.getRegions(), items: (value, props) => viewModel.regions,
onChanged: (value) => onChanged: (value) => viewModel.setSelectedRegion(value));
viewModel.setSelectedRegion(value ?? 'Addis Ababa'));
Widget _buildRegionFormField(OnboardingViewModel viewModel) => TextFormField( Widget _buildRegionFormField(OnboardingViewModel viewModel) => TextFormField(
controller: regionController, controller: regionController,
onTap: viewModel.setRegionFocus, onTap: viewModel.setRegionFocus,
decoration: inputDecoration( decoration: inputDecoration(
hint: 'Enter Your City',
focus: viewModel.focusRegion, focus: viewModel.focusRegion,
hint: LocaleKeys.enter_your_city.tr(),
filled: regionController.text.isNotEmpty), filled: regionController.text.isNotEmpty),
); );
@ -181,18 +185,26 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: !viewModel.dropdownRegion text: LocaleKeys.cont.tr(),
onTap: viewModel.selectedCountry != null
? !viewModel.dropdownRegion
? regionController.text.isNotEmpty ? regionController.text.isNotEmpty
? () => _next(viewModel) ? () => _next(viewModel)
: null : null
: () => _next(viewModel), : viewModel.selectedRegion != null
backgroundColor: !viewModel.dropdownRegion ? () => _next(viewModel)
: null
: null,
backgroundColor: viewModel.selectedCountry != null
? !viewModel.dropdownRegion
? regionController.text.isNotEmpty ? regionController.text.isNotEmpty
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1) : kcPrimaryColor.withOpacity(0.1)
: kcPrimaryColor, : viewModel.selectedRegion != null
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1)
: kcPrimaryColor.withOpacity(0.1),
); );
} }

View File

@ -1,6 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
@ -20,7 +22,7 @@ class EducationalBackgroundFormScreen
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = { Map<String, dynamic> data = {
'education_level': viewModel.selectedEducationalBackground 'education_level': viewModel.selectedEducationalBackground?.code
}; };
viewModel.addUserData(data); viewModel.addUserData(data);
@ -88,22 +90,20 @@ class EducationalBackgroundFormScreen
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _pop(viewModel), onPop: () => _pop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildTitle() => const Text( Widget _buildTitle() => Text(
'Whats your current educational level?', LocaleKeys.educational_background.tr(),
style: TextStyle( style: style25DG600,
fontSize: 25,
color: kcDarkGrey,
fontWeight: FontWeight.w600,
),
); );
Widget _buildSubtitle() => const Text( Widget _buildSubtitle() => Text(
'This helps us tailor your lessons to your experience.', LocaleKeys.education_for_personalization.tr(),
style: TextStyle(color: kcMediumGrey), style: style14MG400,
); );
Widget _buildEducationalLevels(OnboardingViewModel viewModel) => Widget _buildEducationalLevels(OnboardingViewModel viewModel) =>
@ -112,7 +112,7 @@ class EducationalBackgroundFormScreen
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: viewModel.educationalBackgrounds.length, itemCount: viewModel.educationalBackgrounds.length,
itemBuilder: (context, index) => _buildEducationalLevel( itemBuilder: (context, index) => _buildEducationalLevel(
title: viewModel.educationalBackgrounds[index], title: viewModel.educationalBackgrounds[index].label ?? '',
selected: viewModel.isSelectedEducationalBackground( selected: viewModel.isSelectedEducationalBackground(
viewModel.educationalBackgrounds[index]), viewModel.educationalBackgrounds[index]),
onTap: () => viewModel.setSelectedEducationalBackground( onTap: () => viewModel.setSelectedEducationalBackground(
@ -138,9 +138,9 @@ class EducationalBackgroundFormScreen
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: LocaleKeys.cont.tr(),
onTap: viewModel.selectedEducationalBackground != null onTap: viewModel.selectedEducationalBackground != null
? () => _next(viewModel) ? () => _next(viewModel)
: null, : null,

View File

@ -90,18 +90,20 @@ class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar( Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'${LocaleKeys.what_should_we_call_you.tr()} 😊', '${LocaleKeys.what_should_we_call_you.tr()} 😊',
style:style25DG600, style: style25DG600,
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
LocaleKeys.name_for_personalization.tr(), LocaleKeys.name_for_personalization.tr(),
style:style14MG400, style: style14MG400,
); );
Widget _buildFullNameFormField(OnboardingViewModel viewModel) => Widget _buildFullNameFormField(OnboardingViewModel viewModel) =>

View File

@ -82,8 +82,10 @@ class GenderFormScreen extends ViewModelWidget<OnboardingViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _pop(viewModel), onPop: () => _pop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
@ -98,14 +100,22 @@ class GenderFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildAgeGroups(OnboardingViewModel viewModel) => ListView.builder( Widget _buildAgeGroups(OnboardingViewModel viewModel) => ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: viewModel.selectedLanguage['code'] == 'አማ' physics: const NeverScrollableScrollPhysics(),
itemCount: viewModel.selectedLanguage['code'] == 'am'
? viewModel.gendersAm.length ? viewModel.gendersAm.length
: viewModel.gendersEn.length, : viewModel.gendersEn.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildAgeGroup( itemBuilder: (context, index) => _buildAgeGroup(
title:viewModel.selectedLanguage['code'] == 'አማ' ? viewModel.gendersAm[index]:viewModel.gendersEn[index], selected: viewModel.isSelectedGender(
selected: viewModel.isSelectedGender(viewModel.selectedLanguage['code'] == 'አማ' ? viewModel.gendersAm[index]: viewModel.gendersEn[index]), viewModel.selectedLanguage['code'] == 'am'
onTap: () => viewModel.setSelectedGender(viewModel.selectedLanguage['code'] == 'አማ' ? viewModel.gendersAm[index]: viewModel.gendersEn[index]), ? viewModel.gendersAm[index]
: viewModel.gendersEn[index]),
onTap: () => viewModel.setSelectedGender(
viewModel.selectedLanguage['code'] == 'am'
? viewModel.gendersAm[index]
: viewModel.gendersEn[index]),
title: viewModel.selectedLanguage['code'] == 'am'
? viewModel.gendersAm[index]
: viewModel.gendersEn[index],
), ),
); );

View File

@ -1,6 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_view.form.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_view.form.dart';
@ -9,13 +11,9 @@ import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> { class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
final TextEditingController languageGoalController; const LanguageGoalFormScreen({super.key});
const LanguageGoalFormScreen(
{super.key, required this.languageGoalController});
void _pop(OnboardingViewModel viewModel) { void _pop(OnboardingViewModel viewModel) {
languageGoalController.clear();
viewModel.resetLanguageGoalFormScreen(); viewModel.resetLanguageGoalFormScreen();
viewModel.goBack(); viewModel.goBack();
} }
@ -24,8 +22,7 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = { Map<String, dynamic> data = {
'language_goal': 'language_goal': viewModel.selectedLanguageGoal?.code,
viewModel.selectedLanguageGoal ?? languageGoalController.text,
}; };
viewModel.addUserData(data); viewModel.addUserData(data);
@ -87,15 +84,6 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
_buildSubtitle(), _buildSubtitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildReasons(viewModel), _buildReasons(viewModel),
if (viewModel.showLanguageGoalTextBox) _buildReasonFormField(viewModel),
if (viewModel.showLanguageGoalTextBox &&
viewModel.hasLanguageGoalValidationMessage &&
viewModel.focusLanguageGoal)
verticalSpaceTiny,
if (viewModel.showLanguageGoalTextBox &&
viewModel.hasLanguageGoalValidationMessage &&
viewModel.focusLanguageGoal)
_buildReasonValidatorWrapper(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
]; ];
@ -103,17 +91,19 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _pop(viewModel), onPop: () => _pop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'Whats your main goal for improving your English?', LocaleKeys.language_goal.tr(),
style: style25DG600, style: style25DG600,
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
'Your goal helps us tailor your learning journey.', LocaleKeys.your_goal.tr(),
style: style14MG400, style: style14MG400,
); );
@ -123,7 +113,7 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
itemCount: viewModel.languageGoals.length, itemCount: viewModel.languageGoals.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildLanguageGoal( itemBuilder: (context, index) => _buildLanguageGoal(
title: viewModel.languageGoals[index], title: viewModel.languageGoals[index].label ?? '',
selected: selected:
viewModel.isSelectedLanguageGoal(viewModel.languageGoals[index]), viewModel.isSelectedLanguageGoal(viewModel.languageGoals[index]),
onTap: () => onTap: () =>
@ -141,26 +131,6 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
selected: selected, selected: selected,
); );
Widget _buildReasonFormField(OnboardingViewModel viewModel) => TextFormField(
maxLines: 3,
controller: languageGoalController,
onTap: viewModel.setLanguageGoalFocus,
decoration: inputDecoration(
focus: true,
hint: 'Write your goal…',
filled: languageGoalController.text.isNotEmpty),
);
Widget _buildReasonValidatorWrapper(OnboardingViewModel viewModel) =>
viewModel.hasLanguageGoalValidationMessage
? _buildReasonValidator(viewModel)
: Container();
Widget _buildReasonValidator(OnboardingViewModel viewModel) => Text(
viewModel.languageGoalValidationMessage!,
style: style12R700,
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
@ -169,21 +139,13 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: LocaleKeys.cont.tr(),
onTap: viewModel.selectedLanguageGoal != null onTap: viewModel.selectedLanguageGoal != null
? viewModel.selectedLanguageGoal?.toLowerCase() == 'other'
? languageGoalController.text.isNotEmpty
? () => _next(viewModel) ? () => _next(viewModel)
: null
: () => _next(viewModel)
: null, : null,
backgroundColor: viewModel.selectedLanguageGoal != null backgroundColor: viewModel.selectedLanguageGoal != null
? viewModel.selectedLanguageGoal?.toLowerCase() == 'other'
? languageGoalController.text.isNotEmpty
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1)
: kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1)); : kcPrimaryColor.withOpacity(0.1));
} }

View File

@ -1,7 +1,9 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:iconsax/iconsax.dart'; import 'package:iconsax/iconsax.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
@ -32,7 +34,7 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = { Map<String, dynamic> data = {
'learning_goal': viewModel.selectedLearningGoal, 'learning_goal': viewModel.selectedLearningGoal?.code,
}; };
viewModel.addUserData(data); viewModel.addUserData(data);
@ -98,17 +100,20 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _pop(viewModel), onPop: () => _pop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildTitle(OnboardingViewModel viewModel) => Text.rich( Widget _buildTitle(OnboardingViewModel viewModel) => Text.rich(
TextSpan( TextSpan(
text: 'Hi ${viewModel.userData['first_name']},', text:
'${LocaleKeys.hello.tr()} ${viewModel.userData['first_name']},',
style: style18P600.copyWith(fontSize: 22), style: style18P600.copyWith(fontSize: 22),
children: [ children: [
TextSpan( TextSpan(
text: ' Choose your learning goal.', text: ' ${LocaleKeys.learning_goal.tr()}',
style: style16DG600.copyWith(fontSize: 22), style: style16DG600.copyWith(fontSize: 22),
) )
]), ]),
@ -119,27 +124,21 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
itemCount: viewModel.learningGoals.length, itemCount: viewModel.learningGoals.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildLearningGoal( itemBuilder: (context, index) => _buildLearningGoal(
title: viewModel.learningGoals[index]['title'], title: viewModel.learningGoals[index].label ?? '',
icon: getIcon(viewModel.learningGoals[index]['icon']), selected:
subtitle: viewModel.learningGoals[index]['subtitle'], viewModel.isSelectedLearningGoal(viewModel.learningGoals[index]),
selected: viewModel onTap: () =>
.isSelectedLearningGoal(viewModel.learningGoals[index]['title']), viewModel.setSelectedLearningGoal(viewModel.learningGoals[index]),
onTap: () => viewModel
.setSelectedLearningGoal(viewModel.learningGoals[index]['title']),
), ),
); );
Widget _buildLearningGoal( Widget _buildLearningGoal(
{required String title, {required String title,
required bool selected, required bool selected,
required IconData icon,
required String subtitle,
required GestureTapCallback onTap}) => required GestureTapCallback onTap}) =>
CustomLargeRadioButton( CustomLargeRadioButton(
icon: icon,
title: title, title: title,
onTap: onTap, onTap: onTap,
subtitle: subtitle,
selected: selected, selected: selected,
); );
@ -151,9 +150,9 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: LocaleKeys.cont.tr(),
onTap: viewModel.selectedLearningGoal != null onTap: viewModel.selectedLearningGoal != null
? () => _next(viewModel) ? () => _next(viewModel)
: null, : null,

View File

@ -1,6 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
@ -19,7 +21,9 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
Future<void> _next(OnboardingViewModel viewModel) async { Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {'occupation': viewModel.selectedOccupation}; Map<String, dynamic> data = {
'occupation': viewModel.selectedOccupation?.code
};
viewModel.addUserData(data); viewModel.addUserData(data);
viewModel.next(); viewModel.next();
@ -86,28 +90,30 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _pop(viewModel), onPop: () => _pop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'Whats your occupation?', LocaleKeys.your_occupation.tr(),
style: style25DG600, style: style25DG600,
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
'Well personalize your learning experience based on your occupation.', LocaleKeys.occupation_for_personalization.tr(),
style: style14MG400, style: style14MG400,
); );
Widget _buildOccupationDropdown(OnboardingViewModel viewModel) => Widget _buildOccupationDropdown(OnboardingViewModel viewModel) =>
CustomDropdownPicker( CustomDropdownPicker(
hint: 'Select occupation',
icon: _buildSearchIcon(), icon: _buildSearchIcon(),
hint: LocaleKeys.select_occupation.tr(),
selectedItem: viewModel.selectedOccupation, selectedItem: viewModel.selectedOccupation,
items: (value, props) => viewModel.getOccupations(), items: (value, props) => viewModel.occupations,
onChanged: (value) => viewModel.setSelectedOccupation( onChanged: (value) => viewModel.setSelectedOccupation(value));
value ?? 'Students (High school & University)'));
Icon _buildSearchIcon() => const Icon( Icon _buildSearchIcon() => const Icon(
Icons.search, Icons.search,
color: kcPrimaryColor, color: kcPrimaryColor,
@ -121,9 +127,14 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: () => _next(viewModel), text: LocaleKeys.cont.tr(),
backgroundColor: kcPrimaryColor); onTap: viewModel.selectedOccupation != null
? () => _next(viewModel)
: null,
backgroundColor: viewModel.selectedOccupation != null
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
);
} }

View File

@ -1,7 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_view.form.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_view.form.dart';
@ -10,12 +11,9 @@ import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> { class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
final TextEditingController topicController; const TopicFormScreen({super.key});
const TopicFormScreen({super.key, required this.topicController});
void _pop(OnboardingViewModel viewModel) { void _pop(OnboardingViewModel viewModel) {
topicController.clear();
viewModel.resetTopicFormScreen(); viewModel.resetTopicFormScreen();
viewModel.goBack(); viewModel.goBack();
} }
@ -26,8 +24,8 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
Map<String, dynamic> data = { Map<String, dynamic> data = {
'profile_completed': true, 'profile_completed': true,
'preferred_language': 'en', 'preferred_language': 'en',
'favoutite_topic': viewModel.selectedTopic?.code,
'birth_day': DateFormat('yyyy-MM-dd').format(DateTime.now()), 'birth_day': DateFormat('yyyy-MM-dd').format(DateTime.now()),
'favoutite_topic': viewModel.selectedTopic ?? topicController.text,
}; };
viewModel.addUserData(data); viewModel.addUserData(data);
@ -58,8 +56,10 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _pop(viewModel), onPop: () => _pop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody(OnboardingViewModel viewModel) => Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
@ -97,25 +97,16 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
_buildSubtitle(), _buildSubtitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildTopics(viewModel), _buildTopics(viewModel),
if (viewModel.showTopicTextBox) _buildTopicFormField(viewModel),
if (viewModel.showTopicTextBox &&
viewModel.hasTopicValidationMessage &&
viewModel.focusTopic)
verticalSpaceTiny,
if (viewModel.showTopicTextBox &&
viewModel.hasTopicValidationMessage &&
viewModel.focusTopic)
_buildTopicWrapper(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
]; ];
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'Which topics interest you most?', LocaleKeys.topic_interest.tr(),
style: style25DG600, style: style25DG600,
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
'Your favorite topics help us create fun, relatable lessons.', LocaleKeys.favourite_topic.tr(),
style: style14MG400, style: style14MG400,
); );
@ -125,7 +116,7 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
itemCount: viewModel.topics.length, itemCount: viewModel.topics.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTopic( itemBuilder: (context, index) => _buildTopic(
title: viewModel.topics[index], title: viewModel.topics[index].label ?? '',
selected: viewModel.isSelectedTopic(viewModel.topics[index]), selected: viewModel.isSelectedTopic(viewModel.topics[index]),
onTap: () => viewModel.setSelectedTopic(viewModel.topics[index]), onTap: () => viewModel.setSelectedTopic(viewModel.topics[index]),
), ),
@ -141,26 +132,6 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
selected: selected, selected: selected,
); );
Widget _buildTopicFormField(OnboardingViewModel viewModel) => TextFormField(
maxLines: 3,
controller: topicController,
onTap: viewModel.setTopicsFocus,
decoration: inputDecoration(
focus: true,
hint: 'Write you interest…',
filled: topicController.text.isNotEmpty),
);
Widget _buildTopicWrapper(OnboardingViewModel viewModel) =>
viewModel.hasTopicValidationMessage
? _buildTopicValidator(viewModel)
: Container();
Widget _buildTopicValidator(OnboardingViewModel viewModel) => Text(
viewModel.topicValidationMessage!,
style: style12R700,
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
@ -169,21 +140,13 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: LocaleKeys.cont.tr(),
onTap: viewModel.selectedTopic != null onTap: viewModel.selectedTopic != null
? viewModel.selectedTopic?.toLowerCase() == 'other'
? topicController.text.isNotEmpty
? () async => await _next(viewModel) ? () async => await _next(viewModel)
: null
: () async => await _next(viewModel)
: null, : null,
backgroundColor: viewModel.selectedTopic != null backgroundColor: viewModel.selectedTopic != null
? viewModel.selectedTopic?.toLowerCase() == 'other'
? topicController.text.isNotEmpty
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1)
: kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1)); : kcPrimaryColor.withOpacity(0.1));
} }

View File

@ -11,7 +11,6 @@ import '../../common/app_colors.dart';
import '../../common/enmus.dart'; import '../../common/enmus.dart';
import '../../common/ui_helpers.dart'; import '../../common/ui_helpers.dart';
import '../../common/validators/form_validator.dart'; import '../../common/validators/form_validator.dart';
import '../../widgets/custom_dropdown.dart';
import '../../widgets/custom_elevated_button.dart'; import '../../widgets/custom_elevated_button.dart';
import '../../widgets/image_picker_option.dart'; import '../../widgets/image_picker_option.dart';
import '../../widgets/page_loading_indicator.dart'; import '../../widgets/page_loading_indicator.dart';
@ -202,7 +201,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
verticalSpaceMedium, verticalSpaceMedium,
_buildCountryDropdownLabel(), _buildCountryDropdownLabel(),
verticalSpaceSmall, verticalSpaceSmall,
_buildCountryDropdown(viewModel), // _buildCountryDropdown(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildRegionFormFieldWrapper(viewModel), _buildRegionFormFieldWrapper(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
@ -527,13 +526,13 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
label: LocaleKeys.country.tr(), label: LocaleKeys.country.tr(),
); );
Widget _buildCountryDropdown(ProfileDetailViewModel viewModel) => // Widget _buildCountryDropdown(ProfileDetailViewModel viewModel) =>
CustomDropdownPicker( // CustomDropdownPicker(
hint: 'Select country', // hint: 'Select country',
selectedItem: viewModel.selectedCountry, // selectedItem: viewModel.selectedCountry,
items: (value, props) => viewModel.getCountries(), // items: (value, props) => viewModel.getCountries(),
onChanged: (value) => viewModel.setSelectedCountry(value ?? 'Ethiopia'), // onChanged: (value) => viewModel.setSelectedCountry(value ?? 'Ethiopia'),
); // );
Widget _buildRegionFormFieldWrapper(ProfileDetailViewModel viewModel) => Widget _buildRegionFormFieldWrapper(ProfileDetailViewModel viewModel) =>
Column( Column(
@ -565,18 +564,19 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
); );
Widget _buildRegionFormState(ProfileDetailViewModel viewModel) => Widget _buildRegionFormState(ProfileDetailViewModel viewModel) =>
viewModel.dropdownRegion // viewModel.dropdownRegion
? _buildRegionDropDown(viewModel) // ? _buildRegionDropDown(viewModel)
: _buildRegionFormField(viewModel); // :
_buildRegionFormField(viewModel);
Widget _buildRegionDropDown(ProfileDetailViewModel viewModel) => //
CustomDropdownPicker( // Widget _buildRegionDropDown(ProfileDetailViewModel viewModel) =>
icon: _buildSearchIcon(), // CustomDropdownPicker(
hint:LocaleKeys.select_region.tr(), // icon: _buildSearchIcon(),
selectedItem: viewModel.selectedRegion, // hint:LocaleKeys.select_region.tr(),
items: (value, props) => viewModel.getRegions(), // selectedItem: viewModel.selectedRegion,
onChanged: (value) => // items: (value, props) => viewModel.getRegions(),
viewModel.setSelectedRegion(value ?? 'Addis Ababa')); // onChanged: (value) =>
// viewModel.setSelectedRegion(value ?? 'Addis Ababa'));
Widget _buildRegionFormField(ProfileDetailViewModel viewModel) => Widget _buildRegionFormField(ProfileDetailViewModel viewModel) =>
TextFormField( TextFormField(
@ -584,7 +584,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
onTap: viewModel.setRegionFocus, onTap: viewModel.setRegionFocus,
decoration: inputDecoration( decoration: inputDecoration(
focus: viewModel.focusRegion, focus: viewModel.focusRegion,
hint:LocaleKeys.enter_your_city.tr(), hint: LocaleKeys.enter_your_city.tr(),
filled: regionController.text.isNotEmpty), filled: regionController.text.isNotEmpty),
); );
@ -611,7 +611,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
[ [
_buildOccupationDropdownLabel(), _buildOccupationDropdownLabel(),
verticalSpaceSmall, verticalSpaceSmall,
_buildOccupationDropdown(viewModel) // _buildOccupationDropdown(viewModel)
]; ];
Widget _buildOccupationDropdownLabel() => CustomFormLabel( Widget _buildOccupationDropdownLabel() => CustomFormLabel(
@ -619,14 +619,14 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
label: LocaleKeys.occupation.tr(), label: LocaleKeys.occupation.tr(),
); );
Widget _buildOccupationDropdown(ProfileDetailViewModel viewModel) => // Widget _buildOccupationDropdown(ProfileDetailViewModel viewModel) =>
CustomDropdownPicker( // CustomDropdownPicker(
icon: _buildSearchIcon(), // icon: _buildSearchIcon(),
hint:LocaleKeys.select_occupation.tr(), // hint:LocaleKeys.select_occupation.tr(),
selectedItem: viewModel.selectedOccupation, // selectedItem: viewModel.selectedOccupation,
items: (value, props) => viewModel.getOccupations(), // items: (value, props) => viewModel.getOccupations(),
onChanged: (value) => viewModel.setSelectedOccupation( // onChanged: (value) => viewModel.setSelectedOccupation(
value ?? 'Students (High school & University)')); // value ?? 'Students (High school & University)'));
Icon _buildSearchIcon() => const Icon( Icon _buildSearchIcon() => const Icon(
Icons.search, Icons.search,
color: kcPrimaryColor, color: kcPrimaryColor,
@ -663,7 +663,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
borderRadius: 12, borderRadius: 12,
onTap: viewModel.pop, onTap: viewModel.pop,
backgroundColor: kcWhite, backgroundColor: kcWhite,
text:LocaleKeys.cancel.tr(), text: LocaleKeys.cancel.tr(),
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
); );

View File

@ -75,8 +75,10 @@ class CreatePasswordScreen extends ViewModelWidget<RegisterViewModel> {
showBackButton: true, showBackButton: true,
onPop: viewModel.goBack, onPop: viewModel.goBack,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody(RegisterViewModel viewModel) => Widget _buildExpandedBody(RegisterViewModel viewModel) =>

View File

@ -91,8 +91,10 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
showBackButton: true, showBackButton: true,
onPop: viewModel.goBack, onPop: viewModel.goBack,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody( Widget _buildExpandedBody(

View File

@ -94,8 +94,10 @@ class RegisterWithPhoneNumberScreen extends ViewModelWidget<RegisterViewModel> {
showBackButton: true, showBackButton: true,
onPop: viewModel.goBack, onPop: viewModel.goBack,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody( Widget _buildExpandedBody(

View File

@ -105,8 +105,10 @@ class RegistrationOtpScreen extends ViewModelWidget<RegisterViewModel> {
showBackButton: true, showBackButton: true,
onPop: viewModel.goBack, onPop: viewModel.goBack,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody( Widget _buildExpandedBody(

View File

@ -1,6 +1,7 @@
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/services/authentication_service.dart'; import 'package:yimaru_app/services/authentication_service.dart';
import 'package:yimaru_app/services/onboarding_service.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../app/app.router.dart'; import '../../../app/app.router.dart';
@ -14,15 +15,22 @@ import '../../common/enmus.dart';
class StartupViewModel extends ReactiveViewModel { class StartupViewModel extends ReactiveViewModel {
// Dependency injection // Dependency injection
final _apiService = locator<ApiService>(); final _apiService = locator<ApiService>();
final _statusChecker = locator<StatusCheckerService>(); final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
final _onboardingService = locator<OnboardingService>();
final _localizationService = locator<LocalizationService>(); final _localizationService = locator<LocalizationService>();
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
final _imageDownloaderService = locator<ImageDownloaderService>(); final _imageDownloaderService = locator<ImageDownloaderService>();
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices =>
[_authenticationService]; [_onboardingService, _authenticationService];
// Current user // Current user
User? get _user => _authenticationService.user; User? get _user => _authenticationService.user;
@ -81,7 +89,7 @@ class StartupViewModel extends ReactiveViewModel {
response = {'data': true, 'status': ResponseStatus.success}; response = {'data': true, 'status': ResponseStatus.success};
} }
if (response['status'] == ResponseStatus.success && !response['data']) { if (response['status'] == ResponseStatus.success && !response['data']) {
await replaceWithOnboarding(); await etOnboardingFields();
} else if (response['status'] == ResponseStatus.success && } else if (response['status'] == ResponseStatus.success &&
response['data']) { response['data']) {
await saveProfileStatus(response['data']); await saveProfileStatus(response['data']);
@ -122,4 +130,16 @@ class StartupViewModel extends ReactiveViewModel {
} }
} }
} }
// Remote api call
// Onboarding fields
Future<void> etOnboardingFields() async {
bool response = await _onboardingService.getOnboardingFields();
if (response) {
await replaceWithOnboarding();
} else {
await replaceWithFailure();
}
}
} }

View File

@ -55,7 +55,7 @@ class SupportView extends StackedView<SupportViewModel> {
Widget _buildAppbar(SupportViewModel viewModel) => SmallAppBar( Widget _buildAppbar(SupportViewModel viewModel) => SmallAppBar(
showBackButton: true, showBackButton: true,
onPop: viewModel.pop, onPop: viewModel.pop,
title:LocaleKeys.need_help.tr(), title: LocaleKeys.need_help.tr(),
); );
Widget _buildContentWrapper(SupportViewModel viewModel) => Widget _buildContentWrapper(SupportViewModel viewModel) =>
@ -87,7 +87,7 @@ class SupportView extends StackedView<SupportViewModel> {
Widget _buildCallSupport(SupportViewModel viewModel) => SupportCard( Widget _buildCallSupport(SupportViewModel viewModel) => SupportCard(
icon: Icons.call, icon: Icons.call,
color: kcPrimaryColor, color: kcPrimaryColor,
title:LocaleKeys.call_support.tr(), title: LocaleKeys.call_support.tr(),
subtitle: LocaleKeys.talk_with_support.tr(), subtitle: LocaleKeys.talk_with_support.tr(),
onTap: () async => await viewModel.navigateToCallSupport(), onTap: () async => await viewModel.navigateToCallSupport(),
); );

View File

@ -135,7 +135,10 @@ class TelegramSupportView extends StackedView<TelegramSupportViewModel> {
Widget _buildOptionTextDivider() => const OptionTextDivider(); Widget _buildOptionTextDivider() => const OptionTextDivider();
Widget _buildSearchText() => Text.rich( Widget _buildSearchText() => Text.rich(
TextSpan(text: LocaleKeys.search_for.tr(), style: style14DG500, children: [ TextSpan(
text: LocaleKeys.search_for.tr(),
style: style14DG500,
children: [
TextSpan( TextSpan(
style: style14P600, style: style14P600,
text: ' $kTelegramSupport', text: ' $kTelegramSupport',

View File

@ -5,12 +5,15 @@ import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import '../../models/field_option.dart';
class CustomDropdownPicker extends StatelessWidget { class CustomDropdownPicker extends StatelessWidget {
final Icon? icon; final Icon? icon;
final String hint; final String hint;
final String selectedItem; final FieldOption? selectedItem;
final void Function(String?)? onChanged; final void Function(FieldOption?)? onChanged;
final FutureOr<List<String>> Function(String value, LoadProps? props)? items; final FutureOr<List<FieldOption>> Function(String value, LoadProps? props)?
items;
const CustomDropdownPicker( const CustomDropdownPicker(
{super.key, {super.key,
@ -28,16 +31,18 @@ class CustomDropdownPicker extends StatelessWidget {
child: _buildDropDownSearch(), child: _buildDropDownSearch(),
); );
Widget _buildDropDownSearch() => DropdownSearch<String>( Widget _buildDropDownSearch() => DropdownSearch<FieldOption>(
onChanged: onChanged,
popupProps: _popupProps(), popupProps: _popupProps(),
selectedItem: selectedItem, selectedItem: selectedItem,
onChanged: (value) => onChanged!(value), itemAsString: (item) => item.label ?? '',
decoratorProps: _dropDownDecoratorProps(), decoratorProps: _dropDownDecoratorProps(),
compareFn: (item1, item2) => item1.label == item2.label,
items: (value, properties) => items!(value, properties),
dropdownBuilder: (context, value) => _buildDropdownBuilder(value), dropdownBuilder: (context, value) => _buildDropdownBuilder(value),
items: (value, properties) async => await items!(value, properties),
); );
PopupProps<String> _popupProps() => PopupProps.menu( PopupProps<FieldOption> _popupProps() => PopupProps<FieldOption>.menu(
showSearchBox: true, showSearchBox: true,
showSelectedItems: true, showSelectedItems: true,
searchFieldProps: _searchFieldProps(), searchFieldProps: _searchFieldProps(),
@ -57,25 +62,25 @@ class CustomDropdownPicker extends StatelessWidget {
InputDecoration _popUpDecoration() => InputDecoration( InputDecoration _popUpDecoration() => InputDecoration(
filled: true, filled: true,
hintStyle: style14DG400, hintStyle: style14DG400,
fillColor: kcTransparent,
errorBorder: searchBorder, errorBorder: searchBorder,
focusedBorder: searchBorder, focusedBorder: searchBorder,
enabledBorder: searchBorder, enabledBorder: searchBorder,
disabledBorder: searchBorder, disabledBorder: searchBorder,
focusedErrorBorder: searchBorder, focusedErrorBorder: searchBorder,
fillColor: const Color(0xfff5e9f4),
contentPadding: const EdgeInsets.only(top: 12), contentPadding: const EdgeInsets.only(top: 12),
prefixIcon: icon != null ? _buildPrefixIcon() : null, prefixIcon: icon != null ? _buildPrefixIcon() : null,
); );
Widget _buildPopupProsBuilderWrapper(String value) => Padding( Widget _buildPopupProsBuilderWrapper(FieldOption value) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: _buildPopupProsBuilder(value), child: _buildPopupProsBuilder(value),
); );
Widget _buildPopupProsBuilder(String value) => Text( Widget _buildPopupProsBuilder(FieldOption value) => Text(
value, value.label ?? '',
maxLines: 1, maxLines: 1,
style: const TextStyle(color: kcDarkGrey, fontSize: 14), style: style14DG400,
); );
DropDownDecoratorProps _dropDownDecoratorProps() => DropDownDecoratorProps( DropDownDecoratorProps _dropDownDecoratorProps() => DropDownDecoratorProps(
@ -91,7 +96,7 @@ class CustomDropdownPicker extends StatelessWidget {
focusedBorder: border, focusedBorder: border,
enabledBorder: border, enabledBorder: border,
disabledBorder: border, disabledBorder: border,
hintStyle: style14LG400, hintStyle: style14MG400,
fillColor: kcPrimaryColor.withOpacity(0.1), fillColor: kcPrimaryColor.withOpacity(0.1),
contentPadding: contentPadding:
const EdgeInsets.symmetric(vertical: 10, horizontal: 15), const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
@ -102,8 +107,8 @@ class CustomDropdownPicker extends StatelessWidget {
child: icon, child: icon,
); );
Widget _buildDropdownBuilder(String? value) => Text( Widget _buildDropdownBuilder(FieldOption? value) => Text(
value ?? hint, value?.label ?? '',
maxLines: 1, maxLines: 1,
style: style14DG400, style: style14DG400,
); );

View File

@ -5,23 +5,20 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart';
class CustomLargeRadioButton extends StatelessWidget { class CustomLargeRadioButton extends StatelessWidget {
final String title; final String title;
final bool selected; final bool selected;
final IconData icon;
final String subtitle;
final GestureTapCallback? onTap; final GestureTapCallback? onTap;
const CustomLargeRadioButton( const CustomLargeRadioButton({
{super.key, super.key,
this.onTap, this.onTap,
required this.title, required this.title,
required this.icon,
required this.selected, required this.selected,
required this.subtitle}); });
@override @override
Widget build(BuildContext context) => _buildButtonWrapper(); Widget build(BuildContext context) => _buildButtonWrapper();
Widget _buildButtonWrapper() => Container( Widget _buildButtonWrapper() => Container(
height: 125, height: 75,
width: double.maxFinite, width: double.maxFinite,
margin: const EdgeInsets.only(bottom: 15), margin: const EdgeInsets.only(bottom: 15),
child: _buildContainerWrapper(), child: _buildContainerWrapper(),
@ -41,39 +38,24 @@ class CustomLargeRadioButton extends StatelessWidget {
color: selected ? kcPrimaryColor : kcPrimaryColor.withOpacity(0.75), color: selected ? kcPrimaryColor : kcPrimaryColor.withOpacity(0.75),
), ),
), ),
child: _buildButtonColumnWrapper(), child: _buildButtonRowWrapper(),
); );
Widget _buildButtonColumnWrapper() => Column( Widget _buildButtonRowWrapper() => Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildButtonRowChildren(), children: _buildButtonRowChildren(),
); );
List<Widget> _buildButtonRowChildren() => List<Widget> _buildButtonRowChildren() =>
[_buildIconSectionWrapper(), _buildTitle(), _buildSubtitle()]; [_buildTitleWrapper(), _buildSelectedCheckBox()];
Widget _buildIconSectionWrapper() => Row( Widget _buildTitleWrapper() => Expanded(
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: _buildTitle(),
children: _buildIconSectionChildren(),
);
List<Widget> _buildIconSectionChildren() =>
[_buildLeadingIcon(), _buildSelectedCheckBox()];
Widget _buildLeadingIcon() => Icon(
icon,
size: 25,
color: kcPrimaryColor,
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
title, title,
style: style18DG700, maxLines: 1,
); style: style16DG400,
Widget _buildSubtitle() => Text(
subtitle,
style: const TextStyle(color: kcMediumGrey),
); );
Widget _buildSelectedCheckBox() => Checkbox( Widget _buildSelectedCheckBox() => Checkbox(

View File

@ -13,18 +13,26 @@ import 'custom_elevated_button.dart';
class LearnCourseTile extends ViewModelWidget<LearnCourseViewModel> { class LearnCourseTile extends ViewModelWidget<LearnCourseViewModel> {
final LearnCourse course; final LearnCourse course;
final GestureTapCallback? onViewTap; final GestureTapCallback? onViewTap;
final GestureTapCallback? onLockTap;
final GestureTapCallback? onPracticeTap; final GestureTapCallback? onPracticeTap;
const LearnCourseTile({ const LearnCourseTile({
super.key, super.key,
this.onViewTap, this.onViewTap,
this.onLockTap,
this.onPracticeTap, this.onPracticeTap,
required this.course, required this.course,
}); });
@override @override
Widget build(BuildContext context, LearnCourseViewModel viewModel) => Widget build(BuildContext context, LearnCourseViewModel viewModel) =>
_buildExpansionTileCard(viewModel); _buildExpansionTileCardWrapper(viewModel);
Widget _buildExpansionTileCardWrapper(LearnCourseViewModel viewModel) =>
GestureDetector(
onTap: !(course.access?.isAccessible ?? false) ? onLockTap : null,
child: _buildExpansionTileCard(viewModel),
);
Widget _buildExpansionTileCard(LearnCourseViewModel viewModel) => Container( Widget _buildExpansionTileCard(LearnCourseViewModel viewModel) => Container(
margin: const EdgeInsets.only(bottom: 15), margin: const EdgeInsets.only(bottom: 15),
@ -147,7 +155,7 @@ class LearnCourseTile extends ViewModelWidget<LearnCourseViewModel> {
onTap: onViewTap, onTap: onViewTap,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
text:LocaleKeys.view_course.tr(), text: LocaleKeys.view_course.tr(),
); );
Widget _buildPracticeButtonWrapper(LearnCourseViewModel viewModel) => Widget _buildPracticeButtonWrapper(LearnCourseViewModel viewModel) =>
@ -163,6 +171,6 @@ class LearnCourseTile extends ViewModelWidget<LearnCourseViewModel> {
backgroundColor: kcWhite, backgroundColor: kcWhite,
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
text:LocaleKeys.take_practice.tr() , text: LocaleKeys.take_practice.tr(),
); );
} }

View File

@ -130,7 +130,9 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
); );
Widget _buildProgressText() => Text( Widget _buildProgressText() => Text(
(lesson.access?.isCompleted ?? false) ?LocaleKeys.completed.tr() : LocaleKeys.in_progress.tr() , (lesson.access?.isCompleted ?? false)
? LocaleKeys.completed.tr()
: LocaleKeys.in_progress.tr(),
style: style14P600, style: style14P600,
); );
@ -166,7 +168,7 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
width: double.maxFinite, width: double.maxFinite,
backgroundColor: kcWhite, backgroundColor: kcWhite,
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,
text:LocaleKeys.practice.tr() , text: LocaleKeys.practice.tr(),
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
); );
@ -182,7 +184,7 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
onTap: onLessonTap, onTap: onLessonTap,
width: double.maxFinite, width: double.maxFinite,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text:LocaleKeys.start.tr() , text: LocaleKeys.start.tr(),
trailingIcon: Icons.play_arrow, trailingIcon: Icons.play_arrow,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
); );

View File

@ -18,10 +18,7 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
final GestureTapCallback? onPracticeTap; final GestureTapCallback? onPracticeTap;
const LearnModuleTile( const LearnModuleTile(
{super.key, {super.key, this.onModuleTap, this.onPracticeTap, required this.module});
this.onModuleTap,
this.onPracticeTap,
required this.module});
Future<void> _showSheet( Future<void> _showSheet(
{required BuildContext context, {required BuildContext context,
@ -54,7 +51,7 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
Stack( Stack(
children: [ children: [
_buildExpansionTile(context: context, viewModel: viewModel), _buildExpansionTile(context: context, viewModel: viewModel),
_buildContainerShaderState() // _buildContainerShaderState()
], ],
); );
@ -73,14 +70,14 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
shape: Border.all(color: kcTransparent), shape: Border.all(color: kcTransparent),
expandedAlignment: Alignment.centerLeft, expandedAlignment: Alignment.centerLeft,
collapsedBackgroundColor: kcBackgroundColor, collapsedBackgroundColor: kcBackgroundColor,
enabled: (module.access?.isAccessible ?? false), //enabled: (module.access?.isAccessible ?? false),
controlAffinity: ListTileControlAffinity.trailing, controlAffinity: ListTileControlAffinity.trailing,
expandedCrossAxisAlignment: CrossAxisAlignment.start, expandedCrossAxisAlignment: CrossAxisAlignment.start,
tilePadding: const EdgeInsets.symmetric(horizontal: 15), tilePadding: const EdgeInsets.symmetric(horizontal: 15),
childrenPadding: const EdgeInsets.fromLTRB(70, 0, 15, 15), childrenPadding: const EdgeInsets.fromLTRB(70, 0, 15, 15),
initiallyExpanded: (module.access?.isAccessible ?? false), //initiallyExpanded: (module.access?.isAccessible ?? false),
showTrailingIcon: // showTrailingIcon:
!(module.access?.isAccessible ?? false) ? true : false, // !(module.access?.isAccessible ?? false) ? true : false,
children: children:
_buildExpansionTileChildren(context: context, viewModel: viewModel), _buildExpansionTileChildren(context: context, viewModel: viewModel),
); );
@ -211,9 +208,7 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
backgroundColor: kcWhite, backgroundColor: kcWhite,
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
text:LocaleKeys.take_practice.tr(), text: LocaleKeys.take_practice.tr(),
// onTap: () async => await viewModel.navigateToLearnPractice(practices),
); );
Widget _buildSheet(LearnModuleViewModel viewModel) => FinishPracticeSheet( Widget _buildSheet(LearnModuleViewModel viewModel) => FinishPracticeSheet(

View File

@ -53,7 +53,7 @@ class LearnPracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
Widget _buildSampleResponse() => LearnPracticeAnswerCard( Widget _buildSampleResponse() => LearnPracticeAnswerCard(
answer: answer, answer: answer,
voice: Voice.sample, voice: Voice.sample,
title:LocaleKeys.sample_answer.tr() , title: LocaleKeys.sample_answer.tr(),
); );
Widget _buildActualResponseWrapper() => Widget _buildActualResponseWrapper() =>
@ -63,6 +63,5 @@ class LearnPracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
answer: answer, answer: answer,
voice: Voice.recorded, voice: Voice.recorded,
title: LocaleKeys.your_answer.tr(), title: LocaleKeys.your_answer.tr(),
); );
} }

View File

@ -13,20 +13,12 @@ import 'custom_elevated_button.dart';
class LearnProgramTile extends ViewModelWidget<LearnProgramViewModel> { class LearnProgramTile extends ViewModelWidget<LearnProgramViewModel> {
final LearnProgram program; final LearnProgram program;
final GestureTapCallback? onTap; final GestureTapCallback? onTap;
final GestureTapCallback? onLockTap;
const LearnProgramTile( const LearnProgramTile({super.key, this.onTap, required this.program});
{super.key, this.onTap, this.onLockTap, required this.program});
@override @override
Widget build(BuildContext context, LearnProgramViewModel viewModel) => Widget build(BuildContext context, LearnProgramViewModel viewModel) =>
_buildExpansionTileCardWrapper(viewModel); _buildExpansionTileCard(viewModel);
Widget _buildExpansionTileCardWrapper(LearnProgramViewModel viewModel) =>
GestureDetector(
// onTap: !(program.access?.isAccessible ?? false) ? onLockTap : null,
child: _buildExpansionTileCard(viewModel),
);
Widget _buildExpansionTileCard(LearnProgramViewModel viewModel) => Container( Widget _buildExpansionTileCard(LearnProgramViewModel viewModel) => Container(
margin: const EdgeInsets.only(bottom: 15), margin: const EdgeInsets.only(bottom: 15),
@ -109,7 +101,7 @@ class LearnProgramTile extends ViewModelWidget<LearnProgramViewModel> {
Widget _buildProgressStatus() => ProgressStatus( Widget _buildProgressStatus() => ProgressStatus(
color: kcPrimaryColor, color: kcPrimaryColor,
status: (program.access?.isCompleted ?? false) status: (program.access?.isCompleted ?? false)
?LocaleKeys.completed.tr() ? LocaleKeys.completed.tr()
: LocaleKeys.in_progress.tr(), : LocaleKeys.in_progress.tr(),
); );
@ -134,6 +126,6 @@ class LearnProgramTile extends ViewModelWidget<LearnProgramViewModel> {
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
text: program.access?.progressPercent == 0 text: program.access?.progressPercent == 0
? LocaleKeys.start_learning.tr() ? LocaleKeys.start_learning.tr()
:LocaleKeys.continue_learning.tr() , : LocaleKeys.continue_learning.tr(),
); );
} }

View File

@ -74,7 +74,10 @@ class ProfileAppBar extends StatelessWidget {
[_buildGreetingTitle(), _buildSubtitle()]; [_buildGreetingTitle(), _buildSubtitle()];
Widget _buildGreetingTitle() => Text.rich( Widget _buildGreetingTitle() => Text.rich(
TextSpan(text: '${LocaleKeys.hello.tr()},', style: style14DG600, children: [ TextSpan(
text: '${LocaleKeys.hello.tr()},',
style: style14DG600,
children: [
TextSpan( TextSpan(
text: ' $name!', text: ' $name!',
style: style14P600, style: style14P600,

View File

@ -6,7 +6,6 @@ import FlutterMacOS
import Foundation import Foundation
import audioplayers_darwin import audioplayers_darwin
import battery_plus
import connectivity_plus import connectivity_plus
import file_selector_macos import file_selector_macos
import firebase_core import firebase_core
@ -25,7 +24,6 @@ import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin"))
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))

View File

@ -113,22 +113,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.3.0" version: "4.3.0"
battery_plus:
dependency: "direct main"
description:
name: battery_plus
sha256: ad16fcb55b7384be6b4bbc763d5e2031ac7ea62b2d9b6b661490c7b9741155bf
url: "https://pub.dev"
source: hosted
version: "7.0.0"
battery_plus_platform_interface:
dependency: transitive
description:
name: battery_plus_platform_interface
sha256: e8342c0f32de4b1dfd0223114b6785e48e579bfc398da9471c9179b907fa4910
url: "https://pub.dev"
source: hosted
version: "2.0.1"
bloc: bloc:
dependency: transitive dependency: transitive
description: description:
@ -349,10 +333,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dbus name: dbus
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.11" version: "0.7.12"
dio: dio:
dependency: "direct main" dependency: "direct main"
description: description:
@ -421,10 +405,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.2.0"
ffi_leak_tracker:
dependency: transitive
description:
name: ffi_leak_tracker
sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97"
url: "https://pub.dev"
source: hosted
version: "0.1.2"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -743,10 +735,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_secure_storage_windows name: flutter_secure_storage_windows
sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" sha256: "471951813a97006d899db4948acc654a4f28c440083ea08178935ce20b173ec1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0" version: "4.2.2"
flutter_spinkit: flutter_spinkit:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1206,21 +1198,21 @@ packages:
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
package_info_plus: package_info_plus:
dependency: transitive dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d sha256: "4bf625947f6c7713ee242296a682e23e44823c09cf9d79e4f1238923c92db852"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.0.0" version: "10.1.0"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: package_info_plus_platform_interface name: package_info_plus_platform_interface
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" sha256: db762cb2f4f25ee60fb6359773861b0f199e00b90d237bd85a76a1e806b46ef4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.1" version: "4.1.0"
path: path:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1682,14 +1674,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.2" version: "1.4.2"
storage_info:
dependency: "direct main"
description:
name: storage_info
sha256: adbf5fd1a7c2ca977dd828573820db0a0a16f4aa317e0ab72e9b3282eb5bbe42
url: "https://pub.dev"
source: hosted
version: "1.0.0"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
@ -1770,14 +1754,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.1"
upower:
dependency: transitive
description:
name: upower
sha256: cf042403154751180affa1d15614db7fa50234bc2373cd21c3db666c38543ebf
url: "https://pub.dev"
source: hosted
version: "0.7.0"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1942,18 +1918,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: wakelock_plus name: wakelock_plus
sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" sha256: "824c5bba0f800e86d32e57d3d1843c531f090005cc89d9a837933e6601093d53"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.6.1"
wakelock_plus_platform_interface: wakelock_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: wakelock_plus_platform_interface name: wakelock_plus_platform_interface
sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" sha256: b13f99e992e7ae6a152e16c5559d3c07ff445b13330192662494e614ca3e7d7b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.5.1"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
@ -2006,10 +1982,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.15.0" version: "6.3.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@ -1,5 +1,5 @@
name: yimaru_app name: yimaru_app
version: 0.1.21+23 version: 0.1.22+24
publish_to: 'none' publish_to: 'none'
description: A new Flutter project. description: A new Flutter project.
@ -22,8 +22,6 @@ dependencies:
flutter_svg: ^2.2.3 flutter_svg: ^2.2.3
stacked_shared: any stacked_shared: any
image_picker: ^1.2.1 image_picker: ^1.2.1
battery_plus: ^7.0.0
storage_info: ^1.0.0
flutter_html: ^3.0.0 flutter_html: ^3.0.0
email_validator: any email_validator: any
audioplayers: ^6.6.0 audioplayers: ^6.6.0
@ -43,6 +41,7 @@ dependencies:
json_serializable: ^6.8.0 json_serializable: ^6.8.0
waveform_recorder: ^1.8.0 waveform_recorder: ^1.8.0
vimeo_video_player: ^1.0.3 vimeo_video_player: ^1.0.3
package_info_plus: ^10.1.0
permission_handler: ^12.0.1 permission_handler: ^12.0.1
firebase_messaging: ^16.1.1 firebase_messaging: ^16.1.1
cached_network_image: ^3.4.1 cached_network_image: ^3.4.1

View File

@ -22,10 +22,9 @@ import 'package:yimaru_app/services/url_launcher_service.dart';
import 'package:yimaru_app/services/phone_caller_service.dart'; import 'package:yimaru_app/services/phone_caller_service.dart';
import 'package:yimaru_app/services/learn_service.dart'; import 'package:yimaru_app/services/learn_service.dart';
import 'package:yimaru_app/services/localization_service.dart'; import 'package:yimaru_app/services/localization_service.dart';
import 'package:yimaru_app/services/onboarding_service.dart';
// @stacked-import // @stacked-import
import 'test_helpers.mocks.dart';
@GenerateMocks( @GenerateMocks(
[], [],
customMocks: [ customMocks: [
@ -54,10 +53,10 @@ import 'test_helpers.mocks.dart';
MockSpec<UrlLauncherService>(onMissingStub: OnMissingStub.returnDefault), MockSpec<UrlLauncherService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<UrlLauncherService>(onMissingStub: OnMissingStub.returnDefault), MockSpec<UrlLauncherService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<PhoneCallerService>(onMissingStub: OnMissingStub.returnDefault), MockSpec<PhoneCallerService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<LearnLessonService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault), MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault), MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<LocalizationService>(onMissingStub: OnMissingStub.returnDefault), MockSpec<LocalizationService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<OnboardingService>(onMissingStub: OnMissingStub.returnDefault),
// @stacked-mock-spec // @stacked-mock-spec
], ],
) )
@ -85,10 +84,10 @@ void registerServices() {
getAndRegisterUrlLauncherService(); getAndRegisterUrlLauncherService();
getAndRegisterUrlLauncherService(); getAndRegisterUrlLauncherService();
getAndRegisterPhoneCallerService(); getAndRegisterPhoneCallerService();
getAndRegisterLearnLessonService();
getAndRegisterLearnService(); getAndRegisterLearnService();
getAndRegisterLearnService(); getAndRegisterLearnService();
getAndRegisterLocalizationService(); getAndRegisterLocalizationService();
getAndRegisterOnboardingService();
// @stacked-mock-register // @stacked-mock-register
} }
@ -285,6 +284,13 @@ MockLocalizationService getAndRegisterLocalizationService() {
locator.registerSingleton<LocalizationService>(service); locator.registerSingleton<LocalizationService>(service);
return service; return service;
} }
MockOnboardingService getAndRegisterOnboardingService() {
_removeRegistrationIfExists<OnboardingService>();
final service = MockOnboardingService();
locator.registerSingleton<OnboardingService>(service);
return service;
}
// @stacked-mock-create // @stacked-mock-create
void _removeRegistrationIfExists<T extends Object>() { void _removeRegistrationIfExists<T extends Object>() {

View File

@ -0,0 +1,11 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:yimaru_app/app/app.locator.dart';
import '../helpers/test_helpers.dart';
void main() {
group('OnboardingServiceTest -', () {
setUp(() => registerServices());
tearDown(() => locator.reset());
});
}

View File

@ -7,7 +7,6 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <audioplayers_windows/audioplayers_windows_plugin.h> #include <audioplayers_windows/audioplayers_windows_plugin.h>
#include <battery_plus/battery_plus_windows_plugin.h>
#include <connectivity_plus/connectivity_plus_windows_plugin.h> #include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <firebase_core/firebase_core_plugin_c_api.h> #include <firebase_core/firebase_core_plugin_c_api.h>
@ -20,8 +19,6 @@
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
AudioplayersWindowsPluginRegisterWithRegistrar( AudioplayersWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
BatteryPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin"));
ConnectivityPlusWindowsPluginRegisterWithRegistrar( ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
FileSelectorWindowsRegisterWithRegistrar( FileSelectorWindowsRegisterWithRegistrar(

View File

@ -4,7 +4,6 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows audioplayers_windows
battery_plus
connectivity_plus connectivity_plus
file_selector_windows file_selector_windows
firebase_core firebase_core