diff --git a/assets/translations/am.json b/assets/translations/am.json index 7e2b5f9..35660ec 100644 --- a/assets/translations/am.json +++ b/assets/translations/am.json @@ -2,7 +2,7 @@ "loading": "በመጫን ላይ", "welcome_back": "እንኳን በደህና ተመለሱ", "checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ", - "dont_have_account": "መለያ የለዎትም? ይመዝገቡ", + "dont_have_account": "መለያ የለዎትም?", "email": "ኢሜይል", "password": "የይለፍ ቃል", "forgot_password": "የይለፍ ቃል ረሱ?", @@ -43,14 +43,14 @@ "continue_learning": "መማርን ይቀጥሉ", "start_learning": "ትምህርትን ይጀምሩ", "completed": "ተጠናቋል", - "take_practice": " ልምምድ ያድርጉ", + "take_practice": "ልምምድ ያድርጉ", "your_current_level": "የአሁኑ ደረጃዎ", "overall_progress": "አጠቃላይ እድገት", "great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው", "view_module": "ሞጁሉን ይመልከቱ", "progress": "እድገት", - "keep_going": " ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ", - "lessons_in_module": " በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ", + "keep_going": "ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ", + "lessons_in_module": "በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ", "practice": "ልምምድ", "start": "ጀምር", "in_progress": "በሂደት ላይ", @@ -134,7 +134,7 @@ "ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።", "begin_level_practice": "የደረጃ ልምምድን ጀምር", "lets_practice_course": "የኮርሱን ልምምድ እንለማመድ", - "lets_quick_practice": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!", + "lets_quick_review": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!", "speaking": "እየተናገረ ነው", "you_have_finished_practice": "ልምምድህን አጠናቀቅህ", "view_results": "ውጤቶቼን እይ", @@ -153,15 +153,36 @@ "what_should_we_call_you": "ምን ብለን እንጠራህ?", "name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።", "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": "አጠናቅቅ" diff --git a/assets/translations/en.json b/assets/translations/en.json index 2266214..859ce9f 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -2,7 +2,7 @@ {"loading": "Loading", "welcome_back": "Welcome back", "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", "password": "Password", "forgot_password": "Forgot password?", @@ -153,7 +153,34 @@ "what_should_we_call_you": "What should we call you?", "name_for_personalization": "We’ll use your name to personalize your learning journey.", "choose_your_gender": "Choose your gender?", - "gender_for_personalization": "We’ll personalize your learning experience based on your gender." - - + "gender_for_personalization": "We’ll personalize your learning experience based on your gender.", + "age_range": "Which age range are you in?", + "age_for_personalization": "We’ll personalize your learning experience based on your age.", + "educational_background": "What’s your current educational level?", + "education_for_personalization": "This helps us tailor your lessons to your experience.", + "your_occupation": "What’s your occupation?", + "occupation_for_personalization": "We’ll 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": "What’s 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, let’s 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": "You’re likely speaker of", + "great_job": "Great Job! Here’s your next step to keep improving.", + "lets_start_practice": "Let's start your practice", + "welcome_abroad": "Welcome aboard", + "ready_to_explore": "You’re ready to explore your personalized lessons.", + "finish": "Finish" } diff --git a/lib/app/app.dart b/lib/app/app.dart index d7cee32..1838082 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -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/in_app_update_service.dart'; import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'; -import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart'; import 'package:yimaru_app/ui/views/assessment/assessment_view.dart'; import 'package:yimaru_app/services/vimeo_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/ui/views/landing/landing_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 @StackedApp( @@ -88,7 +89,6 @@ import 'package:yimaru_app/ui/views/course_module/course_module_view.dart'; MaterialRoute(page: DuolingoView), MaterialRoute(page: CourseView), MaterialRoute(page: LearnProgramView), - MaterialRoute(page: LearnCourseView), MaterialRoute(page: AssessmentView), MaterialRoute(page: LearnSubscriptionView), MaterialRoute(page: ArifPayView), @@ -123,6 +123,7 @@ import 'package:yimaru_app/ui/views/course_module/course_module_view.dart'; LazySingleton(classType: PhoneCallerService), LazySingleton(classType: LearnService), LazySingleton(classType: LocalizationService), + LazySingleton(classType: OnboardingService), // @stacked-service ], bottomsheets: [ diff --git a/lib/app/app.locator.dart b/lib/app/app.locator.dart index c1c146a..85eb85a 100644 --- a/lib/app/app.locator.dart +++ b/lib/app/app.locator.dart @@ -24,6 +24,7 @@ import '../services/in_app_update_service.dart'; import '../services/learn_service.dart'; import '../services/localization_service.dart'; import '../services/notification_service.dart'; +import '../services/onboarding_service.dart'; import '../services/permission_handler_service.dart'; import '../services/phone_caller_service.dart'; import '../services/secure_storage_service.dart'; @@ -65,4 +66,5 @@ Future setupLocator( locator.registerLazySingleton(() => PhoneCallerService()); locator.registerLazySingleton(() => LearnService()); locator.registerLazySingleton(() => LocalizationService()); + locator.registerLazySingleton(() => OnboardingService()); } diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart index 2171016..0f868d9 100644 --- a/lib/app/app.router.dart +++ b/lib/app/app.router.dart @@ -6,8 +6,8 @@ // ************************************************************************** // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:flutter/material.dart' as _i37; import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' as _i37; import 'package:stacked/stacked.dart' as _i1; import 'package:stacked_services/stacked_services.dart' as _i46; 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/views/account_privacy/account_privacy_view.dart' as _i9; -import 'package:yimaru_app/ui/views/arif_pay/arif_pay_view.dart' as _i32; -import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i30; +import 'package:yimaru_app/ui/views/arif_pay/arif_pay_view.dart' as _i31; +import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i29; import 'package:yimaru_app/ui/views/call_support/call_support_view.dart' as _i12; import 'package:yimaru_app/ui/views/course/course_view.dart' as _i27; 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' as _i25; 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' 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/duolingo/duolingo_view.dart' as _i26; import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i24; import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart' as _i20; 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/learn_course/learn_course_view.dart' - as _i29; + as _i36; import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart' as _i19; 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' as _i28; 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/onboarding/onboarding_view.dart' as _i3; 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 learnCourseView = '/learn-course-view'; - static const assessmentView = '/assessment-view'; static const learnSubscriptionView = '/learn-subscription-view'; @@ -143,6 +141,7 @@ class Routes { static const courseModuleView = '/course-module-view'; + static const learnCourseView = '/learn-course-view'; static const all = { homeView, @@ -172,7 +171,6 @@ class Routes { duolingoView, courseView, learnProgramView, - learnCourseView, assessmentView, learnSubscriptionView, arifPayView, @@ -180,6 +178,7 @@ class Routes { courseUnitView, landingView, courseModuleView, + learnCourseView, }; } @@ -293,41 +292,37 @@ class StackedRouter extends _i1.RouterBase { Routes.learnProgramView, page: _i28.LearnProgramView, ), - _i1.RouteDef( - Routes.learnCourseView, - page: _i29.LearnCourseView, - ), _i1.RouteDef( Routes.assessmentView, - page: _i30.AssessmentView, + page: _i29.AssessmentView, ), _i1.RouteDef( Routes.learnSubscriptionView, - page: _i31.LearnSubscriptionView, + page: _i30.LearnSubscriptionView, ), _i1.RouteDef( Routes.arifPayView, - page: _i32.ArifPayView, + page: _i31.ArifPayView, ), _i1.RouteDef( Routes.courseCatalogView, - page: _i33.CourseCatalogView, + page: _i32.CourseCatalogView, ), _i1.RouteDef( Routes.courseUnitView, - page: _i34.CourseUnitView, + page: _i33.CourseUnitView, ), _i1.RouteDef( Routes.landingView, - page: _i35.LandingView, + page: _i34.LandingView, ), _i1.RouteDef( Routes.courseModuleView, - page: _i36.CourseModuleView, + page: _i35.CourseModuleView, ), _i1.RouteDef( Routes.learnCourseView, - page: _i29.LearnCourseView, + page: _i36.LearnCourseView, ), ]; @@ -577,72 +572,72 @@ class StackedRouter extends _i1.RouterBase { settings: data, ); }, - _i29.LearnCourseView: (data) { - final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( - builder: (context) => _i29.LearnCourseView(key: args.key, id: args.id), - settings: data, - ); - }, - _i30.AssessmentView: (data) { + _i29.AssessmentView: (data) { final args = data.getArgs(nullOk: false); return _i37.MaterialPageRoute( builder: (context) => - _i30.AssessmentView(key: args.key, data: args.data), + _i29.AssessmentView(key: args.key, data: args.data), settings: data, ); }, - _i31.LearnSubscriptionView: (data) { + _i30.LearnSubscriptionView: (data) { final args = data.getArgs( orElse: () => const LearnSubscriptionViewArguments(), ); return _i37.MaterialPageRoute( - builder: (context) => _i31.LearnSubscriptionView(key: args.key), + builder: (context) => _i30.LearnSubscriptionView(key: args.key), settings: data, ); }, - _i32.ArifPayView: (data) { + _i31.ArifPayView: (data) { final args = data.getArgs(nullOk: false); return _i37.MaterialPageRoute( builder: (context) => - _i32.ArifPayView(key: args.key, phone: args.phone), + _i31.ArifPayView(key: args.key, phone: args.phone), settings: data, ); }, - _i33.CourseCatalogView: (data) { + _i32.CourseCatalogView: (data) { final args = data.getArgs( orElse: () => const CourseCatalogViewArguments(), ); return _i37.MaterialPageRoute( - builder: (context) => _i33.CourseCatalogView(key: args.key), + builder: (context) => _i32.CourseCatalogView(key: args.key), settings: data, ); }, - _i34.CourseUnitView: (data) { + _i33.CourseUnitView: (data) { final args = data.getArgs(nullOk: false); return _i37.MaterialPageRoute( builder: (context) => - _i34.CourseUnitView(key: args.key, catalog: args.catalog), + _i33.CourseUnitView(key: args.key, catalog: args.catalog), settings: data, ); }, - _i35.LandingView: (data) { + _i34.LandingView: (data) { final args = data.getArgs( orElse: () => const LandingViewArguments(), ); return _i37.MaterialPageRoute( - builder: (context) => _i35.LandingView(key: args.key), + builder: (context) => _i34.LandingView(key: args.key), settings: data, ); }, - _i36.CourseModuleView: (data) { + _i35.CourseModuleView: (data) { final args = data.getArgs(nullOk: false); return _i37.MaterialPageRoute( - builder: (context) => _i36.CourseModuleView( + builder: (context) => _i35.CourseModuleView( key: args.key, module: args.module, catalog: args.catalog), settings: data, ); }, + _i36.LearnCourseView: (data) { + final args = data.getArgs(nullOk: false); + return _i37.MaterialPageRoute( + builder: (context) => _i36.LearnCourseView(key: args.key, id: args.id), + settings: data, + ); + }, }; @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 { const AssessmentViewArguments({ 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 { Future navigateToHomeView({ _i37.Key? key, @@ -1991,23 +1986,6 @@ extension NavigatorStateExtension on _i46.NavigationService { transition: transition); } - Future navigateToLearnCourseView({ - _i37.Key? key, - required int id, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo(Routes.learnCourseView, - arguments: LearnCourseViewArguments(key: key, id: id), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future navigateToAssessmentView({ _i37.Key? key, required Map data, @@ -2126,7 +2104,22 @@ extension NavigatorStateExtension on _i46.NavigationService { transition: transition); } - + Future navigateToLearnCourseView({ + _i37.Key? key, + required int id, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo(Routes.learnCourseView, + arguments: LearnCourseViewArguments(key: key, id: id), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } Future replaceWithHomeView({ _i37.Key? key, @@ -2584,23 +2577,6 @@ extension NavigatorStateExtension on _i46.NavigationService { transition: transition); } - Future replaceWithLearnCourseView({ - _i37.Key? key, - required int id, - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return replaceWith(Routes.learnCourseView, - arguments: LearnCourseViewArguments(key: key, id: id), - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition); - } - Future replaceWithAssessmentView({ _i37.Key? key, required Map data, @@ -2719,5 +2695,20 @@ extension NavigatorStateExtension on _i46.NavigationService { transition: transition); } - + Future replaceWithLearnCourseView({ + _i37.Key? key, + required int id, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return replaceWith(Routes.learnCourseView, + arguments: LearnCourseViewArguments(key: key, id: id), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } } diff --git a/lib/main.dart b/lib/main.dart index 923e5d4..b56ef67 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -23,7 +23,9 @@ Future main() async { EasyLocalization( supportedLocales: const [ Locale('en'), - Locale('አማ'), + Locale( + 'am', + ), ], path: 'assets/translations', startLocale: const Locale('en'), diff --git a/lib/models/app_update.dart b/lib/models/app_update.dart new file mode 100644 index 0000000..87a7cf9 --- /dev/null +++ b/lib/models/app_update.dart @@ -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 json) => + _$AppUpdateFromJson(json); + + Map toJson() => _$AppUpdateToJson(this); +} diff --git a/lib/models/app_update.g.dart b/lib/models/app_update.g.dart new file mode 100644 index 0000000..8a456ae --- /dev/null +++ b/lib/models/app_update.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_update.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AppUpdate _$AppUpdateFromJson(Map json) => AppUpdate( + forceUpdate: json['force_update'] as bool?, + updateAvailable: json['update_available'] as bool?, + ); + +Map _$AppUpdateToJson(AppUpdate instance) => { + 'force_update': instance.forceUpdate, + 'update_available': instance.updateAvailable, + }; diff --git a/lib/models/field_option.dart b/lib/models/field_option.dart new file mode 100644 index 0000000..267fb3c --- /dev/null +++ b/lib/models/field_option.dart @@ -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 json) => + _$FieldOptionFromJson(json); + + Map toJson() => _$FieldOptionToJson(this); +} diff --git a/lib/models/field_option.g.dart b/lib/models/field_option.g.dart new file mode 100644 index 0000000..ff7a92b --- /dev/null +++ b/lib/models/field_option.g.dart @@ -0,0 +1,18 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'field_option.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FieldOption _$FieldOptionFromJson(Map json) => FieldOption( + code: json['code'] as String?, + label: json['label'] as String?, + ); + +Map _$FieldOptionToJson(FieldOption instance) => + { + 'code': instance.code, + 'label': instance.label, + }; diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 5b5781d..555b1e3 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -1,4 +1,5 @@ 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_practice.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 '../models/course_module.dart'; import '../models/course_unit.dart'; +import '../models/field_option.dart'; import '../models/learn_course.dart'; import '../models/learn_module.dart'; import '../models/learn_question.dart'; @@ -379,6 +381,222 @@ class ApiService { } } + // Get educational levels + Future> getEducationalLevels() async { + try { + List 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> getEthiopiaRegions() async { + try { + List 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> getLanguageChallenges() async { + try { + List 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> getOccupations() async { + try { + List 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> getAgeGroups() async { + try { + List 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> getCountries() async { + try { + List 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> getTopics() async { + try { + List 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> getLanguageGoals() async { + try { + List 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> getLearningGoals() async { + try { + List 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 Future> getAssessmentQuestions(int id) async { try { @@ -795,4 +1013,29 @@ class ApiService { return []; } } + + // Check update + Future> checkUpdate(Map 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, + }; + } + } } diff --git a/lib/services/in_app_update_service.dart b/lib/services/in_app_update_service.dart index 286a0dc..2c43862 100644 --- a/lib/services/in_app_update_service.dart +++ b/lib/services/in_app_update_service.dart @@ -1,62 +1,51 @@ -import 'package:battery_plus/battery_plus.dart'; import 'package:flutter/services.dart'; import 'package:in_app_update/in_app_update.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/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 'api_service.dart'; class InAppUpdateService { + // Dependency Injection + final _apiService = locator(); + final _navigationService = locator(); - Future getBatteryLevel() async { - final battery = Battery(); - final batteryLevel = await battery.batteryLevel; - return batteryLevel; - } - - Future getAvailableStorage() async { - try { - final availableStorage = - await StorageInfo().getStorageFreeSpace(SpaceUnit.Bytes); - return availableStorage.toInt(); // Convert GB to bytes - } catch (e) { - return 0; - } - } + final _statusCheckerService = locator(); Future 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 { final info = await InAppUpdate.checkForUpdate(); - if (info.updateAvailability == UpdateAvailability.updateAvailable) { - AppUpdateResult result = await InAppUpdate.performImmediateUpdate(); - if (result == AppUpdateResult.userDeniedUpdate) { - showErrorToast('An update is required to continue using this app.'); - _navigationService.back(); + final String version = await _statusCheckerService.getAppVersion(); + + if (version != info.availableVersionCode.toString()) { + Map data = { + 'platform': 'ANDROID', + 'version_code': info.availableVersionCode + }; + Map 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) { + showErrorToast( + 'An update is required to continue using this app.'); + _navigationService.back(); + } + } else { + await InAppUpdate.startFlexibleUpdate(); + } + } } } - - // ... rest of your update logic ... } on PlatformException { // Handle specific error code for better user experience and potentially different error messages for each issue } diff --git a/lib/services/localization_service.dart b/lib/services/localization_service.dart index 822d5fe..41ee4d7 100644 --- a/lib/services/localization_service.dart +++ b/lib/services/localization_service.dart @@ -23,7 +23,7 @@ class LocalizationService with ListenableServiceMixin { Map get selectedLanguage => _selectedLanguage; final List> _languages = [ - {'code': 'አማ', 'language': 'አማርኛ'}, + {'code': 'am', 'language': 'አማርኛ'}, {'code': 'en', 'language': 'English'}, ]; @@ -37,7 +37,7 @@ class LocalizationService with ListenableServiceMixin { required Map title}) async { _selectedLanguage = title; - if (title['code'] == 'አማ') { + if (title['code'] == 'am') { await setAmharicLanguage(context); } else { await setEnglishLanguage(context); @@ -50,17 +50,15 @@ class LocalizationService with ListenableServiceMixin { if (language == 'en') { _selectedLanguage = {'code': 'en', 'language': 'English'}; - } else { - _selectedLanguage = {'code': 'አማ', 'language': 'አማርኛ'}; + _selectedLanguage = {'code': 'am', 'language': 'አማርኛ'}; } -notifyListeners(); - print('SELECTED LANGUAGE: $language $_selectedLanguage'); + notifyListeners(); } Future setAmharicLanguage(BuildContext context) async { - await context.setLocale(const Locale('አማ')); - await _secureService.setString('language', 'አማ'); + await context.setLocale(const Locale('am')); + await _secureService.setString('language', 'am'); notifyListeners(); } diff --git a/lib/services/onboarding_service.dart b/lib/services/onboarding_service.dart new file mode 100644 index 0000000..9f0a0f1 --- /dev/null +++ b/lib/services/onboarding_service.dart @@ -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(); + + // Initialization + learnService() { + listenToReactiveValues([ + _topics, + _regions, + _ageGroups, + _countries, + _challenges, + _occupations, + _learningGoals, + _languageGoals, + _educationalBackgrounds + ]); + } + + // Topics + List _topics = []; + + List get topics => _topics; + + // Regions + List _regions = []; + + List get regions => _regions; + + // Age groups + List _ageGroups = []; + + List get ageGroups => _ageGroups; + + // Countries + List _countries = []; + + List get countries => _countries; + + // Challenges + List _challenges = []; + + List get challenges => _challenges; + + // Occupations + List _occupations = []; + + List get occupations => _occupations; + + // Learning goals + List _learningGoals = []; + + List get learningGoals => _learningGoals; + + // Language goals + List _languageGoals = []; + + List get languageGoals => _languageGoals; + + // Educational backgrounds + List _educationalBackgrounds = []; + + List get educationalBackgrounds => _educationalBackgrounds; + + // Onboarding fields + Future 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; + } +} diff --git a/lib/services/status_checker_service.dart b/lib/services/status_checker_service.dart index 7ff004e..b9356c0 100644 --- a/lib/services/status_checker_service.dart +++ b/lib/services/status_checker_service.dart @@ -1,4 +1,5 @@ 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 '../app/app.locator.dart'; @@ -25,4 +26,16 @@ class StatusCheckerService { return false; } } + + // Get app version + Future 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; + } } diff --git a/lib/ui/common/app_constants.dart b/lib/ui/common/app_constants.dart index 4b6388a..22c675f 100644 --- a/lib/ui/common/app_constants.dart +++ b/lib/ui/common/app_constants.dart @@ -1,10 +1,14 @@ // Endpoints String kBaseUrl = 'https://api.yimaruacademy.com'; +String kAppUrl = 'app'; + String kApiUrl = 'api'; String kUnitsUrl = 'units'; +String kCheckUrl = 'check'; + String kApiVersionUrl = 'v1'; String kLevelsUrl = 'levels'; @@ -15,6 +19,8 @@ String kModulesUrl = 'modules'; String kLessonsUrl = 'lessons'; +String kVersionUrl = 'version'; + String kProgramsUrl = 'programs'; String kRegisterUrl = 'register'; @@ -47,6 +53,8 @@ String kSubmodulesUrl = 'sub-modules'; String kSubcoursesUrl = 'sub-courses'; +String kFieldOptions = 'field-options'; + String kResetPassword = 'resetPassword'; String kQuestionSetsUrl = 'question-sets'; diff --git a/lib/ui/common/translations/codegen_loader.g.dart b/lib/ui/common/translations/codegen_loader.g.dart index ab164b5..38d1e96 100644 --- a/lib/ui/common/translations/codegen_loader.g.dart +++ b/lib/ui/common/translations/codegen_loader.g.dart @@ -18,7 +18,7 @@ class CodegenLoader extends AssetLoader{ "loading": "በመጫን ላይ", "welcome_back": "እንኳን በደህና ተመለሱ", "checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ", - "dont_have_account": "መለያ የለዎትም? ይመዝገቡ", + "dont_have_account": "መለያ የለዎትም?", "email": "ኢሜይል", "password": "የይለፍ ቃል", "forgot_password": "የይለፍ ቃል ረሱ?", @@ -59,14 +59,14 @@ class CodegenLoader extends AssetLoader{ "continue_learning": "መማርን ይቀጥሉ", "start_learning": "ትምህርትን ይጀምሩ", "completed": "ተጠናቋል", - "take_practice": " ልምምድ ያድርጉ", + "take_practice": "ልምምድ ያድርጉ", "your_current_level": "የአሁኑ ደረጃዎ", "overall_progress": "አጠቃላይ እድገት", "great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው", "view_module": "ሞጁሉን ይመልከቱ", "progress": "እድገት", - "keep_going": " ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ", - "lessons_in_module": " በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ", + "keep_going": "ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ", + "lessons_in_module": "በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ", "practice": "ልምምድ", "start": "ጀምር", "in_progress": "በሂደት ላይ", @@ -150,7 +150,7 @@ class CodegenLoader extends AssetLoader{ "ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።", "begin_level_practice": "የደረጃ ልምምድን ጀምር", "lets_practice_course": "የኮርሱን ልምምድ እንለማመድ", - "lets_quick_practice": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!", + "lets_quick_review": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!", "speaking": "እየተናገረ ነው", "you_have_finished_practice": "ልምምድህን አጠናቀቅህ", "view_results": "ውጤቶቼን እይ", @@ -169,13 +169,42 @@ class CodegenLoader extends AssetLoader{ "what_should_we_call_you": "ምን ብለን እንጠራህ?", "name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።", "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 _en = { "loading": "Loading", "welcome_back": "Welcome back", "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", "password": "Password", "forgot_password": "Forgot password?", @@ -326,7 +355,36 @@ static const Map _en = { "what_should_we_call_you": "What should we call you?", "name_for_personalization": "We’ll use your name to personalize your learning journey.", "choose_your_gender": "Choose your gender?", - "gender_for_personalization": "We’ll personalize your learning experience based on your gender." + "gender_for_personalization": "We’ll personalize your learning experience based on your gender.", + "age_range": "Which age range are you in?", + "age_for_personalization": "We’ll personalize your learning experience based on your age.", + "educational_background": "What’s your current educational level?", + "education_for_personalization": "This helps us tailor your lessons to your experience.", + "your_occupation": "What’s your occupation?", + "occupation_for_personalization": "We’ll 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": "What’s 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, let’s 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": "You’re likely speaker of", + "great_job": "Great Job! Here’s your next step to keep improving.", + "lets_start_practice": "Let's start your practice", + "welcome_abroad": "Welcome aboard", + "ready_to_explore": "You’re ready to explore your personalized lessons.", + "finish": "Finish" }; static const Map> mapLocales = {"am": _am, "en": _en}; } diff --git a/lib/ui/common/translations/locale_keys.g.dart b/lib/ui/common/translations/locale_keys.g.dart index 3a88e2d..f7dcb52 100644 --- a/lib/ui/common/translations/locale_keys.g.dart +++ b/lib/ui/common/translations/locale_keys.g.dart @@ -136,7 +136,7 @@ abstract class LocaleKeys { static const ask_you_few_actions = 'ask_you_few_actions'; static const begin_level_practice = 'begin_level_practice'; 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 you_have_finished_practice = 'you_have_finished_practice'; static const view_results = 'view_results'; @@ -156,5 +156,34 @@ abstract class LocaleKeys { static const name_for_personalization = 'name_for_personalization'; static const choose_your_gender = 'choose_your_gender'; 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'; } diff --git a/lib/ui/common/validators/form_validator.dart b/lib/ui/common/validators/form_validator.dart index 556383d..647b2cb 100644 --- a/lib/ui/common/validators/form_validator.dart +++ b/lib/ui/common/validators/form_validator.dart @@ -57,7 +57,7 @@ class FormValidator { } if (value.isEmpty) { - return LocaleKeys.required_field.tr(); + return LocaleKeys.required_field.tr(); } return null; } diff --git a/lib/ui/views/account_privacy/account_privacy_viewmodel.dart b/lib/ui/views/account_privacy/account_privacy_viewmodel.dart index 8bc8d4b..01308b4 100644 --- a/lib/ui/views/account_privacy/account_privacy_viewmodel.dart +++ b/lib/ui/views/account_privacy/account_privacy_viewmodel.dart @@ -12,8 +12,7 @@ class AccountPrivacyViewModel extends ReactiveViewModel { final _localizationService = locator(); @override - List get listenableServices => - [ _localizationService]; + List get listenableServices => [_localizationService]; // Languages Map get _selectedLanguage => diff --git a/lib/ui/views/assessment/screens/assessment_intro_screen.dart b/lib/ui/views/assessment/screens/assessment_intro_screen.dart index 5602549..8315824 100644 --- a/lib/ui/views/assessment/screens/assessment_intro_screen.dart +++ b/lib/ui/views/assessment/screens/assessment_intro_screen.dart @@ -1,6 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; @@ -67,17 +69,19 @@ class AssessmentIntroScreen extends ViewModelWidget { showBackButton: true, onPop: viewModel.goBack, showLanguageSelection: true, - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildTitle() => Text( - 'Want a quick assessment to know your English level?', + LocaleKeys.want_quick_assessment.tr(), style: style25DG600, ); Widget _buildSubtitle() => Text( - 'Answer a few quick questions to help us understand your English proficiency.', + LocaleKeys.answer_quick_questions.tr(), style: style14MG400, ); @@ -96,9 +100,9 @@ class AssessmentIntroScreen extends ViewModelWidget { CustomElevatedButton( height: 55, safe: false, - text: 'Continue', borderRadius: 12, foregroundColor: kcWhite, + text: LocaleKeys.cont.tr(), backgroundColor: kcPrimaryColor, onTap: () async => await _next(viewModel), ); @@ -111,9 +115,9 @@ class AssessmentIntroScreen extends ViewModelWidget { Widget _buildSkipButton(AssessmentViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Skip', borderRadius: 12, backgroundColor: kcWhite, + text: LocaleKeys.skip.tr(), borderColor: kcPrimaryColor, foregroundColor: kcPrimaryColor, onTap: () => viewModel.next(page: 3), diff --git a/lib/ui/views/assessment/screens/assessment_questions_screen.dart b/lib/ui/views/assessment/screens/assessment_questions_screen.dart index 2235c4a..5651b31 100644 --- a/lib/ui/views/assessment/screens/assessment_questions_screen.dart +++ b/lib/ui/views/assessment/screens/assessment_questions_screen.dart @@ -1,6 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart'; @@ -134,8 +136,8 @@ class AssessmentQuestionsScreen extends ViewModelWidget { foregroundColor: kcWhite, text: viewModel.currentQuestionIndex == viewModel.assessmentQuestions.length - 1 - ? 'Finish Level' - : 'Continue', + ? LocaleKeys.finish_level.tr() + : LocaleKeys.cont.tr(), backgroundColor: viewModel.selectedAnswers .containsKey('${viewModel.currentQuestionIndex + 1}') ? kcPrimaryColor diff --git a/lib/ui/views/assessment/screens/assessment_result_screen.dart b/lib/ui/views/assessment/screens/assessment_result_screen.dart index 0a93b88..51fa739 100644 --- a/lib/ui/views/assessment/screens/assessment_result_screen.dart +++ b/lib/ui/views/assessment/screens/assessment_result_screen.dart @@ -1,7 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.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/large_app_bar.dart'; @@ -35,8 +37,10 @@ class AssessmentResultScreen extends ViewModelWidget { showBackButton: true, onPop: viewModel.goBack, showLanguageSelection: true, - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildExpandedBody(AssessmentViewModel viewModel) => @@ -75,13 +79,13 @@ class AssessmentResultScreen extends ViewModelWidget { ]; Widget _buildTitle(AssessmentViewModel viewModel) => Text( - 'You’re likely a ${viewModel.proficiencyLevel?.toUpperCase()} speaker!', + '${LocaleKeys.likely_speaker.tr()} ${viewModel.proficiencyLevel?.toUpperCase()}', style: style25DG600, textAlign: TextAlign.center, ); Widget _buildPrimarySubtitle() => Text( - 'Great Job! Here’s your next step to keep improving.', + LocaleKeys.great_job.tr(), style: style14MG400, textAlign: TextAlign.center, ); @@ -93,7 +97,7 @@ class AssessmentResultScreen extends ViewModelWidget { 'assets/icons/${viewModel.proficiencyLevel?.substring(0, 1).toLowerCase()}_${viewModel.proficiencyLevel?.substring(1).toLowerCase()}.svg'); Widget _buildSecondarySubtitle() => Text( - 'Let\'s start your practice', + LocaleKeys.lets_start_practice.tr(), style: style14DG400, textAlign: TextAlign.center, ); @@ -106,9 +110,9 @@ class AssessmentResultScreen extends ViewModelWidget { Widget _buildContinueButton(AssessmentViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Continue', borderRadius: 12, foregroundColor: kcWhite, + text: LocaleKeys.cont.tr(), onTap: () => viewModel.next(), backgroundColor: kcPrimaryColor, ); diff --git a/lib/ui/views/assessment/screens/start_lesson_screen.dart b/lib/ui/views/assessment/screens/start_lesson_screen.dart index 62df74e..b43ba38 100644 --- a/lib/ui/views/assessment/screens/start_lesson_screen.dart +++ b/lib/ui/views/assessment/screens/start_lesson_screen.dart @@ -1,7 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.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/large_app_bar.dart'; @@ -52,8 +54,10 @@ class StartLessonScreen extends ViewModelWidget { showBackButton: true, onPop: viewModel.goBack, showLanguageSelection: true, - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildExpandedBody(AssessmentViewModel viewModel) => @@ -92,7 +96,7 @@ class StartLessonScreen extends ViewModelWidget { Widget _buildTitle(AssessmentViewModel viewModel) => Text.rich( TextSpan( - text: 'Welcome aboard', + text: LocaleKeys.welcome_abroad.tr(), style: style25DG600, children: [ TextSpan( @@ -104,7 +108,7 @@ class StartLessonScreen extends ViewModelWidget { ); Widget _buildSubtitle() => Text( - 'You’re ready to explore your personalized lessons.', + LocaleKeys.ready_to_explore.tr(), style: style14MG400, ); @@ -116,9 +120,9 @@ class StartLessonScreen extends ViewModelWidget { Widget _buildContinueButton(AssessmentViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Finish', borderRadius: 12, foregroundColor: kcWhite, + text: LocaleKeys.finish.tr(), backgroundColor: kcPrimaryColor, onTap: () async => await _start(viewModel), ); diff --git a/lib/ui/views/call_support/call_support_view.dart b/lib/ui/views/call_support/call_support_view.dart index 211807f..b47b545 100644 --- a/lib/ui/views/call_support/call_support_view.dart +++ b/lib/ui/views/call_support/call_support_view.dart @@ -93,7 +93,7 @@ class CallSupportView extends StackedView { const CircularIcon(icon: Icons.call, size: 50, color: kcPrimaryColor); Widget _buildTitle() => Text( - LocaleKeys.call_our_support.tr(), + LocaleKeys.call_our_support.tr(), style: style25DG600, textAlign: TextAlign.center, ); @@ -116,7 +116,7 @@ class CallSupportView extends StackedView { leadingIcon: Icons.call, foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, - text:LocaleKeys.tap_to_call.tr(), + text: LocaleKeys.tap_to_call.tr(), onTap: () async => await viewModel.callSupport(), ); } diff --git a/lib/ui/views/forget_password/screens/request_reset_code_screen.dart b/lib/ui/views/forget_password/screens/request_reset_code_screen.dart index e0de93b..936c0e9 100644 --- a/lib/ui/views/forget_password/screens/request_reset_code_screen.dart +++ b/lib/ui/views/forget_password/screens/request_reset_code_screen.dart @@ -96,8 +96,10 @@ class RequestCodeScreen extends ViewModelWidget { showBackButton: true, showLanguageSelection: true, onPop: () => _inAppPop(viewModel), - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildExpandedBody( diff --git a/lib/ui/views/forget_password/screens/reset_password_screen.dart b/lib/ui/views/forget_password/screens/reset_password_screen.dart index 8d5807e..0ffcfaf 100644 --- a/lib/ui/views/forget_password/screens/reset_password_screen.dart +++ b/lib/ui/views/forget_password/screens/reset_password_screen.dart @@ -96,8 +96,10 @@ class ResetPasswordScreen extends ViewModelWidget { showBackButton: true, showLanguageSelection: true, onPop: () => _inAppPop(viewModel), - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildExpandedBody(ForgetPasswordViewModel viewModel) => diff --git a/lib/ui/views/home/home_view.dart b/lib/ui/views/home/home_view.dart index b9c5d90..89b3327 100644 --- a/lib/ui/views/home/home_view.dart +++ b/lib/ui/views/home/home_view.dart @@ -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 '../../widgets/coming_soon.dart'; -import '../course/course_view.dart'; import 'home_viewmodel.dart'; class HomeView extends StackedView { diff --git a/lib/ui/views/landing/landing_view.dart b/lib/ui/views/landing/landing_view.dart index d136a1c..471389a 100644 --- a/lib/ui/views/landing/landing_view.dart +++ b/lib/ui/views/landing/landing_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/views/landing/screens/first_landing_screen.dart'; import 'package:yimaru_app/ui/views/landing/screens/fourth_landing_screen.dart'; import 'package:yimaru_app/ui/views/landing/screens/second_landing_screen.dart'; @@ -23,19 +24,21 @@ class LandingView extends StackedView { LandingViewModel viewModel, Widget? child, ) => - _buildLandingScreens(viewModel); + _buildLandingScreensWrapper(viewModel); + + Widget _buildLandingScreensWrapper(LandingViewModel viewModel) => Scaffold( + backgroundColor: kcPrimaryColor, + body: _buildLandingScreens(viewModel), + ); Widget _buildLandingScreens(LandingViewModel viewModel) => FlutterCarousel( options: FlutterCarouselOptions( autoPlay: true, viewportFraction: 1, - showIndicator: true, indicatorMargin: 40, + showIndicator: false, height: double.maxFinite, - slideIndicator: CircularSlideIndicator( - slideIndicatorOptions: - const SlideIndicatorOptions(indicatorRadius: 2.5), - ), + controller: viewModel.controller, ), items: _buildScreens(), ); diff --git a/lib/ui/views/landing/landing_viewmodel.dart b/lib/ui/views/landing/landing_viewmodel.dart index 9e755f4..d58c71e 100644 --- a/lib/ui/views/landing/landing_viewmodel.dart +++ b/lib/ui/views/landing/landing_viewmodel.dart @@ -1,3 +1,4 @@ +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; @@ -11,6 +12,16 @@ class LandingViewModel extends BaseViewModel { final _authenticationService = locator(); + // Controller + final FlutterCarouselController _controller = FlutterCarouselController(); + + FlutterCarouselController get controller => _controller; + + // In-app navigation + void next() { + _controller.nextPage(); + } + // Navigation Future navigateToLogin() async => await _navigationService.replaceWithLoginView(); @@ -18,6 +29,7 @@ class LandingViewModel extends BaseViewModel { // Remote api call // First time install + Future setFirstTimeInstall() async { await runBusyFuture(_setFirstTimeInstall()); } diff --git a/lib/ui/views/landing/screens/first_landing_screen.dart b/lib/ui/views/landing/screens/first_landing_screen.dart index 7180f65..3d047c0 100644 --- a/lib/ui/views/landing/screens/first_landing_screen.dart +++ b/lib/ui/views/landing/screens/first_landing_screen.dart @@ -126,10 +126,10 @@ class FirstLandingScreen extends ViewModelWidget { Widget _buildContinueButton(LandingViewModel viewModel) => CustomElevatedButton( height: 55, + text: 'Next', borderRadius: 25, - text: 'Get Started', + onTap: viewModel.next, backgroundColor: kcWhite, foregroundColor: kcPrimaryColor, - onTap: () async => await viewModel.setFirstTimeInstall(), ); } diff --git a/lib/ui/views/landing/screens/second_landing_screen.dart b/lib/ui/views/landing/screens/second_landing_screen.dart index 47ee511..c4657d3 100644 --- a/lib/ui/views/landing/screens/second_landing_screen.dart +++ b/lib/ui/views/landing/screens/second_landing_screen.dart @@ -127,10 +127,10 @@ class SecondLandingScreen extends ViewModelWidget { Widget _buildContinueButton(LandingViewModel viewModel) => CustomElevatedButton( height: 55, + text: 'Next', borderRadius: 25, - text: 'Get Started', + onTap: viewModel.next, foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, - onTap: () async => await viewModel.setFirstTimeInstall(), ); } diff --git a/lib/ui/views/landing/screens/third_landing_screen.dart b/lib/ui/views/landing/screens/third_landing_screen.dart index 5ea816a..046314e 100644 --- a/lib/ui/views/landing/screens/third_landing_screen.dart +++ b/lib/ui/views/landing/screens/third_landing_screen.dart @@ -122,15 +122,15 @@ class ThirdLandingScreen extends ViewModelWidget { viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel); Widget _buildIndicator() => - const CustomCircularProgressIndicator(color: kcWhite); + const CustomCircularProgressIndicator(color: kcPrimaryColor); Widget _buildContinueButton(LandingViewModel viewModel) => CustomElevatedButton( height: 55, + text: 'Next', borderRadius: 25, - text: 'Get Started', + onTap: viewModel.next, foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, - onTap: () async => await viewModel.setFirstTimeInstall(), ); } diff --git a/lib/ui/views/language/language_view.dart b/lib/ui/views/language/language_view.dart index c37a1b7..a08e11d 100644 --- a/lib/ui/views/language/language_view.dart +++ b/lib/ui/views/language/language_view.dart @@ -108,16 +108,16 @@ class LanguageView extends StackedView { Widget _buildAppbar(LanguageViewModel viewModel) => SmallAppBar( showBackButton: true, onPop: viewModel.pop, - title:LocaleKeys.language_preference.tr() , + title: LocaleKeys.language_preference.tr(), ); Widget _buildTitle() => Text( - LocaleKeys.choose_your_language.tr(), + LocaleKeys.choose_your_language.tr(), style: style25DG600, ); Widget _buildSubtitle() => Text( - LocaleKeys.switch_language_anytime.tr() , + LocaleKeys.switch_language_anytime.tr(), style: style14MG400, ); diff --git a/lib/ui/views/learn_course/learn_course_view.dart b/lib/ui/views/learn_course/learn_course_view.dart index ad0e978..572a93c 100644 --- a/lib/ui/views/learn_course/learn_course_view.dart +++ b/lib/ui/views/learn_course/learn_course_view.dart @@ -89,6 +89,7 @@ class LearnCourseView extends StackedView { course: viewModel.courses[index], onViewTap: () async => await viewModel.navigateToLearnModule(viewModel.courses[index]), + onLockTap: () async => await viewModel.navigateToLearnSubscription(), onPracticeTap: () async => await viewModel.navigateToLearnPractice( id: viewModel.courses[index].id ?? 0, level: viewModel.courses[index].name ?? ''), @@ -99,11 +100,13 @@ class LearnCourseView extends StackedView { Widget _buildTile({ required LearnCourse course, required GestureTapCallback onViewTap, + required GestureTapCallback onLockTap, required GestureTapCallback onPracticeTap, }) => LearnCourseTile( course: course, onViewTap: onViewTap, + onLockTap: onLockTap, onPracticeTap: onPracticeTap, ); } diff --git a/lib/ui/views/learn_course/learn_course_viewmodel.dart b/lib/ui/views/learn_course/learn_course_viewmodel.dart index daa9bb5..6b3caac 100644 --- a/lib/ui/views/learn_course/learn_course_viewmodel.dart +++ b/lib/ui/views/learn_course/learn_course_viewmodel.dart @@ -29,6 +29,9 @@ class LearnCourseViewModel extends ReactiveViewModel { // Navigation void pop() => _navigationService.back(); + Future navigateToLearnSubscription() async => + await _navigationService.navigateToLearnSubscriptionView(); + Future navigateToLearnModule(LearnCourse course) async => _navigationService.navigateToLearnModuleView(course: course); @@ -39,7 +42,7 @@ class LearnCourseViewModel extends ReactiveViewModel { level: level, practice: LearnPractices.course, 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', ); diff --git a/lib/ui/views/learn_lesson/learn_lesson_view.dart b/lib/ui/views/learn_lesson/learn_lesson_view.dart index 3f4b80c..3f04346 100644 --- a/lib/ui/views/learn_lesson/learn_lesson_view.dart +++ b/lib/ui/views/learn_lesson/learn_lesson_view.dart @@ -152,7 +152,7 @@ class LearnLessonView extends StackedView { Widget _buildMotivationCard() => const MotivationCard(); Widget _buildHeader() => Text( - LocaleKeys.lessons_in_module.tr(), + LocaleKeys.lessons_in_module.tr(), style: style18DG700, ); diff --git a/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart b/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart index 38019ad..0c4a729 100644 --- a/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart +++ b/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart @@ -34,9 +34,9 @@ class LearnLessonViewModel extends ReactiveViewModel { await _navigationService.navigateToLearnPracticeView( id: id, practice: LearnPractices.lesson, - label:LocaleKeys.start_practice.tr(), + label: LocaleKeys.start_practice.tr(), title: LocaleKeys.lets_practice_module.tr(), - subtitle:LocaleKeys.ask_you_few_actions.tr(), + subtitle: LocaleKeys.ask_you_few_actions.tr(), ); Future navigateToLearnLessonDetail( diff --git a/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart index 6d14d17..2c58ac7 100644 --- a/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart +++ b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart @@ -192,7 +192,7 @@ class LearnLessonDetailView extends StackedView { borderRadius: 12, foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, - text:LocaleKeys.take_practice.tr() , + text: LocaleKeys.take_practice.tr(), onTap: () async => await _navigate(viewModel), ); } diff --git a/lib/ui/views/learn_module/learn_module_view.dart b/lib/ui/views/learn_module/learn_module_view.dart index d05b8b1..8213654 100644 --- a/lib/ui/views/learn_module/learn_module_view.dart +++ b/lib/ui/views/learn_module/learn_module_view.dart @@ -99,7 +99,7 @@ class LearnModuleView extends StackedView { ); Widget _buildSubtitle() => Text( - LocaleKeys.your_current_level.tr(), + LocaleKeys.your_current_level.tr(), style: style14P400, ); diff --git a/lib/ui/views/learn_module/learn_module_viewmodel.dart b/lib/ui/views/learn_module/learn_module_viewmodel.dart index 4cf84b7..85b20d8 100644 --- a/lib/ui/views/learn_module/learn_module_viewmodel.dart +++ b/lib/ui/views/learn_module/learn_module_viewmodel.dart @@ -29,8 +29,6 @@ class LearnModuleViewModel extends ReactiveViewModel { // Navigation void pop() => _navigationService.back(); - - Future navigateToLearnLesson(LearnModule module) async => await _navigationService.navigateToLearnLessonView(module: module); diff --git a/lib/ui/views/learn_practice/screens/interact_learn_practice_screen.dart b/lib/ui/views/learn_practice/screens/interact_learn_practice_screen.dart index 6ff6351..f0e18d4 100644 --- a/lib/ui/views/learn_practice/screens/interact_learn_practice_screen.dart +++ b/lib/ui/views/learn_practice/screens/interact_learn_practice_screen.dart @@ -127,7 +127,8 @@ class InteractLearnPracticeScreen showBackButton: true, onPop: () async => 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) => Column( @@ -154,13 +155,13 @@ class InteractLearnPracticeScreen : const SizedBox(height: 20); Widget _buildListeningLabel() => Text( - 'Daniel ${LocaleKeys.speaking.tr()}', + 'Daniel ${LocaleKeys.speaking.tr()}', style: style14P400, textAlign: TextAlign.center, ); Widget _buildSpeakingLabel() => Text( - '${ LocaleKeys.you_are_speaking.tr()}...', + '${LocaleKeys.you_are_speaking.tr()}...', style: style14P400, textAlign: TextAlign.center, ); @@ -239,7 +240,7 @@ class InteractLearnPracticeScreen ]; Widget _buildActionLabel() => Text( - LocaleKeys.tap_microphone.tr(), + LocaleKeys.tap_microphone.tr(), style: style14DG400, textAlign: TextAlign.center, ); @@ -359,7 +360,7 @@ class InteractLearnPracticeScreen CustomColumnButton( color: kcRed, icon: Icons.close, - label:LocaleKeys.cancel.tr() , + label: LocaleKeys.cancel.tr(), onTap: () async => await _showSheet(context: context, viewModel: viewModel), ); diff --git a/lib/ui/views/learn_practice/screens/learn_loading_screen.dart b/lib/ui/views/learn_practice/screens/learn_loading_screen.dart index 47ad432..b264998 100644 --- a/lib/ui/views/learn_practice/screens/learn_loading_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_loading_screen.dart @@ -67,7 +67,7 @@ class LearnLoadingScreen extends StatelessWidget { Align(alignment: Alignment.center, child: _buildRefreshButton()); Widget _buildRefreshButton() => NoDataIndicator( - onTap: onTap, - title: LocaleKeys.no_practice_available.tr(), + onTap: onTap, + title: LocaleKeys.no_practice_available.tr(), ); } diff --git a/lib/ui/views/learn_practice/screens/learn_practice_appreciation_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_appreciation_screen.dart index 6d3487c..f1260be 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_appreciation_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_appreciation_screen.dart @@ -139,7 +139,7 @@ class LearnPracticeAppreciationScreen ); Widget _buildSubtitle() => Text( - LocaleKeys.you_have_finished_practice.tr(), + LocaleKeys.you_have_finished_practice.tr(), style: style14DG400, textAlign: TextAlign.center, ); @@ -158,6 +158,5 @@ class LearnPracticeAppreciationScreen onTap: () => viewModel.goTo(4), backgroundColor: kcPrimaryColor, text: LocaleKeys.view_results.tr(), - ); } diff --git a/lib/ui/views/learn_practice/screens/learn_practice_description_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_description_screen.dart index 1572306..6928e7e 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_description_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_description_screen.dart @@ -180,6 +180,6 @@ class LearnPracticeDescriptionScreen foregroundColor: kcWhite, onTap: () => viewModel.goTo(2), backgroundColor: kcPrimaryColor, - text: LocaleKeys.start_practice.tr() , + text: LocaleKeys.start_practice.tr(), ); } diff --git a/lib/ui/views/learn_practice/screens/learn_practice_finish_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_finish_screen.dart index bee1bdc..c694516 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_finish_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_finish_screen.dart @@ -61,7 +61,7 @@ class LearnPracticeFinishScreen Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( showBackButton: true, onPop: viewModel.goBack, - title:LocaleKeys.practice_speaking.tr(), + title: LocaleKeys.practice_speaking.tr(), ); Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) => @@ -81,13 +81,13 @@ class LearnPracticeFinishScreen Widget _buildIcon() => SvgPicture.asset('assets/icons/success.svg'); Widget _buildTitle() => Text( - LocaleKeys.practice_completed.tr(), + LocaleKeys.practice_completed.tr(), style: style25DG600, textAlign: TextAlign.center, ); Widget _buildSubtitle() => Text( - LocaleKeys.sound_confident.tr() , + LocaleKeys.sound_confident.tr(), style: style14DG400, textAlign: TextAlign.center, ); @@ -120,7 +120,7 @@ class LearnPracticeFinishScreen onTap: viewModel.pop, foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, - text: LocaleKeys.continue_practice.tr() , + text: LocaleKeys.continue_practice.tr(), ); Widget _buildPracticeAgainButton(LearnPracticeViewModel viewModel) => diff --git a/lib/ui/views/learn_practice/screens/learn_practice_result_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_result_screen.dart index 0e7cd9c..f8a0fb3 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_result_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_result_screen.dart @@ -115,7 +115,7 @@ class LearnPracticeResultScreen required LearnPracticeViewModel viewModel}) => SmallAppBar( showBackButton: true, - title:LocaleKeys.result.tr(), + title: LocaleKeys.result.tr(), onPop: () async => await _showSheet(context: context, viewModel: viewModel), ); @@ -176,7 +176,7 @@ class LearnPracticeResultScreen height: 55, borderRadius: 12, foregroundColor: kcWhite, - text:LocaleKeys.cont.tr(), + text: LocaleKeys.cont.tr(), backgroundColor: kcPrimaryColor, onTap: () async => await _navigate(viewModel), ); diff --git a/lib/ui/views/learn_practice/screens/start_learn_practice_screen.dart b/lib/ui/views/learn_practice/screens/start_learn_practice_screen.dart index 87a8781..82e7c86 100644 --- a/lib/ui/views/learn_practice/screens/start_learn_practice_screen.dart +++ b/lib/ui/views/learn_practice/screens/start_learn_practice_screen.dart @@ -196,7 +196,7 @@ class StartLearnPracticeScreen extends ViewModelWidget { ]; Widget _buildActionLabel() => Text( - LocaleKeys.tap_start_to_listen.tr() , + LocaleKeys.tap_start_to_listen.tr(), style: style14DG400, textAlign: TextAlign.center, ); @@ -215,8 +215,8 @@ class StartLearnPracticeScreen extends ViewModelWidget { Widget _buildReplyButtonWrapper() => Expanded(child: _buildReplyButton()); - Widget _buildReplyButton() => CustomColumnButton( - icon: Icons.replay, label:LocaleKeys.reply.tr() , color: kcLightGrey); + Widget _buildReplyButton() => CustomColumnButton( + icon: Icons.replay, label: LocaleKeys.reply.tr(), color: kcLightGrey); Widget _buildMicButtonWrapper(LearnPracticeViewModel viewModel) => Expanded(child: _buildMicButton(viewModel)); diff --git a/lib/ui/views/learn_program/learn_program_view.dart b/lib/ui/views/learn_program/learn_program_view.dart index 774e451..38f985f 100644 --- a/lib/ui/views/learn_program/learn_program_view.dart +++ b/lib/ui/views/learn_program/learn_program_view.dart @@ -89,14 +89,12 @@ class LearnProgramView extends StackedView { program: viewModel.learnPrograms[index], onTap: () async => await viewModel .navigateToLearnCourse(viewModel.learnPrograms[index].id ?? 0), - onLockTap: () async => await viewModel.navigateToLearnSubscription(), ), ); Widget _buildTile({ required LearnProgram program, required GestureTapCallback onTap, - required GestureTapCallback onLockTap, }) => - LearnProgramTile(onTap: onTap, program: program,onLockTap: onLockTap,); + LearnProgramTile(onTap: onTap, program: program); } diff --git a/lib/ui/views/learn_program/learn_program_viewmodel.dart b/lib/ui/views/learn_program/learn_program_viewmodel.dart index 3227879..0c63a3a 100644 --- a/lib/ui/views/learn_program/learn_program_viewmodel.dart +++ b/lib/ui/views/learn_program/learn_program_viewmodel.dart @@ -39,9 +39,6 @@ class LearnProgramViewModel extends ReactiveViewModel { Future navigateToLearnCourse(int id) async => _navigationService.navigateToLearnCourseView(id: id); - Future navigateToLearnSubscription() async => - await _navigationService.navigateToLearnSubscriptionView(); - // Remote api call // Learn programs diff --git a/lib/ui/views/login/screens/login_otp_screen.dart b/lib/ui/views/login/screens/login_otp_screen.dart index 055d8de..8a3d0b1 100644 --- a/lib/ui/views/login/screens/login_otp_screen.dart +++ b/lib/ui/views/login/screens/login_otp_screen.dart @@ -92,8 +92,10 @@ class LoginOtpScreen extends ViewModelWidget { Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar( showBackButton: false, showLanguageSelection: true, - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildExpandedBody( diff --git a/lib/ui/views/login/screens/login_with_email_screen.dart b/lib/ui/views/login/screens/login_with_email_screen.dart index 196d9fb..6ae08bd 100644 --- a/lib/ui/views/login/screens/login_with_email_screen.dart +++ b/lib/ui/views/login/screens/login_with_email_screen.dart @@ -88,8 +88,10 @@ class LoginWithEmailScreen extends ViewModelWidget { Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar( showBackButton: false, showLanguageSelection: true, - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildExpandedBody( diff --git a/lib/ui/views/login/screens/login_with_phone_number_screen.dart b/lib/ui/views/login/screens/login_with_phone_number_screen.dart index 8d26215..78991eb 100644 --- a/lib/ui/views/login/screens/login_with_phone_number_screen.dart +++ b/lib/ui/views/login/screens/login_with_phone_number_screen.dart @@ -85,8 +85,10 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget { showBackButton: true, showLanguageSelection: true, onPop: () => viewModel.goTo(0), - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildExpandedBody( diff --git a/lib/ui/views/onboarding/onboarding_view.dart b/lib/ui/views/onboarding/onboarding_view.dart index 623ac2d..2f7b220 100644 --- a/lib/ui/views/onboarding/onboarding_view.dart +++ b/lib/ui/views/onboarding/onboarding_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.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/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/occupation_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 'onboarding_viewmodel.dart'; import 'onboarding_view.form.dart'; @FormView(fields: [ - FormTextField(name: 'topic', validator: FormValidator.validateForm), FormTextField( name: 'fullName', validator: FormValidator.validateFullNameForm), FormTextField(name: 'region', validator: FormValidator.validateForm), - FormTextField(name: 'challenge', validator: FormValidator.validateForm), - FormTextField(name: 'languageGoal', validator: FormValidator.validateForm), ]) class OnboardingView extends StackedView with $OnboardingView { @@ -34,11 +33,8 @@ class OnboardingView extends StackedView } void _initClearData() { - topicController.clear(); regionController.clear(); fullNameController.clear(); - challengeController.clear(); - languageGoalController.clear(); } void _clearDataOnNavigation(OnboardingViewModel viewModel) { @@ -54,17 +50,15 @@ class OnboardingView extends StackedView } else if (viewModel.currentPage == 4) { viewModel.resetOccupationFormScreen(); } else if (viewModel.currentPage == 5) { + regionController.clear(); viewModel.resetCountryRegionFormScreen(); } else if (viewModel.currentPage == 6) { viewModel.resetLearningGoalFormScreen(); } else if (viewModel.currentPage == 7) { - languageGoalController.clear(); viewModel.resetLanguageGoalFormScreen(); } else if (viewModel.currentPage == 8) { - challengeController.clear(); viewModel.resetChallengeFormScreen(); } else if (viewModel.currentPage == 9) { - topicController.clear(); viewModel.resetTopicFormScreen(); } } @@ -77,7 +71,7 @@ class OnboardingView extends StackedView } @override - void onViewModelReady(OnboardingViewModel viewModel) { + void onViewModelReady(OnboardingViewModel viewModel) async { _initClearData(); _initUserData(viewModel); syncFormWithViewModel(viewModel); @@ -134,17 +128,13 @@ class OnboardingView extends StackedView Widget _buildOccupationForm() => const OccupationFormScreen(); - Widget _buildCountryRegionForm() => CountryRegionFormScreen( - regionController: regionController, - ); + Widget _buildCountryRegionForm() => CountryRegionFormScreen(regionController: regionController); Widget _buildLearningGoalForm() => const LearningGoalFormScreen(); - Widget _buildLanguageGoalForm() => - LanguageGoalFormScreen(languageGoalController: languageGoalController); + Widget _buildLanguageGoalForm() => const LanguageGoalFormScreen(); - Widget _buildChallengeForm() => - ChallengeFormScreen(challengeController: challengeController); + Widget _buildChallengeForm() => const ChallengeFormScreen(); - Widget _buildTopicForm() => TopicFormScreen(topicController: topicController); + Widget _buildTopicForm() => const TopicFormScreen(); } diff --git a/lib/ui/views/onboarding/onboarding_view.form.dart b/lib/ui/views/onboarding/onboarding_view.form.dart index 453c5c7..02220e1 100644 --- a/lib/ui/views/onboarding/onboarding_view.form.dart +++ b/lib/ui/views/onboarding/onboarding_view.form.dart @@ -13,11 +13,8 @@ import 'package:yimaru_app/ui/common/validators/form_validator.dart'; const bool _autoTextFieldValidation = true; -const String TopicValueKey = 'topic'; const String FullNameValueKey = 'fullName'; const String RegionValueKey = 'region'; -const String ChallengeValueKey = 'challenge'; -const String LanguageGoalValueKey = 'languageGoal'; final Map _OnboardingViewTextEditingControllers = {}; @@ -25,31 +22,18 @@ final Map _OnboardingViewTextEditingControllers = final Map _OnboardingViewFocusNodes = {}; final Map _OnboardingViewTextValidations = { - TopicValueKey: FormValidator.validateForm, FullNameValueKey: FormValidator.validateFullNameForm, RegionValueKey: FormValidator.validateForm, - ChallengeValueKey: FormValidator.validateForm, - LanguageGoalValueKey: FormValidator.validateForm, }; mixin $OnboardingView { - TextEditingController get topicController => - _getFormTextEditingController(TopicValueKey); TextEditingController get fullNameController => _getFormTextEditingController(FullNameValueKey); TextEditingController get regionController => _getFormTextEditingController(RegionValueKey); - TextEditingController get challengeController => - _getFormTextEditingController(ChallengeValueKey); - TextEditingController get languageGoalController => - _getFormTextEditingController(LanguageGoalValueKey); - FocusNode get topicFocusNode => _getFormFocusNode(TopicValueKey); FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey); FocusNode get regionFocusNode => _getFormFocusNode(RegionValueKey); - FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey); - FocusNode get languageGoalFocusNode => - _getFormFocusNode(LanguageGoalValueKey); TextEditingController _getFormTextEditingController( String key, { @@ -75,11 +59,8 @@ mixin $OnboardingView { /// Registers a listener on every generated controller that calls [model.setData()] /// with the latest textController values void syncFormWithViewModel(FormStateHelper model) { - topicController.addListener(() => _updateFormData(model)); fullNameController.addListener(() => _updateFormData(model)); regionController.addListener(() => _updateFormData(model)); - challengeController.addListener(() => _updateFormData(model)); - languageGoalController.addListener(() => _updateFormData(model)); _updateFormData(model, forceValidate: _autoTextFieldValidation); } @@ -91,11 +72,8 @@ mixin $OnboardingView { 'This feature was deprecated after 3.1.0.', ) void listenToFormUpdated(FormViewModel model) { - topicController.addListener(() => _updateFormData(model)); fullNameController.addListener(() => _updateFormData(model)); regionController.addListener(() => _updateFormData(model)); - challengeController.addListener(() => _updateFormData(model)); - languageGoalController.addListener(() => _updateFormData(model)); _updateFormData(model, forceValidate: _autoTextFieldValidation); } @@ -105,11 +83,8 @@ mixin $OnboardingView { model.setData( model.formValueMap ..addAll({ - TopicValueKey: topicController.text, FullNameValueKey: fullNameController.text, RegionValueKey: regionController.text, - ChallengeValueKey: challengeController.text, - LanguageGoalValueKey: languageGoalController.text, }), ); @@ -151,22 +126,8 @@ extension ValueProperties on FormStateHelper { return !hasAnyValidationMessage; } - String? get topicValue => this.formValueMap[TopicValueKey] as String?; String? get fullNameValue => this.formValueMap[FullNameValueKey] 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) { 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 => this.formValueMap.containsKey(FullNameValueKey) && (fullNameValue?.isNotEmpty ?? false); bool get hasRegion => this.formValueMap.containsKey(RegionValueKey) && (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 => this.fieldsValidationMessages[FullNameValueKey]?.isNotEmpty ?? false; bool get hasRegionValidationMessage => 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 => this.fieldsValidationMessages[FullNameValueKey]; String? get regionValidationMessage => this.fieldsValidationMessages[RegionValueKey]; - String? get challengeValidationMessage => - this.fieldsValidationMessages[ChallengeValueKey]; - String? get languageGoalValidationMessage => - this.fieldsValidationMessages[LanguageGoalValueKey]; } extension Methods on FormStateHelper { - void setTopicValidationMessage(String? validationMessage) => - this.fieldsValidationMessages[TopicValueKey] = validationMessage; void setFullNameValidationMessage(String? validationMessage) => this.fieldsValidationMessages[FullNameValueKey] = validationMessage; void setRegionValidationMessage(String? 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 void clearForm() { - topicValue = ''; fullNameValue = ''; regionValue = ''; - challengeValue = ''; - languageGoalValue = ''; } /// Validates text input fields on the Form void validateForm() { this.setValidationMessages({ - TopicValueKey: getValidationMessage(TopicValueKey), FullNameValueKey: getValidationMessage(FullNameValueKey), RegionValueKey: getValidationMessage(RegionValueKey), - ChallengeValueKey: getValidationMessage(ChallengeValueKey), - LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey), }); } } @@ -299,9 +204,6 @@ String? getValidationMessage(String key) { /// Updates the fieldsValidationMessages on the FormViewModel void updateValidationData(FormStateHelper model) => model.setValidationMessages({ - TopicValueKey: getValidationMessage(TopicValueKey), FullNameValueKey: getValidationMessage(FullNameValueKey), RegionValueKey: getValidationMessage(RegionValueKey), - ChallengeValueKey: getValidationMessage(ChallengeValueKey), - LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey), }); diff --git a/lib/ui/views/onboarding/onboarding_viewmodel.dart b/lib/ui/views/onboarding/onboarding_viewmodel.dart index d418b8a..3424e03 100644 --- a/lib/ui/views/onboarding/onboarding_viewmodel.dart +++ b/lib/ui/views/onboarding/onboarding_viewmodel.dart @@ -3,8 +3,13 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/app/app.router.dart'; import '../../../app/app.locator.dart'; +import '../../../models/field_option.dart'; +import '../../../services/api_service.dart'; import '../../../services/google_auth_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 with FormStateHelper @@ -15,6 +20,8 @@ class OnboardingViewModel extends ReactiveViewModel final _googleAuthService = locator(); + final _onboardingService = locator(); + final _localizationService = locator(); @override @@ -47,19 +54,15 @@ class OnboardingViewModel extends ReactiveViewModel bool get focusFullName => _focusFullName; // Educational background - final List _educationalBackgrounds = [ - 'No formal education', - 'Primary school', - 'Secondary /High school', - 'College / Diploma', - 'Bachelor’s and above', - ]; + List get _educationalBackgrounds => + _onboardingService.educationalBackgrounds; - List get educationalBackgrounds => _educationalBackgrounds; + List get educationalBackgrounds => _educationalBackgrounds; - String? _selectedEducationalBackground; + FieldOption? _selectedEducationalBackground; - String? get selectedEducationalBackground => _selectedEducationalBackground; + FieldOption? get selectedEducationalBackground => + _selectedEducationalBackground; // Gender final List _gendersEn = [ @@ -81,150 +84,73 @@ class OnboardingViewModel extends ReactiveViewModel String? get selectedGender => _selectedGender; // Age group - final List> _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 get _ageGroups => _onboardingService.ageGroups; - List> get ageGroups => _ageGroups; + List get ageGroups => _ageGroups; - Map? _selectedAgeGroup; + FieldOption? _selectedAgeGroup; - Map? get selectedAgeGroup => _selectedAgeGroup; + FieldOption? get selectedAgeGroup => _selectedAgeGroup; // Occupation - String _selectedOccupation = 'Students (High school & University)'; + FieldOption? _selectedOccupation; - String get selectedOccupation => _selectedOccupation; + FieldOption? get selectedOccupation => _selectedOccupation; // Country - String _selectedCountry = 'Ethiopia'; + FieldOption? _selectedCountry; - String get selectedCountry => _selectedCountry; + FieldOption? get selectedCountry => _selectedCountry; // Region bool _focusRegion = false; bool get focusRegion => _focusRegion; - bool _dropdownRegion = true; + bool _dropdownRegion = false; bool get dropdownRegion => _dropdownRegion; - String _selectedRegion = 'Addis Ababa'; + FieldOption? _selectedRegion; - String get selectedRegion => _selectedRegion; + FieldOption? get selectedRegion => _selectedRegion; // Learning goal - String? _selectedLearningGoal; + FieldOption? _selectedLearningGoal; - String? get selectedLearningGoal => _selectedLearningGoal; + FieldOption? get selectedLearningGoal => _selectedLearningGoal; - final List> _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 get _learningGoals => _onboardingService.learningGoals; - List> get learningGoals => _learningGoals; + List get learningGoals => _learningGoals; // Learning reason - bool _showLanguageGoalTextBox = false; - bool get showLanguageGoalTextBox => _showLanguageGoalTextBox; + FieldOption? _selectedLanguageGoal; - bool _focusLanguageGoal = false; + FieldOption? get selectedLanguageGoal => _selectedLanguageGoal; - bool get focusLanguageGoal => _focusLanguageGoal; + List get _languageGoals => _onboardingService.languageGoals; - String? _selectedLanguageGoal; - - String? get selectedLanguageGoal => _selectedLanguageGoal; - - final List _languageGoals = [ - 'Speak confidently at work or school', - 'Travel or handle daily situations', - 'Connect with family or friends', - 'General skills expansion', - 'Other' - ]; - - List get languageGoals => _languageGoals; + List get languageGoals => _languageGoals; // Challenges - bool _showChallengeTextBox = false; + FieldOption? _selectedChallenge; - bool get showChallengeTextBox => _showChallengeTextBox; + FieldOption? get selectedChallenge => _selectedChallenge; - bool _focusChallenge = false; + List get _challenges => _onboardingService.challenges; - bool get focusChallenge => _focusChallenge; - - String? _selectedChallenge; - - String? get selectedChallenge => _selectedChallenge; - - final List _challenges = [ - 'Pronunciation', - 'Finding words or grammar quickly', - 'Feeling nervous or lacking confidence', - 'Understanding accents or fast speech', - 'Other' - ]; - - List get challenges => _challenges; + List get challenges => _challenges; // Topic - bool _showTopicTextBox = false; + FieldOption? _selectedTopic; - bool get showTopicTextBox => _showTopicTextBox; + FieldOption? get selectedTopic => _selectedTopic; - bool _focusTopic = false; + List get _topics => _onboardingService.topics; - bool get focusTopic => _focusTopic; - - String? _selectedTopic; - - String? get selectedTopic => _selectedTopic; - - final List _topics = [ - 'Food & Cooking', - 'Hobbies, Sports, Music', - 'Tech, News, Business', - 'Travel, Places, Culture', - 'Other' - ]; - - List get topics => _topics; + List get topics => _topics; // User data final Map _userData = {}; @@ -238,12 +164,12 @@ class OnboardingViewModel extends ReactiveViewModel } // Education background - void setSelectedEducationalBackground(String value) { + void setSelectedEducationalBackground(FieldOption value) { _selectedEducationalBackground = value; rebuildUi(); } - bool isSelectedEducationalBackground(String value) => + bool isSelectedEducationalBackground(FieldOption value) => _selectedEducationalBackground == value; // Gender @@ -255,192 +181,35 @@ class OnboardingViewModel extends ReactiveViewModel bool isSelectedGender(String value) => _selectedGender == value; // Age group - void setSelectedAgeGroup(Map value) { + void setSelectedAgeGroup(FieldOption value) { _selectedAgeGroup = value; rebuildUi(); } - bool isSelectedAgeGroup(Map value) => - _selectedAgeGroup == value; + bool isSelectedAgeGroup(FieldOption value) => _selectedAgeGroup == value; // Occupation - List getOccupations() => [ - '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)' - ]; + List get _occupations => _onboardingService.occupations; - void setSelectedOccupation(String value) { + List get occupations => _occupations; + + void setSelectedOccupation(FieldOption? value) { _selectedOccupation = value; rebuildUi(); } // Country - List getCountries() => [ - "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" - ]; + List get _countries => _onboardingService.countries; - void setSelectedCountry(String value) { + List get countries => _countries; + + void setSelectedCountry(FieldOption? value) { _selectedCountry = value; - - if (value == 'Ethiopia') { + if (value?.label?.toLowerCase().trim() == 'ethiopia') { _dropdownRegion = true; - _selectedRegion = 'Addis Ababa'; + _selectedRegion = _regions + .firstWhere((e) => e.label?.toLowerCase().trim() == 'addis ababa'); } else { _dropdownRegion = false; } @@ -449,24 +218,11 @@ class OnboardingViewModel extends ReactiveViewModel } // Region - List getRegions() => [ - 'Addis Ababa', - 'Afar', - 'Amhara', - 'Benishangul-Gumuz', - 'Central Ethiopia', - 'Dire Dawa', - 'Gambela', - 'Harari', - 'Oromia', - 'Sidama', - 'Somali', - 'South Ethiopia', - 'South West Ethiopia Peoples', - 'Tigray', - ]; + List get _regions => _onboardingService.regions; - void setSelectedRegion(String value) { + List get regions => _regions; + + void setSelectedRegion(FieldOption? value) { _selectedRegion = value; rebuildUi(); } @@ -482,75 +238,42 @@ class OnboardingViewModel extends ReactiveViewModel } // Learning goal - void setSelectedLearningGoal(String value) { + void setSelectedLearningGoal(FieldOption value) { _selectedLearningGoal = value; rebuildUi(); } - bool isSelectedLearningGoal(String value) => _selectedLearningGoal == value; + bool isSelectedLearningGoal(FieldOption value) => + _selectedLearningGoal == value; // Learning reason - void setLanguageGoalFocus() { - _focusLanguageGoal = true; - rebuildUi(); - } - void setSelectedLanguageGoal(String value) { + void setSelectedLanguageGoal(FieldOption value) { _selectedLanguageGoal = value; - if (value.toLowerCase() == 'other') { - _showLanguageGoalTextBox = true; - } else { - if (_showLanguageGoalTextBox) { - _showLanguageGoalTextBox = false; - _focusLanguageGoal = false; - } - } + rebuildUi(); } - bool isSelectedLanguageGoal(String value) => _selectedLanguageGoal == value; + bool isSelectedLanguageGoal(FieldOption value) => + _selectedLanguageGoal == value; // Challenges - void setChallengesFocus() { - _focusChallenge = true; - rebuildUi(); - } - void setSelectedChallenge(String value) { + void setSelectedChallenge(FieldOption value) { _selectedChallenge = value; - if (value.toLowerCase() == 'other') { - _showChallengeTextBox = true; - } else { - if (_showChallengeTextBox) { - _showChallengeTextBox = false; - _focusChallenge = false; - } - } rebuildUi(); } - bool isSelectedChallenge(String value) => _selectedChallenge == value; + bool isSelectedChallenge(FieldOption value) => _selectedChallenge == value; // Topics - void setTopicsFocus() { - _focusTopic = true; - rebuildUi(); - } - void setSelectedTopic(String value) { + void setSelectedTopic(FieldOption value) { _selectedTopic = value; - if (value.toLowerCase() == 'other') { - _showTopicTextBox = true; - } else { - if (_showTopicTextBox) { - _showTopicTextBox = false; - _focusTopic = false; - } - } rebuildUi(); } - bool isSelectedTopic(String value) => _selectedTopic == value; + bool isSelectedTopic(FieldOption value) => _selectedTopic == value; // Add user data void addUserData(Map data) { @@ -589,15 +312,15 @@ class OnboardingViewModel extends ReactiveViewModel // Reset occupation form screen void resetOccupationFormScreen() { - _selectedOccupation = 'Students (High school & University)'; + _selectedOccupation = null; rebuildUi(); } // Reset country region form screen void resetCountryRegionFormScreen() { _focusRegion = false; - _selectedCountry = 'Ethiopia'; - _selectedRegion = 'Addis Ababa'; + _selectedRegion = null; + _selectedCountry = null; rebuildUi(); } @@ -609,26 +332,20 @@ class OnboardingViewModel extends ReactiveViewModel // Reset language goal form screen void resetLanguageGoalFormScreen() { - _focusLanguageGoal = false; _selectedLanguageGoal = null; - _showLanguageGoalTextBox = false; rebuildUi(); } // Reset challenge form screen void resetChallengeFormScreen() { - _focusChallenge = false; _selectedChallenge = null; - _showChallengeTextBox = false; rebuildUi(); } // Reset topic form screen void resetTopicFormScreen() { - _focusTopic = false; _selectedTopic = null; - _showTopicTextBox = false; rebuildUi(); } diff --git a/lib/ui/views/onboarding/screens/age_group_form_screen.dart b/lib/ui/views/onboarding/screens/age_group_form_screen.dart index d31ead4..6a6e351 100644 --- a/lib/ui/views/onboarding/screens/age_group_form_screen.dart +++ b/lib/ui/views/onboarding/screens/age_group_form_screen.dart @@ -1,6 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; @@ -19,9 +21,7 @@ class AgeGroupFormScreen extends ViewModelWidget { Future _next(OnboardingViewModel viewModel) async { FocusManager.instance.primaryFocus?.unfocus(); - Map data = { - 'age_group': viewModel.selectedAgeGroup?.values.first - }; + Map data = {'age_group': viewModel.selectedAgeGroup?.code}; viewModel.addUserData(data); viewModel.next(); @@ -88,17 +88,19 @@ class AgeGroupFormScreen extends ViewModelWidget { showBackButton: true, showLanguageSelection: true, onPop: () => _pop(viewModel), - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildTitle() => Text( - 'Which age range are you in?', + LocaleKeys.age_range.tr(), style: style25DG600, ); Widget _buildSubtitle() => Text( - 'We’ll personalize your learning experience based on your age.', + LocaleKeys.age_for_personalization.tr(), style: style14DG400, ); @@ -107,7 +109,7 @@ class AgeGroupFormScreen extends ViewModelWidget { itemCount: viewModel.ageGroups.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildAgeGroup( - title: viewModel.ageGroups[index].keys.first, + title: viewModel.ageGroups[index].label ?? '', selected: viewModel.isSelectedAgeGroup(viewModel.ageGroups[index]), onTap: () => viewModel.setSelectedAgeGroup(viewModel.ageGroups[index]), @@ -132,9 +134,9 @@ class AgeGroupFormScreen extends ViewModelWidget { Widget _buildContinueButton(OnboardingViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Continue', borderRadius: 12, foregroundColor: kcWhite, + text: LocaleKeys.cont.tr(), backgroundColor: viewModel.selectedAgeGroup != null ? kcPrimaryColor : kcPrimaryColor.withOpacity(0.1), diff --git a/lib/ui/views/onboarding/screens/challenge_form_screen.dart b/lib/ui/views/onboarding/screens/challenge_form_screen.dart index 9c9563c..1bcbbce 100644 --- a/lib/ui/views/onboarding/screens/challenge_form_screen.dart +++ b/lib/ui/views/onboarding/screens/challenge_form_screen.dart @@ -1,6 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.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'; class ChallengeFormScreen extends ViewModelWidget { - final TextEditingController challengeController; - - const ChallengeFormScreen({super.key, required this.challengeController}); + const ChallengeFormScreen({super.key}); void _pop(OnboardingViewModel viewModel) { - challengeController.clear(); viewModel.resetChallengeFormScreen(); viewModel.goBack(); } @@ -23,8 +22,7 @@ class ChallengeFormScreen extends ViewModelWidget { FocusManager.instance.primaryFocus?.unfocus(); Map data = { - 'language_challange': - viewModel.selectedChallenge ?? challengeController.text, + 'language_challange': viewModel.selectedChallenge?.code, }; viewModel.addUserData(data); @@ -86,15 +84,6 @@ class ChallengeFormScreen extends ViewModelWidget { _buildSubtitle(), verticalSpaceMedium, _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, ]; @@ -102,17 +91,19 @@ class ChallengeFormScreen extends ViewModelWidget { showBackButton: true, showLanguageSelection: true, onPop: () => _pop(viewModel), - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildTitle() => Text( - 'What challenge do you face most with English?', + LocaleKeys.challenge_you_face.tr(), style: style25DG600, ); Widget _buildSubtitle() => Text( - 'Everyone has struggles, let’s start fixing yours 😊', + '${LocaleKeys.evey_one_has_strugle.tr()} 😊', style: style14MG400, ); @@ -122,7 +113,7 @@ class ChallengeFormScreen extends ViewModelWidget { itemCount: viewModel.challenges.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildChallenge( - title: viewModel.challenges[index], + title: viewModel.challenges[index].label ?? '', onTap: () => viewModel.setSelectedChallenge(viewModel.challenges[index]), selected: viewModel.isSelectedChallenge(viewModel.challenges[index]), @@ -139,27 +130,6 @@ class ChallengeFormScreen extends ViewModelWidget { 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( padding: const EdgeInsets.only(bottom: 50), child: _buildContinueButton(viewModel), @@ -168,21 +138,13 @@ class ChallengeFormScreen extends ViewModelWidget { Widget _buildContinueButton(OnboardingViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Continue', borderRadius: 12, foregroundColor: kcWhite, + text: LocaleKeys.cont.tr(), onTap: viewModel.selectedChallenge != null - ? viewModel.selectedChallenge?.toLowerCase() == 'other' - ? challengeController.text.isNotEmpty - ? () => _next(viewModel) - : null - : () => _next(viewModel) + ? () => _next(viewModel) : null, backgroundColor: viewModel.selectedChallenge != null - ? viewModel.selectedChallenge?.toLowerCase() == 'other' - ? challengeController.text.isNotEmpty - ? kcPrimaryColor - : kcPrimaryColor.withOpacity(0.1) - : kcPrimaryColor + ? kcPrimaryColor : kcPrimaryColor.withOpacity(0.1)); } diff --git a/lib/ui/views/onboarding/screens/country_region_form_screen.dart b/lib/ui/views/onboarding/screens/country_region_form_screen.dart index e629288..64f00ff 100644 --- a/lib/ui/views/onboarding/screens/country_region_form_screen.dart +++ b/lib/ui/views/onboarding/screens/country_region_form_screen.dart @@ -1,11 +1,14 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.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/widgets/custom_dropdown.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; +import '../../../../models/field_option.dart'; import '../onboarding_view.form.dart'; class CountryRegionFormScreen extends ViewModelWidget { @@ -13,10 +16,10 @@ class CountryRegionFormScreen extends ViewModelWidget { const CountryRegionFormScreen({super.key, required this.regionController}); void _setSelectedCountry( - {String? value, required OnboardingViewModel viewModel}) { - viewModel.setSelectedCountry(value ?? 'Ethiopia'); + {FieldOption? value, required OnboardingViewModel viewModel}) { + viewModel.setSelectedCountry(value); - if (viewModel.selectedCountry != 'Ethiopia') { + if (viewModel.selectedCountry?.label?.toLowerCase().tr() != 'ethiopia') { regionController.clear(); viewModel.unsetRegionFocus(); } @@ -32,9 +35,9 @@ class CountryRegionFormScreen extends ViewModelWidget { Map data = { 'region': viewModel.dropdownRegion - ? viewModel.selectedRegion + ? viewModel.selectedRegion?.code : regionController.text, - 'country': viewModel.selectedCountry, + 'country': viewModel.selectedCountry?.code, }; viewModel.addUserData(data); @@ -112,26 +115,28 @@ class CountryRegionFormScreen extends ViewModelWidget { showBackButton: true, showLanguageSelection: true, onPop: () => _pop(viewModel), - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildTitle() => Text( - 'Where are you from?', + LocaleKeys.location.tr(), style: style25DG600, ); Widget _buildSubtitle() => Text( - 'Select your country and region from the dropdown', + LocaleKeys.select_country_region.tr(), style: style14MG400, ); Widget _buildCountryDropDown(OnboardingViewModel viewModel) => CustomDropdownPicker( - hint: 'Select country', icon: _buildSearchIcon(), + hint: LocaleKeys.select_country.tr(), selectedItem: viewModel.selectedCountry, - items: (value, props) => viewModel.getCountries(), + items: (value, props) => viewModel.countries, onChanged: (value) => _setSelectedCountry(value: value, viewModel: viewModel)); @@ -142,19 +147,18 @@ class CountryRegionFormScreen extends ViewModelWidget { Widget _buildRegionDropDown(OnboardingViewModel viewModel) => CustomDropdownPicker( - hint: 'Select region', icon: _buildSearchIcon(), + hint: LocaleKeys.select_region.tr(), selectedItem: viewModel.selectedRegion, - items: (value, props) => viewModel.getRegions(), - onChanged: (value) => - viewModel.setSelectedRegion(value ?? 'Addis Ababa')); + items: (value, props) => viewModel.regions, + onChanged: (value) => viewModel.setSelectedRegion(value)); Widget _buildRegionFormField(OnboardingViewModel viewModel) => TextFormField( controller: regionController, onTap: viewModel.setRegionFocus, decoration: inputDecoration( - hint: 'Enter Your City', focus: viewModel.focusRegion, + hint: LocaleKeys.enter_your_city.tr(), filled: regionController.text.isNotEmpty), ); @@ -181,18 +185,26 @@ class CountryRegionFormScreen extends ViewModelWidget { Widget _buildContinueButton(OnboardingViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Continue', borderRadius: 12, foregroundColor: kcWhite, - onTap: !viewModel.dropdownRegion - ? regionController.text.isNotEmpty - ? () => _next(viewModel) - : null - : () => _next(viewModel), - backgroundColor: !viewModel.dropdownRegion - ? regionController.text.isNotEmpty - ? kcPrimaryColor - : kcPrimaryColor.withOpacity(0.1) - : kcPrimaryColor, + text: LocaleKeys.cont.tr(), + onTap: viewModel.selectedCountry != null + ? !viewModel.dropdownRegion + ? regionController.text.isNotEmpty + ? () => _next(viewModel) + : null + : viewModel.selectedRegion != null + ? () => _next(viewModel) + : null + : null, + backgroundColor: viewModel.selectedCountry != null + ? !viewModel.dropdownRegion + ? regionController.text.isNotEmpty + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.1) + : viewModel.selectedRegion != null + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.1) + : kcPrimaryColor.withOpacity(0.1), ); } diff --git a/lib/ui/views/onboarding/screens/educational_background_form_screen.dart b/lib/ui/views/onboarding/screens/educational_background_form_screen.dart index 91a0cbf..b01743c 100644 --- a/lib/ui/views/onboarding/screens/educational_background_form_screen.dart +++ b/lib/ui/views/onboarding/screens/educational_background_form_screen.dart @@ -1,6 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; @@ -20,7 +22,7 @@ class EducationalBackgroundFormScreen FocusManager.instance.primaryFocus?.unfocus(); Map data = { - 'education_level': viewModel.selectedEducationalBackground + 'education_level': viewModel.selectedEducationalBackground?.code }; viewModel.addUserData(data); @@ -88,22 +90,20 @@ class EducationalBackgroundFormScreen showBackButton: true, showLanguageSelection: true, onPop: () => _pop(viewModel), - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); - Widget _buildTitle() => const Text( - 'What’s your current educational level?', - style: TextStyle( - fontSize: 25, - color: kcDarkGrey, - fontWeight: FontWeight.w600, - ), + Widget _buildTitle() => Text( + LocaleKeys.educational_background.tr(), + style: style25DG600, ); - Widget _buildSubtitle() => const Text( - 'This helps us tailor your lessons to your experience.', - style: TextStyle(color: kcMediumGrey), + Widget _buildSubtitle() => Text( + LocaleKeys.education_for_personalization.tr(), + style: style14MG400, ); Widget _buildEducationalLevels(OnboardingViewModel viewModel) => @@ -112,7 +112,7 @@ class EducationalBackgroundFormScreen physics: const NeverScrollableScrollPhysics(), itemCount: viewModel.educationalBackgrounds.length, itemBuilder: (context, index) => _buildEducationalLevel( - title: viewModel.educationalBackgrounds[index], + title: viewModel.educationalBackgrounds[index].label ?? '', selected: viewModel.isSelectedEducationalBackground( viewModel.educationalBackgrounds[index]), onTap: () => viewModel.setSelectedEducationalBackground( @@ -138,9 +138,9 @@ class EducationalBackgroundFormScreen Widget _buildContinueButton(OnboardingViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Continue', borderRadius: 12, foregroundColor: kcWhite, + text: LocaleKeys.cont.tr(), onTap: viewModel.selectedEducationalBackground != null ? () => _next(viewModel) : null, diff --git a/lib/ui/views/onboarding/screens/full_name_form_screen.dart b/lib/ui/views/onboarding/screens/full_name_form_screen.dart index 1ccf894..7a03fda 100644 --- a/lib/ui/views/onboarding/screens/full_name_form_screen.dart +++ b/lib/ui/views/onboarding/screens/full_name_form_screen.dart @@ -90,18 +90,20 @@ class FullNameFormScreen extends ViewModelWidget { Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar( showBackButton: false, showLanguageSelection: true, - language: viewModel.selectedLanguage['code'], 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()} 😊', - style:style25DG600, + style: style25DG600, ); - Widget _buildSubtitle() => Text( + Widget _buildSubtitle() => Text( LocaleKeys.name_for_personalization.tr(), - style:style14MG400, + style: style14MG400, ); Widget _buildFullNameFormField(OnboardingViewModel viewModel) => diff --git a/lib/ui/views/onboarding/screens/gender_form_screen.dart b/lib/ui/views/onboarding/screens/gender_form_screen.dart index 36e3773..3aaafb8 100644 --- a/lib/ui/views/onboarding/screens/gender_form_screen.dart +++ b/lib/ui/views/onboarding/screens/gender_form_screen.dart @@ -82,8 +82,10 @@ class GenderFormScreen extends ViewModelWidget { showBackButton: true, showLanguageSelection: true, onPop: () => _pop(viewModel), - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildTitle() => Text( @@ -98,14 +100,22 @@ class GenderFormScreen extends ViewModelWidget { Widget _buildAgeGroups(OnboardingViewModel viewModel) => ListView.builder( shrinkWrap: true, - itemCount: viewModel.selectedLanguage['code'] == 'አማ' + physics: const NeverScrollableScrollPhysics(), + itemCount: viewModel.selectedLanguage['code'] == 'am' ? viewModel.gendersAm.length : viewModel.gendersEn.length, - physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildAgeGroup( - title:viewModel.selectedLanguage['code'] == 'አማ' ? viewModel.gendersAm[index]:viewModel.gendersEn[index], - selected: viewModel.isSelectedGender(viewModel.selectedLanguage['code'] == 'አማ' ? viewModel.gendersAm[index]: viewModel.gendersEn[index]), - onTap: () => viewModel.setSelectedGender(viewModel.selectedLanguage['code'] == 'አማ' ? viewModel.gendersAm[index]: viewModel.gendersEn[index]), + selected: viewModel.isSelectedGender( + viewModel.selectedLanguage['code'] == 'am' + ? 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], ), ); diff --git a/lib/ui/views/onboarding/screens/language_goal_form_screen.dart b/lib/ui/views/onboarding/screens/language_goal_form_screen.dart index e082097..59b120e 100644 --- a/lib/ui/views/onboarding/screens/language_goal_form_screen.dart +++ b/lib/ui/views/onboarding/screens/language_goal_form_screen.dart @@ -1,6 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.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'; class LanguageGoalFormScreen extends ViewModelWidget { - final TextEditingController languageGoalController; - - const LanguageGoalFormScreen( - {super.key, required this.languageGoalController}); + const LanguageGoalFormScreen({super.key}); void _pop(OnboardingViewModel viewModel) { - languageGoalController.clear(); viewModel.resetLanguageGoalFormScreen(); viewModel.goBack(); } @@ -24,8 +22,7 @@ class LanguageGoalFormScreen extends ViewModelWidget { FocusManager.instance.primaryFocus?.unfocus(); Map data = { - 'language_goal': - viewModel.selectedLanguageGoal ?? languageGoalController.text, + 'language_goal': viewModel.selectedLanguageGoal?.code, }; viewModel.addUserData(data); @@ -87,15 +84,6 @@ class LanguageGoalFormScreen extends ViewModelWidget { _buildSubtitle(), verticalSpaceMedium, _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, ]; @@ -103,17 +91,19 @@ class LanguageGoalFormScreen extends ViewModelWidget { showBackButton: true, showLanguageSelection: true, onPop: () => _pop(viewModel), - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildTitle() => Text( - 'What’s your main goal for improving your English?', + LocaleKeys.language_goal.tr(), style: style25DG600, ); Widget _buildSubtitle() => Text( - 'Your goal helps us tailor your learning journey.', + LocaleKeys.your_goal.tr(), style: style14MG400, ); @@ -123,7 +113,7 @@ class LanguageGoalFormScreen extends ViewModelWidget { itemCount: viewModel.languageGoals.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildLanguageGoal( - title: viewModel.languageGoals[index], + title: viewModel.languageGoals[index].label ?? '', selected: viewModel.isSelectedLanguageGoal(viewModel.languageGoals[index]), onTap: () => @@ -141,26 +131,6 @@ class LanguageGoalFormScreen extends ViewModelWidget { 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( padding: const EdgeInsets.only(bottom: 50), child: _buildContinueButton(viewModel), @@ -169,21 +139,13 @@ class LanguageGoalFormScreen extends ViewModelWidget { Widget _buildContinueButton(OnboardingViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Continue', borderRadius: 12, foregroundColor: kcWhite, + text: LocaleKeys.cont.tr(), onTap: viewModel.selectedLanguageGoal != null - ? viewModel.selectedLanguageGoal?.toLowerCase() == 'other' - ? languageGoalController.text.isNotEmpty - ? () => _next(viewModel) - : null - : () => _next(viewModel) + ? () => _next(viewModel) : null, backgroundColor: viewModel.selectedLanguageGoal != null - ? viewModel.selectedLanguageGoal?.toLowerCase() == 'other' - ? languageGoalController.text.isNotEmpty - ? kcPrimaryColor - : kcPrimaryColor.withOpacity(0.1) - : kcPrimaryColor + ? kcPrimaryColor : kcPrimaryColor.withOpacity(0.1)); } diff --git a/lib/ui/views/onboarding/screens/learning_goal_form_screen.dart b/lib/ui/views/onboarding/screens/learning_goal_form_screen.dart index 109535b..0f5900c 100644 --- a/lib/ui/views/onboarding/screens/learning_goal_form_screen.dart +++ b/lib/ui/views/onboarding/screens/learning_goal_form_screen.dart @@ -1,7 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:iconsax/iconsax.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; @@ -32,7 +34,7 @@ class LearningGoalFormScreen extends ViewModelWidget { FocusManager.instance.primaryFocus?.unfocus(); Map data = { - 'learning_goal': viewModel.selectedLearningGoal, + 'learning_goal': viewModel.selectedLearningGoal?.code, }; viewModel.addUserData(data); @@ -98,17 +100,20 @@ class LearningGoalFormScreen extends ViewModelWidget { showBackButton: true, showLanguageSelection: true, onPop: () => _pop(viewModel), - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildTitle(OnboardingViewModel viewModel) => Text.rich( TextSpan( - text: 'Hi ${viewModel.userData['first_name']},', + text: + '${LocaleKeys.hello.tr()} ${viewModel.userData['first_name']},', style: style18P600.copyWith(fontSize: 22), children: [ TextSpan( - text: ' Choose your learning goal.', + text: ' ${LocaleKeys.learning_goal.tr()}', style: style16DG600.copyWith(fontSize: 22), ) ]), @@ -119,27 +124,21 @@ class LearningGoalFormScreen extends ViewModelWidget { itemCount: viewModel.learningGoals.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildLearningGoal( - title: viewModel.learningGoals[index]['title'], - icon: getIcon(viewModel.learningGoals[index]['icon']), - subtitle: viewModel.learningGoals[index]['subtitle'], - selected: viewModel - .isSelectedLearningGoal(viewModel.learningGoals[index]['title']), - onTap: () => viewModel - .setSelectedLearningGoal(viewModel.learningGoals[index]['title']), + title: viewModel.learningGoals[index].label ?? '', + selected: + viewModel.isSelectedLearningGoal(viewModel.learningGoals[index]), + onTap: () => + viewModel.setSelectedLearningGoal(viewModel.learningGoals[index]), ), ); Widget _buildLearningGoal( {required String title, required bool selected, - required IconData icon, - required String subtitle, required GestureTapCallback onTap}) => CustomLargeRadioButton( - icon: icon, title: title, onTap: onTap, - subtitle: subtitle, selected: selected, ); @@ -151,9 +150,9 @@ class LearningGoalFormScreen extends ViewModelWidget { Widget _buildContinueButton(OnboardingViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Continue', borderRadius: 12, foregroundColor: kcWhite, + text: LocaleKeys.cont.tr(), onTap: viewModel.selectedLearningGoal != null ? () => _next(viewModel) : null, diff --git a/lib/ui/views/onboarding/screens/occupation_form_screen.dart b/lib/ui/views/onboarding/screens/occupation_form_screen.dart index de1797b..731d8cc 100644 --- a/lib/ui/views/onboarding/screens/occupation_form_screen.dart +++ b/lib/ui/views/onboarding/screens/occupation_form_screen.dart @@ -1,6 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; @@ -19,7 +21,9 @@ class OccupationFormScreen extends ViewModelWidget { Future _next(OnboardingViewModel viewModel) async { FocusManager.instance.primaryFocus?.unfocus(); - Map data = {'occupation': viewModel.selectedOccupation}; + Map data = { + 'occupation': viewModel.selectedOccupation?.code + }; viewModel.addUserData(data); viewModel.next(); @@ -86,28 +90,30 @@ class OccupationFormScreen extends ViewModelWidget { showBackButton: true, showLanguageSelection: true, onPop: () => _pop(viewModel), - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildTitle() => Text( - 'What’s your occupation?', + LocaleKeys.your_occupation.tr(), style: style25DG600, ); Widget _buildSubtitle() => Text( - 'We’ll personalize your learning experience based on your occupation.', + LocaleKeys.occupation_for_personalization.tr(), style: style14MG400, ); Widget _buildOccupationDropdown(OnboardingViewModel viewModel) => CustomDropdownPicker( - hint: 'Select occupation', icon: _buildSearchIcon(), + hint: LocaleKeys.select_occupation.tr(), selectedItem: viewModel.selectedOccupation, - items: (value, props) => viewModel.getOccupations(), - onChanged: (value) => viewModel.setSelectedOccupation( - value ?? 'Students (High school & University)')); + items: (value, props) => viewModel.occupations, + onChanged: (value) => viewModel.setSelectedOccupation(value)); + Icon _buildSearchIcon() => const Icon( Icons.search, color: kcPrimaryColor, @@ -120,10 +126,15 @@ class OccupationFormScreen extends ViewModelWidget { Widget _buildContinueButton(OnboardingViewModel viewModel) => CustomElevatedButton( - height: 55, - text: 'Continue', - borderRadius: 12, - foregroundColor: kcWhite, - onTap: () => _next(viewModel), - backgroundColor: kcPrimaryColor); + height: 55, + borderRadius: 12, + foregroundColor: kcWhite, + text: LocaleKeys.cont.tr(), + onTap: viewModel.selectedOccupation != null + ? () => _next(viewModel) + : null, + backgroundColor: viewModel.selectedOccupation != null + ? kcPrimaryColor + : kcPrimaryColor.withOpacity(0.1), + ); } diff --git a/lib/ui/views/onboarding/screens/topic_form_screen.dart b/lib/ui/views/onboarding/screens/topic_form_screen.dart index 21817b6..8108015 100644 --- a/lib/ui/views/onboarding/screens/topic_form_screen.dart +++ b/lib/ui/views/onboarding/screens/topic_form_screen.dart @@ -1,7 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.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'; class TopicFormScreen extends ViewModelWidget { - final TextEditingController topicController; - - const TopicFormScreen({super.key, required this.topicController}); + const TopicFormScreen({super.key}); void _pop(OnboardingViewModel viewModel) { - topicController.clear(); viewModel.resetTopicFormScreen(); viewModel.goBack(); } @@ -26,8 +24,8 @@ class TopicFormScreen extends ViewModelWidget { Map data = { 'profile_completed': true, 'preferred_language': 'en', + 'favoutite_topic': viewModel.selectedTopic?.code, 'birth_day': DateFormat('yyyy-MM-dd').format(DateTime.now()), - 'favoutite_topic': viewModel.selectedTopic ?? topicController.text, }; viewModel.addUserData(data); @@ -58,8 +56,10 @@ class TopicFormScreen extends ViewModelWidget { showBackButton: true, showLanguageSelection: true, onPop: () => _pop(viewModel), - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildExpandedBody(OnboardingViewModel viewModel) => @@ -97,25 +97,16 @@ class TopicFormScreen extends ViewModelWidget { _buildSubtitle(), verticalSpaceMedium, _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, ]; Widget _buildTitle() => Text( - 'Which topics interest you most?', + LocaleKeys.topic_interest.tr(), style: style25DG600, ); Widget _buildSubtitle() => Text( - 'Your favorite topics help us create fun, relatable lessons.', + LocaleKeys.favourite_topic.tr(), style: style14MG400, ); @@ -125,7 +116,7 @@ class TopicFormScreen extends ViewModelWidget { itemCount: viewModel.topics.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildTopic( - title: viewModel.topics[index], + title: viewModel.topics[index].label ?? '', selected: viewModel.isSelectedTopic(viewModel.topics[index]), onTap: () => viewModel.setSelectedTopic(viewModel.topics[index]), ), @@ -141,26 +132,6 @@ class TopicFormScreen extends ViewModelWidget { 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( padding: const EdgeInsets.only(bottom: 50), child: _buildContinueButton(viewModel), @@ -169,21 +140,13 @@ class TopicFormScreen extends ViewModelWidget { Widget _buildContinueButton(OnboardingViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Continue', borderRadius: 12, foregroundColor: kcWhite, + text: LocaleKeys.cont.tr(), onTap: viewModel.selectedTopic != null - ? viewModel.selectedTopic?.toLowerCase() == 'other' - ? topicController.text.isNotEmpty - ? () async => await _next(viewModel) - : null - : () async => await _next(viewModel) + ? () async => await _next(viewModel) : null, backgroundColor: viewModel.selectedTopic != null - ? viewModel.selectedTopic?.toLowerCase() == 'other' - ? topicController.text.isNotEmpty - ? kcPrimaryColor - : kcPrimaryColor.withOpacity(0.1) - : kcPrimaryColor + ? kcPrimaryColor : kcPrimaryColor.withOpacity(0.1)); } diff --git a/lib/ui/views/profile/profile_view.dart b/lib/ui/views/profile/profile_view.dart index e854f6c..4e28737 100644 --- a/lib/ui/views/profile/profile_view.dart +++ b/lib/ui/views/profile/profile_view.dart @@ -158,7 +158,7 @@ class ProfileView extends StackedView { List _buildSettingsChildren(ProfileViewModel viewModel) => [ // _buildDownloadsCard(viewModel), - // _buildProgressCard(viewModel), + // _buildProgressCard(viewModel), _buildAccountCard(viewModel), _buildSupportCard(viewModel) ]; diff --git a/lib/ui/views/profile_detail/profile_detail_view.dart b/lib/ui/views/profile_detail/profile_detail_view.dart index dec02e0..396251e 100644 --- a/lib/ui/views/profile_detail/profile_detail_view.dart +++ b/lib/ui/views/profile_detail/profile_detail_view.dart @@ -11,7 +11,6 @@ import '../../common/app_colors.dart'; import '../../common/enmus.dart'; import '../../common/ui_helpers.dart'; import '../../common/validators/form_validator.dart'; -import '../../widgets/custom_dropdown.dart'; import '../../widgets/custom_elevated_button.dart'; import '../../widgets/image_picker_option.dart'; import '../../widgets/page_loading_indicator.dart'; @@ -202,7 +201,7 @@ class ProfileDetailView extends StackedView verticalSpaceMedium, _buildCountryDropdownLabel(), verticalSpaceSmall, - _buildCountryDropdown(viewModel), + // _buildCountryDropdown(viewModel), verticalSpaceMedium, _buildRegionFormFieldWrapper(viewModel), verticalSpaceMedium, @@ -527,13 +526,13 @@ class ProfileDetailView extends StackedView label: LocaleKeys.country.tr(), ); - Widget _buildCountryDropdown(ProfileDetailViewModel viewModel) => - CustomDropdownPicker( - hint: 'Select country', - selectedItem: viewModel.selectedCountry, - items: (value, props) => viewModel.getCountries(), - onChanged: (value) => viewModel.setSelectedCountry(value ?? 'Ethiopia'), - ); + // Widget _buildCountryDropdown(ProfileDetailViewModel viewModel) => + // CustomDropdownPicker( + // hint: 'Select country', + // selectedItem: viewModel.selectedCountry, + // items: (value, props) => viewModel.getCountries(), + // onChanged: (value) => viewModel.setSelectedCountry(value ?? 'Ethiopia'), + // ); Widget _buildRegionFormFieldWrapper(ProfileDetailViewModel viewModel) => Column( @@ -565,18 +564,19 @@ class ProfileDetailView extends StackedView ); Widget _buildRegionFormState(ProfileDetailViewModel viewModel) => - viewModel.dropdownRegion - ? _buildRegionDropDown(viewModel) - : _buildRegionFormField(viewModel); - - Widget _buildRegionDropDown(ProfileDetailViewModel viewModel) => - CustomDropdownPicker( - icon: _buildSearchIcon(), - hint:LocaleKeys.select_region.tr(), - selectedItem: viewModel.selectedRegion, - items: (value, props) => viewModel.getRegions(), - onChanged: (value) => - viewModel.setSelectedRegion(value ?? 'Addis Ababa')); + // viewModel.dropdownRegion + // ? _buildRegionDropDown(viewModel) + // : + _buildRegionFormField(viewModel); + // + // Widget _buildRegionDropDown(ProfileDetailViewModel viewModel) => + // CustomDropdownPicker( + // icon: _buildSearchIcon(), + // hint:LocaleKeys.select_region.tr(), + // selectedItem: viewModel.selectedRegion, + // items: (value, props) => viewModel.getRegions(), + // onChanged: (value) => + // viewModel.setSelectedRegion(value ?? 'Addis Ababa')); Widget _buildRegionFormField(ProfileDetailViewModel viewModel) => TextFormField( @@ -584,7 +584,7 @@ class ProfileDetailView extends StackedView onTap: viewModel.setRegionFocus, decoration: inputDecoration( focus: viewModel.focusRegion, - hint:LocaleKeys.enter_your_city.tr(), + hint: LocaleKeys.enter_your_city.tr(), filled: regionController.text.isNotEmpty), ); @@ -611,7 +611,7 @@ class ProfileDetailView extends StackedView [ _buildOccupationDropdownLabel(), verticalSpaceSmall, - _buildOccupationDropdown(viewModel) + // _buildOccupationDropdown(viewModel) ]; Widget _buildOccupationDropdownLabel() => CustomFormLabel( @@ -619,14 +619,14 @@ class ProfileDetailView extends StackedView label: LocaleKeys.occupation.tr(), ); - Widget _buildOccupationDropdown(ProfileDetailViewModel viewModel) => - CustomDropdownPicker( - icon: _buildSearchIcon(), - hint:LocaleKeys.select_occupation.tr(), - selectedItem: viewModel.selectedOccupation, - items: (value, props) => viewModel.getOccupations(), - onChanged: (value) => viewModel.setSelectedOccupation( - value ?? 'Students (High school & University)')); + // Widget _buildOccupationDropdown(ProfileDetailViewModel viewModel) => + // CustomDropdownPicker( + // icon: _buildSearchIcon(), + // hint:LocaleKeys.select_occupation.tr(), + // selectedItem: viewModel.selectedOccupation, + // items: (value, props) => viewModel.getOccupations(), + // onChanged: (value) => viewModel.setSelectedOccupation( + // value ?? 'Students (High school & University)')); Icon _buildSearchIcon() => const Icon( Icons.search, color: kcPrimaryColor, @@ -663,7 +663,7 @@ class ProfileDetailView extends StackedView borderRadius: 12, onTap: viewModel.pop, backgroundColor: kcWhite, - text:LocaleKeys.cancel.tr(), + text: LocaleKeys.cancel.tr(), borderColor: kcPrimaryColor, foregroundColor: kcPrimaryColor, ); diff --git a/lib/ui/views/register/screens/create_password_screen.dart b/lib/ui/views/register/screens/create_password_screen.dart index e8350c7..7342909 100644 --- a/lib/ui/views/register/screens/create_password_screen.dart +++ b/lib/ui/views/register/screens/create_password_screen.dart @@ -75,8 +75,10 @@ class CreatePasswordScreen extends ViewModelWidget { showBackButton: true, onPop: viewModel.goBack, showLanguageSelection: true, - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildExpandedBody(RegisterViewModel viewModel) => diff --git a/lib/ui/views/register/screens/register_with_email_screen.dart b/lib/ui/views/register/screens/register_with_email_screen.dart index 3630c4f..32b2285 100644 --- a/lib/ui/views/register/screens/register_with_email_screen.dart +++ b/lib/ui/views/register/screens/register_with_email_screen.dart @@ -91,8 +91,10 @@ class RegisterWithEmailScreen extends ViewModelWidget { showBackButton: true, onPop: viewModel.goBack, showLanguageSelection: true, - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildExpandedBody( diff --git a/lib/ui/views/register/screens/register_with_phone_number_screen.dart b/lib/ui/views/register/screens/register_with_phone_number_screen.dart index e90398b..c0c5c89 100644 --- a/lib/ui/views/register/screens/register_with_phone_number_screen.dart +++ b/lib/ui/views/register/screens/register_with_phone_number_screen.dart @@ -94,8 +94,10 @@ class RegisterWithPhoneNumberScreen extends ViewModelWidget { showBackButton: true, onPop: viewModel.goBack, showLanguageSelection: true, - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildExpandedBody( diff --git a/lib/ui/views/register/screens/registration_otp_screen.dart b/lib/ui/views/register/screens/registration_otp_screen.dart index 2a6f35a..e871b75 100644 --- a/lib/ui/views/register/screens/registration_otp_screen.dart +++ b/lib/ui/views/register/screens/registration_otp_screen.dart @@ -105,8 +105,10 @@ class RegistrationOtpScreen extends ViewModelWidget { showBackButton: true, onPop: viewModel.goBack, showLanguageSelection: true, - language: viewModel.selectedLanguage['code'], onLanguage: () async => await viewModel.navigateToLanguage(), + language: viewModel.selectedLanguage['code'] == 'am' + ? 'አማ' + : viewModel.selectedLanguage['code'], ); Widget _buildExpandedBody( diff --git a/lib/ui/views/startup/startup_viewmodel.dart b/lib/ui/views/startup/startup_viewmodel.dart index 8cbfeaf..2f3172e 100644 --- a/lib/ui/views/startup/startup_viewmodel.dart +++ b/lib/ui/views/startup/startup_viewmodel.dart @@ -1,6 +1,7 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.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.router.dart'; @@ -14,15 +15,22 @@ import '../../common/enmus.dart'; class StartupViewModel extends ReactiveViewModel { // Dependency injection final _apiService = locator(); + final _statusChecker = locator(); + final _navigationService = locator(); + + final _onboardingService = locator(); + final _localizationService = locator(); + final _authenticationService = locator(); + final _imageDownloaderService = locator(); @override List get listenableServices => - [_authenticationService]; + [_onboardingService, _authenticationService]; // Current user User? get _user => _authenticationService.user; @@ -81,7 +89,7 @@ class StartupViewModel extends ReactiveViewModel { response = {'data': true, 'status': ResponseStatus.success}; } if (response['status'] == ResponseStatus.success && !response['data']) { - await replaceWithOnboarding(); + await etOnboardingFields(); } else if (response['status'] == ResponseStatus.success && response['data']) { await saveProfileStatus(response['data']); @@ -122,4 +130,16 @@ class StartupViewModel extends ReactiveViewModel { } } } + + // Remote api call + + // Onboarding fields + Future etOnboardingFields() async { + bool response = await _onboardingService.getOnboardingFields(); + if (response) { + await replaceWithOnboarding(); + } else { + await replaceWithFailure(); + } + } } diff --git a/lib/ui/views/support/support_view.dart b/lib/ui/views/support/support_view.dart index 24674a4..2b6f59b 100644 --- a/lib/ui/views/support/support_view.dart +++ b/lib/ui/views/support/support_view.dart @@ -55,8 +55,8 @@ class SupportView extends StackedView { Widget _buildAppbar(SupportViewModel viewModel) => SmallAppBar( showBackButton: true, onPop: viewModel.pop, - title:LocaleKeys.need_help.tr(), - ); + title: LocaleKeys.need_help.tr(), + ); Widget _buildContentWrapper(SupportViewModel viewModel) => Expanded(child: _buildContentColumnWrapper(viewModel)); @@ -87,7 +87,7 @@ class SupportView extends StackedView { Widget _buildCallSupport(SupportViewModel viewModel) => SupportCard( icon: Icons.call, color: kcPrimaryColor, - title:LocaleKeys.call_support.tr(), + title: LocaleKeys.call_support.tr(), subtitle: LocaleKeys.talk_with_support.tr(), onTap: () async => await viewModel.navigateToCallSupport(), ); diff --git a/lib/ui/views/telegram_support/telegram_support_view.dart b/lib/ui/views/telegram_support/telegram_support_view.dart index 6f02d47..48dad38 100644 --- a/lib/ui/views/telegram_support/telegram_support_view.dart +++ b/lib/ui/views/telegram_support/telegram_support_view.dart @@ -135,11 +135,14 @@ class TelegramSupportView extends StackedView { Widget _buildOptionTextDivider() => const OptionTextDivider(); Widget _buildSearchText() => Text.rich( - TextSpan(text: LocaleKeys.search_for.tr(), style: style14DG500, children: [ - TextSpan( - style: style14P600, - text: ' $kTelegramSupport', - ) - ]), + TextSpan( + text: LocaleKeys.search_for.tr(), + style: style14DG500, + children: [ + TextSpan( + style: style14P600, + text: ' $kTelegramSupport', + ) + ]), ); } diff --git a/lib/ui/widgets/cancel_learn_practice_sheet.dart b/lib/ui/widgets/cancel_learn_practice_sheet.dart index db96c39..9dacb96 100644 --- a/lib/ui/widgets/cancel_learn_practice_sheet.dart +++ b/lib/ui/widgets/cancel_learn_practice_sheet.dart @@ -58,10 +58,10 @@ class CancelLearnPracticeSheet extends StatelessWidget { style: style18DG700, children: [ TextSpan( - text: ' $user', - style: style18P600, - ) - ]), + text: ' $user', + style: style18P600, + ) + ]), ); Widget _buildSubtitle() => Text( diff --git a/lib/ui/widgets/custom_dropdown.dart b/lib/ui/widgets/custom_dropdown.dart index 79339fe..ec80b69 100644 --- a/lib/ui/widgets/custom_dropdown.dart +++ b/lib/ui/widgets/custom_dropdown.dart @@ -5,12 +5,15 @@ import 'package:flutter/material.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import '../../models/field_option.dart'; + class CustomDropdownPicker extends StatelessWidget { final Icon? icon; final String hint; - final String selectedItem; - final void Function(String?)? onChanged; - final FutureOr> Function(String value, LoadProps? props)? items; + final FieldOption? selectedItem; + final void Function(FieldOption?)? onChanged; + final FutureOr> Function(String value, LoadProps? props)? + items; const CustomDropdownPicker( {super.key, @@ -28,16 +31,18 @@ class CustomDropdownPicker extends StatelessWidget { child: _buildDropDownSearch(), ); - Widget _buildDropDownSearch() => DropdownSearch( + Widget _buildDropDownSearch() => DropdownSearch( + onChanged: onChanged, popupProps: _popupProps(), selectedItem: selectedItem, - onChanged: (value) => onChanged!(value), + itemAsString: (item) => item.label ?? '', decoratorProps: _dropDownDecoratorProps(), + compareFn: (item1, item2) => item1.label == item2.label, + items: (value, properties) => items!(value, properties), dropdownBuilder: (context, value) => _buildDropdownBuilder(value), - items: (value, properties) async => await items!(value, properties), ); - PopupProps _popupProps() => PopupProps.menu( + PopupProps _popupProps() => PopupProps.menu( showSearchBox: true, showSelectedItems: true, searchFieldProps: _searchFieldProps(), @@ -57,25 +62,25 @@ class CustomDropdownPicker extends StatelessWidget { InputDecoration _popUpDecoration() => InputDecoration( filled: true, hintStyle: style14DG400, - fillColor: kcTransparent, errorBorder: searchBorder, focusedBorder: searchBorder, enabledBorder: searchBorder, disabledBorder: searchBorder, focusedErrorBorder: searchBorder, + fillColor: const Color(0xfff5e9f4), contentPadding: const EdgeInsets.only(top: 12), prefixIcon: icon != null ? _buildPrefixIcon() : null, ); - Widget _buildPopupProsBuilderWrapper(String value) => Padding( + Widget _buildPopupProsBuilderWrapper(FieldOption value) => Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), child: _buildPopupProsBuilder(value), ); - Widget _buildPopupProsBuilder(String value) => Text( - value, + Widget _buildPopupProsBuilder(FieldOption value) => Text( + value.label ?? '', maxLines: 1, - style: const TextStyle(color: kcDarkGrey, fontSize: 14), + style: style14DG400, ); DropDownDecoratorProps _dropDownDecoratorProps() => DropDownDecoratorProps( @@ -91,7 +96,7 @@ class CustomDropdownPicker extends StatelessWidget { focusedBorder: border, enabledBorder: border, disabledBorder: border, - hintStyle: style14LG400, + hintStyle: style14MG400, fillColor: kcPrimaryColor.withOpacity(0.1), contentPadding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), @@ -102,8 +107,8 @@ class CustomDropdownPicker extends StatelessWidget { child: icon, ); - Widget _buildDropdownBuilder(String? value) => Text( - value ?? hint, + Widget _buildDropdownBuilder(FieldOption? value) => Text( + value?.label ?? '', maxLines: 1, style: style14DG400, ); diff --git a/lib/ui/widgets/custom_large_radio_button.dart b/lib/ui/widgets/custom_large_radio_button.dart index f22d9f3..bb1739d 100644 --- a/lib/ui/widgets/custom_large_radio_button.dart +++ b/lib/ui/widgets/custom_large_radio_button.dart @@ -5,23 +5,20 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart'; class CustomLargeRadioButton extends StatelessWidget { final String title; final bool selected; - final IconData icon; - final String subtitle; final GestureTapCallback? onTap; - const CustomLargeRadioButton( - {super.key, - this.onTap, - required this.title, - required this.icon, - required this.selected, - required this.subtitle}); + const CustomLargeRadioButton({ + super.key, + this.onTap, + required this.title, + required this.selected, + }); @override Widget build(BuildContext context) => _buildButtonWrapper(); Widget _buildButtonWrapper() => Container( - height: 125, + height: 75, width: double.maxFinite, margin: const EdgeInsets.only(bottom: 15), child: _buildContainerWrapper(), @@ -41,39 +38,24 @@ class CustomLargeRadioButton extends StatelessWidget { color: selected ? kcPrimaryColor : kcPrimaryColor.withOpacity(0.75), ), ), - child: _buildButtonColumnWrapper(), + child: _buildButtonRowWrapper(), ); - Widget _buildButtonColumnWrapper() => Column( - crossAxisAlignment: CrossAxisAlignment.start, + Widget _buildButtonRowWrapper() => Row( children: _buildButtonRowChildren(), ); List _buildButtonRowChildren() => - [_buildIconSectionWrapper(), _buildTitle(), _buildSubtitle()]; + [_buildTitleWrapper(), _buildSelectedCheckBox()]; - Widget _buildIconSectionWrapper() => Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildIconSectionChildren(), - ); - - List _buildIconSectionChildren() => - [_buildLeadingIcon(), _buildSelectedCheckBox()]; - - Widget _buildLeadingIcon() => Icon( - icon, - size: 25, - color: kcPrimaryColor, + Widget _buildTitleWrapper() => Expanded( + child: _buildTitle(), ); Widget _buildTitle() => Text( title, - style: style18DG700, - ); - - Widget _buildSubtitle() => Text( - subtitle, - style: const TextStyle(color: kcMediumGrey), + maxLines: 1, + style: style16DG400, ); Widget _buildSelectedCheckBox() => Checkbox( diff --git a/lib/ui/widgets/learn_course_tile.dart b/lib/ui/widgets/learn_course_tile.dart index a1c643d..7689c77 100644 --- a/lib/ui/widgets/learn_course_tile.dart +++ b/lib/ui/widgets/learn_course_tile.dart @@ -13,18 +13,26 @@ import 'custom_elevated_button.dart'; class LearnCourseTile extends ViewModelWidget { final LearnCourse course; final GestureTapCallback? onViewTap; + final GestureTapCallback? onLockTap; final GestureTapCallback? onPracticeTap; const LearnCourseTile({ super.key, this.onViewTap, + this.onLockTap, this.onPracticeTap, required this.course, }); @override 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( margin: const EdgeInsets.only(bottom: 15), @@ -109,7 +117,7 @@ class LearnCourseTile extends ViewModelWidget { ? _buildProgressStatus() : Container(); - Widget _buildProgressStatus() => ProgressStatus( + Widget _buildProgressStatus() => ProgressStatus( color: kcPrimaryColor, status: LocaleKeys.current_level.tr(), ); @@ -147,7 +155,7 @@ class LearnCourseTile extends ViewModelWidget { onTap: onViewTap, foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, - text:LocaleKeys.view_course.tr(), + text: LocaleKeys.view_course.tr(), ); Widget _buildPracticeButtonWrapper(LearnCourseViewModel viewModel) => @@ -163,6 +171,6 @@ class LearnCourseTile extends ViewModelWidget { backgroundColor: kcWhite, borderColor: kcPrimaryColor, foregroundColor: kcPrimaryColor, - text:LocaleKeys.take_practice.tr() , + text: LocaleKeys.take_practice.tr(), ); } diff --git a/lib/ui/widgets/learn_lesson_tile.dart b/lib/ui/widgets/learn_lesson_tile.dart index 1467f64..8c74cc4 100644 --- a/lib/ui/widgets/learn_lesson_tile.dart +++ b/lib/ui/widgets/learn_lesson_tile.dart @@ -130,7 +130,9 @@ class LearnLessonTile extends ViewModelWidget { ); 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, ); @@ -166,7 +168,7 @@ class LearnLessonTile extends ViewModelWidget { width: double.maxFinite, backgroundColor: kcWhite, borderColor: kcPrimaryColor, - text:LocaleKeys.practice.tr() , + text: LocaleKeys.practice.tr(), foregroundColor: kcPrimaryColor, ); @@ -182,7 +184,7 @@ class LearnLessonTile extends ViewModelWidget { onTap: onLessonTap, width: double.maxFinite, foregroundColor: kcWhite, - text:LocaleKeys.start.tr() , + text: LocaleKeys.start.tr(), trailingIcon: Icons.play_arrow, backgroundColor: kcPrimaryColor, ); diff --git a/lib/ui/widgets/learn_module_tile.dart b/lib/ui/widgets/learn_module_tile.dart index 0be786d..579fb2f 100644 --- a/lib/ui/widgets/learn_module_tile.dart +++ b/lib/ui/widgets/learn_module_tile.dart @@ -18,10 +18,7 @@ class LearnModuleTile extends ViewModelWidget { final GestureTapCallback? onPracticeTap; const LearnModuleTile( - {super.key, - this.onModuleTap, - this.onPracticeTap, - required this.module}); + {super.key, this.onModuleTap, this.onPracticeTap, required this.module}); Future _showSheet( {required BuildContext context, @@ -54,7 +51,7 @@ class LearnModuleTile extends ViewModelWidget { Stack( children: [ _buildExpansionTile(context: context, viewModel: viewModel), - _buildContainerShaderState() + // _buildContainerShaderState() ], ); @@ -73,14 +70,14 @@ class LearnModuleTile extends ViewModelWidget { shape: Border.all(color: kcTransparent), expandedAlignment: Alignment.centerLeft, collapsedBackgroundColor: kcBackgroundColor, - enabled: (module.access?.isAccessible ?? false), + //enabled: (module.access?.isAccessible ?? false), controlAffinity: ListTileControlAffinity.trailing, expandedCrossAxisAlignment: CrossAxisAlignment.start, tilePadding: const EdgeInsets.symmetric(horizontal: 15), childrenPadding: const EdgeInsets.fromLTRB(70, 0, 15, 15), - initiallyExpanded: (module.access?.isAccessible ?? false), - showTrailingIcon: - !(module.access?.isAccessible ?? false) ? true : false, + //initiallyExpanded: (module.access?.isAccessible ?? false), + // showTrailingIcon: + // !(module.access?.isAccessible ?? false) ? true : false, children: _buildExpansionTileChildren(context: context, viewModel: viewModel), ); @@ -211,9 +208,7 @@ class LearnModuleTile extends ViewModelWidget { backgroundColor: kcWhite, borderColor: kcPrimaryColor, foregroundColor: kcPrimaryColor, - text:LocaleKeys.take_practice.tr(), - - // onTap: () async => await viewModel.navigateToLearnPractice(practices), + text: LocaleKeys.take_practice.tr(), ); Widget _buildSheet(LearnModuleViewModel viewModel) => FinishPracticeSheet( diff --git a/lib/ui/widgets/learn_practice_result_card.dart b/lib/ui/widgets/learn_practice_result_card.dart index 6849cb0..f2e026b 100644 --- a/lib/ui/widgets/learn_practice_result_card.dart +++ b/lib/ui/widgets/learn_practice_result_card.dart @@ -53,7 +53,7 @@ class LearnPracticeResultCard extends ViewModelWidget { Widget _buildSampleResponse() => LearnPracticeAnswerCard( answer: answer, voice: Voice.sample, - title:LocaleKeys.sample_answer.tr() , + title: LocaleKeys.sample_answer.tr(), ); Widget _buildActualResponseWrapper() => @@ -62,7 +62,6 @@ class LearnPracticeResultCard extends ViewModelWidget { Widget _buildActualResponse() => LearnPracticeAnswerCard( answer: answer, voice: Voice.recorded, - title: LocaleKeys.your_answer.tr(), - - ); + title: LocaleKeys.your_answer.tr(), + ); } diff --git a/lib/ui/widgets/learn_practice_results_wrapper.dart b/lib/ui/widgets/learn_practice_results_wrapper.dart index c9c639c..682fe41 100644 --- a/lib/ui/widgets/learn_practice_results_wrapper.dart +++ b/lib/ui/widgets/learn_practice_results_wrapper.dart @@ -35,7 +35,7 @@ class LearnPracticeResultsWrapper [_buildTitle(), verticalSpaceSmall, _buildResults(viewModel)]; Widget _buildTitle() => Text( - LocaleKeys.conversation_review.tr(), + LocaleKeys.conversation_review.tr(), style: style16DG600, textAlign: TextAlign.center, ); diff --git a/lib/ui/widgets/learn_practice_tip_section.dart b/lib/ui/widgets/learn_practice_tip_section.dart index bb866ea..4e2f8d3 100644 --- a/lib/ui/widgets/learn_practice_tip_section.dart +++ b/lib/ui/widgets/learn_practice_tip_section.dart @@ -41,7 +41,7 @@ class LearnPracticeTipSection extends ViewModelWidget { ); Widget _buildTitle() => Text( - LocaleKeys.quick_tip.tr(), + LocaleKeys.quick_tip.tr(), style: style16B600, ); diff --git a/lib/ui/widgets/learn_program_tile.dart b/lib/ui/widgets/learn_program_tile.dart index 712b9a9..11c2f0d 100644 --- a/lib/ui/widgets/learn_program_tile.dart +++ b/lib/ui/widgets/learn_program_tile.dart @@ -13,20 +13,12 @@ import 'custom_elevated_button.dart'; class LearnProgramTile extends ViewModelWidget { final LearnProgram program; final GestureTapCallback? onTap; - final GestureTapCallback? onLockTap; - const LearnProgramTile( - {super.key, this.onTap, this.onLockTap, required this.program}); + const LearnProgramTile({super.key, this.onTap, required this.program}); @override Widget build(BuildContext context, LearnProgramViewModel viewModel) => - _buildExpansionTileCardWrapper(viewModel); - - Widget _buildExpansionTileCardWrapper(LearnProgramViewModel viewModel) => - GestureDetector( - // onTap: !(program.access?.isAccessible ?? false) ? onLockTap : null, - child: _buildExpansionTileCard(viewModel), - ); + _buildExpansionTileCard(viewModel); Widget _buildExpansionTileCard(LearnProgramViewModel viewModel) => Container( margin: const EdgeInsets.only(bottom: 15), @@ -109,7 +101,7 @@ class LearnProgramTile extends ViewModelWidget { Widget _buildProgressStatus() => ProgressStatus( color: kcPrimaryColor, status: (program.access?.isCompleted ?? false) - ?LocaleKeys.completed.tr() + ? LocaleKeys.completed.tr() : LocaleKeys.in_progress.tr(), ); @@ -134,6 +126,6 @@ class LearnProgramTile extends ViewModelWidget { backgroundColor: kcPrimaryColor, text: program.access?.progressPercent == 0 ? LocaleKeys.start_learning.tr() - :LocaleKeys.continue_learning.tr() , + : LocaleKeys.continue_learning.tr(), ); } diff --git a/lib/ui/widgets/motivation_card.dart b/lib/ui/widgets/motivation_card.dart index 7a0d596..c0087b6 100644 --- a/lib/ui/widgets/motivation_card.dart +++ b/lib/ui/widgets/motivation_card.dart @@ -33,7 +33,7 @@ class MotivationCard extends StatelessWidget { Widget _buildText() => Expanded( child: Text( - LocaleKeys.keep_going.tr(), + LocaleKeys.keep_going.tr(), maxLines: 2, style: style14DG400, ), diff --git a/lib/ui/widgets/overall_progress.dart b/lib/ui/widgets/overall_progress.dart index f37c92a..e7a0059 100644 --- a/lib/ui/widgets/overall_progress.dart +++ b/lib/ui/widgets/overall_progress.dart @@ -50,7 +50,7 @@ class OverallProgress extends StatelessWidget { [_buildProgressInfo(), _buildProgress()]; Widget _buildProgressInfo() => Text( - LocaleKeys.overall_progress.tr(), + LocaleKeys.overall_progress.tr(), style: style16DG600, ); @@ -66,7 +66,7 @@ class OverallProgress extends StatelessWidget { ); Widget _buildSubtitle() => Text( - LocaleKeys.keep_up_the_great_work.tr(), + LocaleKeys.keep_up_the_great_work.tr(), style: style14DG500, ); } diff --git a/lib/ui/widgets/profile_app_bar.dart b/lib/ui/widgets/profile_app_bar.dart index 3ab77b2..1473d2e 100644 --- a/lib/ui/widgets/profile_app_bar.dart +++ b/lib/ui/widgets/profile_app_bar.dart @@ -74,16 +74,19 @@ class ProfileAppBar extends StatelessWidget { [_buildGreetingTitle(), _buildSubtitle()]; Widget _buildGreetingTitle() => Text.rich( - TextSpan(text: '${LocaleKeys.hello.tr()},', style: style14DG600, children: [ - TextSpan( - text: ' $name!', - style: style14P600, - ) - ]), + TextSpan( + text: '${LocaleKeys.hello.tr()},', + style: style14DG600, + children: [ + TextSpan( + text: ' $name!', + style: style14P600, + ) + ]), ); Widget _buildSubtitle() => Text( - LocaleKeys.ready_to_learn.tr(), + LocaleKeys.ready_to_learn.tr(), textAlign: TextAlign.center, style: style14DG400, ); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 2f01177..eb9631b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,6 @@ import FlutterMacOS import Foundation import audioplayers_darwin -import battery_plus import connectivity_plus import file_selector_macos import firebase_core @@ -25,7 +24,6 @@ import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) - BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) diff --git a/pubspec.lock b/pubspec.lock index 34535e2..1dc8795 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,22 +113,6 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -349,10 +333,10 @@ packages: dependency: transitive description: name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.12" dio: dependency: "direct main" description: @@ -421,10 +405,18 @@ packages: dependency: transitive description: name: ffi - sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" 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: dependency: transitive description: @@ -743,10 +735,10 @@ packages: dependency: transitive description: name: flutter_secure_storage_windows - sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" + sha256: "471951813a97006d899db4948acc654a4f28c440083ea08178935ce20b173ec1" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.2" flutter_spinkit: dependency: "direct main" description: @@ -1206,21 +1198,21 @@ packages: source: hosted version: "2.2.0" package_info_plus: - dependency: transitive + dependency: "direct main" description: name: package_info_plus - sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d + sha256: "4bf625947f6c7713ee242296a682e23e44823c09cf9d79e4f1238923c92db852" url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "10.1.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + sha256: db762cb2f4f25ee60fb6359773861b0f199e00b90d237bd85a76a1e806b46ef4 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "4.1.0" path: dependency: "direct main" description: @@ -1682,14 +1674,6 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -1770,14 +1754,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" - upower: - dependency: transitive - description: - name: upower - sha256: cf042403154751180affa1d15614db7fa50234bc2373cd21c3db666c38543ebf - url: "https://pub.dev" - source: hosted - version: "0.7.0" url_launcher: dependency: "direct main" description: @@ -1942,18 +1918,18 @@ packages: dependency: transitive description: name: wakelock_plus - sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" + sha256: "824c5bba0f800e86d32e57d3d1843c531f090005cc89d9a837933e6601093d53" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.6.1" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" + sha256: b13f99e992e7ae6a152e16c5559d3c07ff445b13330192662494e614ca3e7d7b url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.1" watcher: dependency: transitive description: @@ -2006,10 +1982,10 @@ packages: dependency: transitive description: name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738 url: "https://pub.dev" source: hosted - version: "5.15.0" + version: "6.3.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c706ca0..8526672 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: yimaru_app -version: 0.1.21+23 +version: 0.1.22+24 publish_to: 'none' description: A new Flutter project. @@ -22,8 +22,6 @@ dependencies: flutter_svg: ^2.2.3 stacked_shared: any image_picker: ^1.2.1 - battery_plus: ^7.0.0 - storage_info: ^1.0.0 flutter_html: ^3.0.0 email_validator: any audioplayers: ^6.6.0 @@ -43,6 +41,7 @@ dependencies: json_serializable: ^6.8.0 waveform_recorder: ^1.8.0 vimeo_video_player: ^1.0.3 + package_info_plus: ^10.1.0 permission_handler: ^12.0.1 firebase_messaging: ^16.1.1 cached_network_image: ^3.4.1 diff --git a/test/helpers/test_helpers.dart b/test/helpers/test_helpers.dart index e6484fa..cd907f9 100644 --- a/test/helpers/test_helpers.dart +++ b/test/helpers/test_helpers.dart @@ -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/learn_service.dart'; import 'package:yimaru_app/services/localization_service.dart'; +import 'package:yimaru_app/services/onboarding_service.dart'; // @stacked-import -import 'test_helpers.mocks.dart'; - @GenerateMocks( [], customMocks: [ @@ -54,10 +53,10 @@ import 'test_helpers.mocks.dart'; MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), - MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), + MockSpec(onMissingStub: OnMissingStub.returnDefault), // @stacked-mock-spec ], ) @@ -85,10 +84,10 @@ void registerServices() { getAndRegisterUrlLauncherService(); getAndRegisterUrlLauncherService(); getAndRegisterPhoneCallerService(); - getAndRegisterLearnLessonService(); getAndRegisterLearnService(); getAndRegisterLearnService(); getAndRegisterLocalizationService(); + getAndRegisterOnboardingService(); // @stacked-mock-register } @@ -285,6 +284,13 @@ MockLocalizationService getAndRegisterLocalizationService() { locator.registerSingleton(service); return service; } + +MockOnboardingService getAndRegisterOnboardingService() { + _removeRegistrationIfExists(); + final service = MockOnboardingService(); + locator.registerSingleton(service); + return service; +} // @stacked-mock-create void _removeRegistrationIfExists() { diff --git a/test/services/onboarding_service_test.dart b/test/services/onboarding_service_test.dart new file mode 100644 index 0000000..32c527a --- /dev/null +++ b/test/services/onboarding_service_test.dart @@ -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()); + }); +} diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 051255a..3335918 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,7 +7,6 @@ #include "generated_plugin_registrant.h" #include -#include #include #include #include @@ -20,8 +19,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { AudioplayersWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); - BatteryPlusWindowsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin")); ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); FileSelectorWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 749dce4..a04a45f 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,7 +4,6 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows - battery_plus connectivity_plus file_selector_windows firebase_core