Compare commits

...

3 Commits

Author SHA1 Message Date
a67a2ec7ea Merge tag '0.1.22' into develop
-fix: Apply fix on onboarding issue
2026-05-26 16:17:42 +03:00
f71220fa80 Merge branch 'release/0.1.22'
-fix: Apply fix on onboarding issue
2026-05-26 16:17:19 +03:00
f957d36e14 fix: Apply fix on onboarding issue 2026-05-26 16:16:12 +03:00
95 changed files with 1314 additions and 1186 deletions

View File

@ -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": "አጠናቅቅ"

View File

@ -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": "Well use your name to personalize your learning journey.",
"choose_your_gender": "Choose your gender?",
"gender_for_personalization": "Well personalize your learning experience based on your gender."
"gender_for_personalization": "Well personalize your learning experience based on your gender.",
"age_range": "Which age range are you in?",
"age_for_personalization": "Well personalize your learning experience based on your age.",
"educational_background": "Whats your current educational level?",
"education_for_personalization": "This helps us tailor your lessons to your experience.",
"your_occupation": "Whats your occupation?",
"occupation_for_personalization": "Well personalize your learning experience based on your occupation.",
"location": "Where are you from?",
"select_country_region": "Select your country and region from the dropdown",
"select_country": "Select country",
"learning_goal": "Choose your learning goal.",
"language_goal": "Whats your main goal for improving your English?",
"your_goal": "Your goal helps us tailor your learning journey.",
"write_your_goal": "Write your goal…",
"challenge_you_face": "What challenge do you face most with English?",
"evey_one_has_strugle": "Everyone has struggles, lets start fixing yours",
"write_your_challenge": "Write your challenge…",
"topic_interest": "Which topics interest you most?",
"favourite_topic": "Your favorite topics help us create fun, relatable lessons.",
"your_interest": "Write your interest…",
"want_quick_assessment": "Want a quick assessment to know your English level?",
"answer_quick_questions": "Answer a few quick questions to help us understand your English proficiency.",
"skip": "Skip",
"finish_level": "Finish Level",
"likely_speaker": "Youre likely speaker of",
"great_job": "Great Job! Heres your next step to keep improving.",
"lets_start_practice": "Let's start your practice",
"welcome_abroad": "Welcome aboard",
"ready_to_explore": "Youre ready to explore your personalized lessons.",
"finish": "Finish"
}

View File

@ -44,7 +44,6 @@ import 'package:yimaru_app/services/audio_player_service.dart';
import 'package:yimaru_app/services/voice_recorder_service.dart';
import 'package:yimaru_app/services/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: [

View File

@ -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<void> setupLocator(
locator.registerLazySingleton(() => PhoneCallerService());
locator.registerLazySingleton(() => LearnService());
locator.registerLazySingleton(() => LocalizationService());
locator.registerLazySingleton(() => OnboardingService());
}

View File

@ -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 = <String>{
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<LearnCourseViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i29.LearnCourseView(key: args.key, id: args.id),
settings: data,
);
},
_i30.AssessmentView: (data) {
_i29.AssessmentView: (data) {
final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
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<LearnSubscriptionViewArguments>(
orElse: () => const LearnSubscriptionViewArguments(),
);
return _i37.MaterialPageRoute<dynamic>(
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<ArifPayViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
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<CourseCatalogViewArguments>(
orElse: () => const CourseCatalogViewArguments(),
);
return _i37.MaterialPageRoute<dynamic>(
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<CourseUnitViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
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<LandingViewArguments>(
orElse: () => const LandingViewArguments(),
);
return _i37.MaterialPageRoute<dynamic>(
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<CourseModuleViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
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<LearnCourseViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
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<dynamic> navigateToHomeView({
_i37.Key? key,
@ -1991,23 +1986,6 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition);
}
Future<dynamic> navigateToLearnCourseView({
_i37.Key? key,
required int id,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return navigateTo<dynamic>(Routes.learnCourseView,
arguments: LearnCourseViewArguments(key: key, id: id),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> navigateToAssessmentView({
_i37.Key? key,
required Map<String, dynamic> data,
@ -2126,7 +2104,22 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition);
}
Future<dynamic> navigateToLearnCourseView({
_i37.Key? key,
required int id,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return navigateTo<dynamic>(Routes.learnCourseView,
arguments: LearnCourseViewArguments(key: key, id: id),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithHomeView({
_i37.Key? key,
@ -2584,23 +2577,6 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition);
}
Future<dynamic> replaceWithLearnCourseView({
_i37.Key? key,
required int id,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return replaceWith<dynamic>(Routes.learnCourseView,
arguments: LearnCourseViewArguments(key: key, id: id),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithAssessmentView({
_i37.Key? key,
required Map<String, dynamic> data,
@ -2719,5 +2695,20 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition);
}
Future<dynamic> replaceWithLearnCourseView({
_i37.Key? key,
required int id,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return replaceWith<dynamic>(Routes.learnCourseView,
arguments: LearnCourseViewArguments(key: key, id: id),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,62 +1,51 @@
import 'package:battery_plus/battery_plus.dart';
import 'package:flutter/services.dart';
import 'package: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<ApiService>();
final _navigationService = locator<NavigationService>();
Future<int> getBatteryLevel() async {
final battery = Battery();
final batteryLevel = await battery.batteryLevel;
return batteryLevel;
}
Future<int> getAvailableStorage() async {
try {
final availableStorage =
await StorageInfo().getStorageFreeSpace(SpaceUnit.Bytes);
return availableStorage.toInt(); // Convert GB to bytes
} catch (e) {
return 0;
}
}
final _statusCheckerService = locator<StatusCheckerService>();
Future<void> checkForUpdate() async {
const requiredStorage = 500 * 1024 * 1024;
final batteryLevel =
await getBatteryLevel(); // Implement getBatteryLevel function
final int storageAvailable =
await getAvailableStorage(); // Implement getAvailableStorage
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
// KewedeConst().showErrorToast(
// 'Unable to update app, please charge your phone & free up space.');
} else if (batteryLevel < 20) {
// KewedeConst()
// .showErrorToast('Unable to update app, please charge your phone.');
} else if (storageAvailable < requiredStorage) {
// KewedeConst()
// .showErrorToast('Unable to update app, please free up space.');
}
// Show user-friendly message explaining why update failed and suggesting solutions (e.g., charge device, free up space)
}
try {
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<String, dynamic> data = {
'platform': 'ANDROID',
'version_code': info.availableVersionCode
};
Map<String, dynamic> response = await _apiService.checkUpdate(data);
if (response['status'] == ResponseStatus.success) {
AppUpdate update = response['data'] as AppUpdate;
if (update.updateAvailable ?? false) {
if (update.forceUpdate ?? false) {
AppUpdateResult result =
await InAppUpdate.performImmediateUpdate();
if (result == AppUpdateResult.userDeniedUpdate) {
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
}

View File

@ -23,7 +23,7 @@ class LocalizationService with ListenableServiceMixin {
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
final List<Map<String, dynamic>> _languages = [
{'code': 'አማ', 'language': 'አማርኛ'},
{'code': 'am', 'language': 'አማርኛ'},
{'code': 'en', 'language': 'English'},
];
@ -37,7 +37,7 @@ class LocalizationService with ListenableServiceMixin {
required Map<String, dynamic> 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<void> 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();
}

View File

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

View File

@ -1,4 +1,5 @@
import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';
import 'package: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<String> getAppVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String version = packageInfo.version; // e.g. 1.0.0
String buildNumber = packageInfo.buildNumber; // version code
print("Version: $version");
print("Build Number: $buildNumber");
return buildNumber;
}
}

View File

@ -1,10 +1,14 @@
// Endpoints
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';

View File

@ -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<String,dynamic> _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<String,dynamic> _en = {
"what_should_we_call_you": "What should we call you?",
"name_for_personalization": "Well use your name to personalize your learning journey.",
"choose_your_gender": "Choose your gender?",
"gender_for_personalization": "Well personalize your learning experience based on your gender."
"gender_for_personalization": "Well personalize your learning experience based on your gender.",
"age_range": "Which age range are you in?",
"age_for_personalization": "Well personalize your learning experience based on your age.",
"educational_background": "Whats your current educational level?",
"education_for_personalization": "This helps us tailor your lessons to your experience.",
"your_occupation": "Whats your occupation?",
"occupation_for_personalization": "Well personalize your learning experience based on your occupation.",
"location": "Where are you from?",
"select_country_region": "Select your country and region from the dropdown",
"select_country": "Select country",
"learning_goal": "Choose your learning goal.",
"language_goal": "Whats your main goal for improving your English?",
"your_goal": "Your goal helps us tailor your learning journey.",
"write_your_goal": "Write your goal…",
"challenge_you_face": "What challenge do you face most with English?",
"evey_one_has_strugle": "Everyone has struggles, lets start fixing yours",
"write_your_challenge": "Write your challenge…",
"topic_interest": "Which topics interest you most?",
"favourite_topic": "Your favorite topics help us create fun, relatable lessons.",
"your_interest": "Write your interest…",
"want_quick_assessment": "Want a quick assessment to know your English level?",
"answer_quick_questions": "Answer a few quick questions to help us understand your English proficiency.",
"skip": "Skip",
"finish_level": "Finish Level",
"likely_speaker": "Youre likely speaker of",
"great_job": "Great Job! Heres your next step to keep improving.",
"lets_start_practice": "Let's start your practice",
"welcome_abroad": "Welcome aboard",
"ready_to_explore": "Youre ready to explore your personalized lessons.",
"finish": "Finish"
};
static const Map<String, Map<String,dynamic>> mapLocales = {"am": _am, "en": _en};
}

View File

@ -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';
}

View File

@ -57,7 +57,7 @@ class FormValidator {
}
if (value.isEmpty) {
return LocaleKeys.required_field.tr();
return LocaleKeys.required_field.tr();
}
return null;
}

View File

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

View File

@ -1,6 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package: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<AssessmentViewModel> {
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<AssessmentViewModel> {
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<AssessmentViewModel> {
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),

View File

@ -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<AssessmentViewModel> {
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

View File

@ -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<AssessmentViewModel> {
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<AssessmentViewModel> {
];
Widget _buildTitle(AssessmentViewModel viewModel) => Text(
'Youre likely a ${viewModel.proficiencyLevel?.toUpperCase()} speaker!',
'${LocaleKeys.likely_speaker.tr()} ${viewModel.proficiencyLevel?.toUpperCase()}',
style: style25DG600,
textAlign: TextAlign.center,
);
Widget _buildPrimarySubtitle() => Text(
'Great Job! Heres your next step to keep improving.',
LocaleKeys.great_job.tr(),
style: style14MG400,
textAlign: TextAlign.center,
);
@ -93,7 +97,7 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
'assets/icons/${viewModel.proficiencyLevel?.substring(0, 1).toLowerCase()}_${viewModel.proficiencyLevel?.substring(1).toLowerCase()}.svg');
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<AssessmentViewModel> {
Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
text: LocaleKeys.cont.tr(),
onTap: () => viewModel.next(),
backgroundColor: kcPrimaryColor,
);

View File

@ -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<AssessmentViewModel> {
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<AssessmentViewModel> {
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<AssessmentViewModel> {
);
Widget _buildSubtitle() => Text(
'Youre ready to explore your personalized lessons.',
LocaleKeys.ready_to_explore.tr(),
style: style14MG400,
);
@ -116,9 +120,9 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Finish',
borderRadius: 12,
foregroundColor: kcWhite,
text: LocaleKeys.finish.tr(),
backgroundColor: kcPrimaryColor,
onTap: () async => await _start(viewModel),
);

View File

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

View File

@ -96,8 +96,10 @@ class RequestCodeScreen extends ViewModelWidget<ForgetPasswordViewModel> {
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(

View File

@ -96,8 +96,10 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
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) =>

View File

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

View File

@ -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> {
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(),
);

View File

@ -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<AuthenticationService>();
// Controller
final FlutterCarouselController _controller = FlutterCarouselController();
FlutterCarouselController get controller => _controller;
// In-app navigation
void next() {
_controller.nextPage();
}
// Navigation
Future<void> navigateToLogin() async =>
await _navigationService.replaceWithLoginView();
@ -18,6 +29,7 @@ class LandingViewModel extends BaseViewModel {
// Remote api call
// First time install
Future<void> setFirstTimeInstall() async {
await runBusyFuture(_setFirstTimeInstall());
}

View File

@ -126,10 +126,10 @@ class FirstLandingScreen extends ViewModelWidget<LandingViewModel> {
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(),
);
}

View File

@ -127,10 +127,10 @@ class SecondLandingScreen extends ViewModelWidget<LandingViewModel> {
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(),
);
}

View File

@ -122,15 +122,15 @@ class ThirdLandingScreen extends ViewModelWidget<LandingViewModel> {
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(),
);
}

View File

@ -108,16 +108,16 @@ class LanguageView extends StackedView<LanguageViewModel> {
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,
);

View File

@ -89,6 +89,7 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
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<LearnCourseViewModel> {
Widget _buildTile({
required LearnCourse course,
required GestureTapCallback onViewTap,
required GestureTapCallback onLockTap,
required GestureTapCallback onPracticeTap,
}) =>
LearnCourseTile(
course: course,
onViewTap: onViewTap,
onLockTap: onLockTap,
onPracticeTap: onPracticeTap,
);
}

View File

@ -29,6 +29,9 @@ class LearnCourseViewModel extends ReactiveViewModel {
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnSubscription() async =>
await _navigationService.navigateToLearnSubscriptionView();
Future<void> 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',
);

View File

@ -152,7 +152,7 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
Widget _buildMotivationCard() => const MotivationCard();
Widget _buildHeader() => Text(
LocaleKeys.lessons_in_module.tr(),
LocaleKeys.lessons_in_module.tr(),
style: style18DG700,
);

View File

@ -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<void> navigateToLearnLessonDetail(

View File

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

View File

@ -99,7 +99,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
);
Widget _buildSubtitle() => Text(
LocaleKeys.your_current_level.tr(),
LocaleKeys.your_current_level.tr(),
style: style14P400,
);

View File

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

View File

@ -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),
);

View File

@ -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(),
);
}

View File

@ -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(),
);
}

View File

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

View File

@ -61,7 +61,7 @@ class LearnPracticeFinishScreen
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
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) =>

View File

@ -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),
);

View File

@ -196,7 +196,7 @@ class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
];
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<LearnPracticeViewModel> {
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));

View File

@ -89,14 +89,12 @@ class LearnProgramView extends StackedView<LearnProgramViewModel> {
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);
}

View File

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

View File

@ -92,8 +92,10 @@ class LoginOtpScreen extends ViewModelWidget<LoginViewModel> {
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(

View File

@ -88,8 +88,10 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
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(

View File

@ -85,8 +85,10 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
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(

View File

@ -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<OnboardingViewModel>
with $OnboardingView {
@ -34,11 +33,8 @@ class OnboardingView extends StackedView<OnboardingViewModel>
}
void _initClearData() {
topicController.clear();
regionController.clear();
fullNameController.clear();
challengeController.clear();
languageGoalController.clear();
}
void _clearDataOnNavigation(OnboardingViewModel viewModel) {
@ -54,17 +50,15 @@ class OnboardingView extends StackedView<OnboardingViewModel>
} 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<OnboardingViewModel>
}
@override
void onViewModelReady(OnboardingViewModel viewModel) {
void onViewModelReady(OnboardingViewModel viewModel) async {
_initClearData();
_initUserData(viewModel);
syncFormWithViewModel(viewModel);
@ -134,17 +128,13 @@ class OnboardingView extends StackedView<OnboardingViewModel>
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();
}

View File

@ -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<String, TextEditingController> _OnboardingViewTextEditingControllers =
{};
@ -25,31 +22,18 @@ final Map<String, TextEditingController> _OnboardingViewTextEditingControllers =
final Map<String, FocusNode> _OnboardingViewFocusNodes = {};
final Map<String, String? Function(String?)?> _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),
});

View File

@ -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<GoogleAuthService>();
final _onboardingService = locator<OnboardingService>();
final _localizationService = locator<LocalizationService>();
@override
@ -47,19 +54,15 @@ class OnboardingViewModel extends ReactiveViewModel
bool get focusFullName => _focusFullName;
// Educational background
final List<String> _educationalBackgrounds = [
'No formal education',
'Primary school',
'Secondary /High school',
'College / Diploma',
'Bachelors and above',
];
List<FieldOption> get _educationalBackgrounds =>
_onboardingService.educationalBackgrounds;
List<String> get educationalBackgrounds => _educationalBackgrounds;
List<FieldOption> get educationalBackgrounds => _educationalBackgrounds;
String? _selectedEducationalBackground;
FieldOption? _selectedEducationalBackground;
String? get selectedEducationalBackground => _selectedEducationalBackground;
FieldOption? get selectedEducationalBackground =>
_selectedEducationalBackground;
// Gender
final List<String> _gendersEn = [
@ -81,150 +84,73 @@ class OnboardingViewModel extends ReactiveViewModel
String? get selectedGender => _selectedGender;
// Age group
final List<Map<String, dynamic>> _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<FieldOption> get _ageGroups => _onboardingService.ageGroups;
List<Map<String, dynamic>> get ageGroups => _ageGroups;
List<FieldOption> get ageGroups => _ageGroups;
Map<String, dynamic>? _selectedAgeGroup;
FieldOption? _selectedAgeGroup;
Map<String, dynamic>? get selectedAgeGroup => _selectedAgeGroup;
FieldOption? get selectedAgeGroup => _selectedAgeGroup;
// Occupation
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<Map<String, dynamic>> _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<FieldOption> get _learningGoals => _onboardingService.learningGoals;
List<Map<String, dynamic>> get learningGoals => _learningGoals;
List<FieldOption> 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<FieldOption> get _languageGoals => _onboardingService.languageGoals;
String? _selectedLanguageGoal;
String? get selectedLanguageGoal => _selectedLanguageGoal;
final List<String> _languageGoals = [
'Speak confidently at work or school',
'Travel or handle daily situations',
'Connect with family or friends',
'General skills expansion',
'Other'
];
List<String> get languageGoals => _languageGoals;
List<FieldOption> get languageGoals => _languageGoals;
// Challenges
bool _showChallengeTextBox = false;
FieldOption? _selectedChallenge;
bool get showChallengeTextBox => _showChallengeTextBox;
FieldOption? get selectedChallenge => _selectedChallenge;
bool _focusChallenge = false;
List<FieldOption> get _challenges => _onboardingService.challenges;
bool get focusChallenge => _focusChallenge;
String? _selectedChallenge;
String? get selectedChallenge => _selectedChallenge;
final List<String> _challenges = [
'Pronunciation',
'Finding words or grammar quickly',
'Feeling nervous or lacking confidence',
'Understanding accents or fast speech',
'Other'
];
List<String> get challenges => _challenges;
List<FieldOption> get challenges => _challenges;
// Topic
bool _showTopicTextBox = false;
FieldOption? _selectedTopic;
bool get showTopicTextBox => _showTopicTextBox;
FieldOption? get selectedTopic => _selectedTopic;
bool _focusTopic = false;
List<FieldOption> get _topics => _onboardingService.topics;
bool get focusTopic => _focusTopic;
String? _selectedTopic;
String? get selectedTopic => _selectedTopic;
final List<String> _topics = [
'Food & Cooking',
'Hobbies, Sports, Music',
'Tech, News, Business',
'Travel, Places, Culture',
'Other'
];
List<String> get topics => _topics;
List<FieldOption> get topics => _topics;
// User data
final Map<String, dynamic> _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<String, dynamic> value) {
void setSelectedAgeGroup(FieldOption value) {
_selectedAgeGroup = value;
rebuildUi();
}
bool isSelectedAgeGroup(Map<String, dynamic> value) =>
_selectedAgeGroup == value;
bool isSelectedAgeGroup(FieldOption value) => _selectedAgeGroup == value;
// Occupation
List<String> 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<FieldOption> get _occupations => _onboardingService.occupations;
void setSelectedOccupation(String value) {
List<FieldOption> get occupations => _occupations;
void setSelectedOccupation(FieldOption? value) {
_selectedOccupation = value;
rebuildUi();
}
// Country
List<String> 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<FieldOption> get _countries => _onboardingService.countries;
void setSelectedCountry(String value) {
List<FieldOption> 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<String> getRegions() => [
'Addis Ababa',
'Afar',
'Amhara',
'Benishangul-Gumuz',
'Central Ethiopia',
'Dire Dawa',
'Gambela',
'Harari',
'Oromia',
'Sidama',
'Somali',
'South Ethiopia',
'South West Ethiopia Peoples',
'Tigray',
];
List<FieldOption> get _regions => _onboardingService.regions;
void setSelectedRegion(String value) {
List<FieldOption> 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<String, dynamic> 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();
}

View File

@ -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<OnboardingViewModel> {
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {
'age_group': viewModel.selectedAgeGroup?.values.first
};
Map<String, dynamic> data = {'age_group': viewModel.selectedAgeGroup?.code};
viewModel.addUserData(data);
viewModel.next();
@ -88,17 +88,19 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
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(
'Well personalize your learning experience based on your age.',
LocaleKeys.age_for_personalization.tr(),
style: style14DG400,
);
@ -107,7 +109,7 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
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<OnboardingViewModel> {
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),

View File

@ -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<OnboardingViewModel> {
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<OnboardingViewModel> {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {
'language_challange':
viewModel.selectedChallenge ?? challengeController.text,
'language_challange': viewModel.selectedChallenge?.code,
};
viewModel.addUserData(data);
@ -86,15 +84,6 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
_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<OnboardingViewModel> {
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, lets start fixing yours 😊',
'${LocaleKeys.evey_one_has_strugle.tr()} 😊',
style: style14MG400,
);
@ -122,7 +113,7 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
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<OnboardingViewModel> {
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<OnboardingViewModel> {
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));
}

View File

@ -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<OnboardingViewModel> {
@ -13,10 +16,10 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
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<OnboardingViewModel> {
Map<String, dynamic> 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<OnboardingViewModel> {
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<OnboardingViewModel> {
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<OnboardingViewModel> {
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),
);
}

View File

@ -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<String, dynamic> 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(
'Whats 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,

View File

@ -90,18 +90,20 @@ class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
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) =>

View File

@ -82,8 +82,10 @@ class GenderFormScreen extends ViewModelWidget<OnboardingViewModel> {
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<OnboardingViewModel> {
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],
),
);

View File

@ -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<OnboardingViewModel> {
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<OnboardingViewModel> {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {
'language_goal':
viewModel.selectedLanguageGoal ?? languageGoalController.text,
'language_goal': viewModel.selectedLanguageGoal?.code,
};
viewModel.addUserData(data);
@ -87,15 +84,6 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
_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<OnboardingViewModel> {
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(
'Whats 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<OnboardingViewModel> {
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<OnboardingViewModel> {
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<OnboardingViewModel> {
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));
}

View File

@ -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<OnboardingViewModel> {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {
'learning_goal': viewModel.selectedLearningGoal,
'learning_goal': viewModel.selectedLearningGoal?.code,
};
viewModel.addUserData(data);
@ -98,17 +100,20 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
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<OnboardingViewModel> {
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<OnboardingViewModel> {
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
text: LocaleKeys.cont.tr(),
onTap: viewModel.selectedLearningGoal != null
? () => _next(viewModel)
: null,

View File

@ -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<OnboardingViewModel> {
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {'occupation': viewModel.selectedOccupation};
Map<String, dynamic> data = {
'occupation': viewModel.selectedOccupation?.code
};
viewModel.addUserData(data);
viewModel.next();
@ -86,28 +90,30 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
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(
'Whats your occupation?',
LocaleKeys.your_occupation.tr(),
style: style25DG600,
);
Widget _buildSubtitle() => Text(
'Well 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<OnboardingViewModel> {
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),
);
}

View File

@ -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<OnboardingViewModel> {
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<OnboardingViewModel> {
Map<String, dynamic> 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<OnboardingViewModel> {
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<OnboardingViewModel> {
_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<OnboardingViewModel> {
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<OnboardingViewModel> {
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<OnboardingViewModel> {
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));
}

View File

@ -158,7 +158,7 @@ class ProfileView extends StackedView<ProfileViewModel> {
List<Widget> _buildSettingsChildren(ProfileViewModel viewModel) => [
// _buildDownloadsCard(viewModel),
// _buildProgressCard(viewModel),
// _buildProgressCard(viewModel),
_buildAccountCard(viewModel),
_buildSupportCard(viewModel)
];

View File

@ -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<ProfileDetailViewModel>
verticalSpaceMedium,
_buildCountryDropdownLabel(),
verticalSpaceSmall,
_buildCountryDropdown(viewModel),
// _buildCountryDropdown(viewModel),
verticalSpaceMedium,
_buildRegionFormFieldWrapper(viewModel),
verticalSpaceMedium,
@ -527,13 +526,13 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
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<ProfileDetailViewModel>
);
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<ProfileDetailViewModel>
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<ProfileDetailViewModel>
[
_buildOccupationDropdownLabel(),
verticalSpaceSmall,
_buildOccupationDropdown(viewModel)
// _buildOccupationDropdown(viewModel)
];
Widget _buildOccupationDropdownLabel() => CustomFormLabel(
@ -619,14 +619,14 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
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<ProfileDetailViewModel>
borderRadius: 12,
onTap: viewModel.pop,
backgroundColor: kcWhite,
text:LocaleKeys.cancel.tr(),
text: LocaleKeys.cancel.tr(),
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
);

View File

@ -75,8 +75,10 @@ class CreatePasswordScreen extends ViewModelWidget<RegisterViewModel> {
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) =>

View File

@ -91,8 +91,10 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
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(

View File

@ -94,8 +94,10 @@ class RegisterWithPhoneNumberScreen extends ViewModelWidget<RegisterViewModel> {
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(

View File

@ -105,8 +105,10 @@ class RegistrationOtpScreen extends ViewModelWidget<RegisterViewModel> {
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(

View File

@ -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<ApiService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
final _onboardingService = locator<OnboardingService>();
final _localizationService = locator<LocalizationService>();
final _authenticationService = locator<AuthenticationService>();
final _imageDownloaderService = locator<ImageDownloaderService>();
@override
List<ListenableServiceMixin> 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<void> etOnboardingFields() async {
bool response = await _onboardingService.getOnboardingFields();
if (response) {
await replaceWithOnboarding();
} else {
await replaceWithFailure();
}
}
}

View File

@ -55,8 +55,8 @@ class SupportView extends StackedView<SupportViewModel> {
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<SupportViewModel> {
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(),
);

View File

@ -135,11 +135,14 @@ class TelegramSupportView extends StackedView<TelegramSupportViewModel> {
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',
)
]),
);
}

View File

@ -58,10 +58,10 @@ class CancelLearnPracticeSheet extends StatelessWidget {
style: style18DG700,
children: [
TextSpan(
text: ' $user',
style: style18P600,
)
]),
text: ' $user',
style: style18P600,
)
]),
);
Widget _buildSubtitle() => Text(

View File

@ -5,12 +5,15 @@ import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/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<List<String>> Function(String value, LoadProps? props)? items;
final FieldOption? selectedItem;
final void Function(FieldOption?)? onChanged;
final FutureOr<List<FieldOption>> Function(String value, LoadProps? props)?
items;
const CustomDropdownPicker(
{super.key,
@ -28,16 +31,18 @@ class CustomDropdownPicker extends StatelessWidget {
child: _buildDropDownSearch(),
);
Widget _buildDropDownSearch() => DropdownSearch<String>(
Widget _buildDropDownSearch() => DropdownSearch<FieldOption>(
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<String> _popupProps() => PopupProps.menu(
PopupProps<FieldOption> _popupProps() => PopupProps<FieldOption>.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,
);

View File

@ -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<Widget> _buildButtonRowChildren() =>
[_buildIconSectionWrapper(), _buildTitle(), _buildSubtitle()];
[_buildTitleWrapper(), _buildSelectedCheckBox()];
Widget _buildIconSectionWrapper() => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildIconSectionChildren(),
);
List<Widget> _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(

View File

@ -13,18 +13,26 @@ import 'custom_elevated_button.dart';
class LearnCourseTile extends ViewModelWidget<LearnCourseViewModel> {
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<LearnCourseViewModel> {
? _buildProgressStatus()
: Container();
Widget _buildProgressStatus() => ProgressStatus(
Widget _buildProgressStatus() => ProgressStatus(
color: kcPrimaryColor,
status: LocaleKeys.current_level.tr(),
);
@ -147,7 +155,7 @@ class LearnCourseTile extends ViewModelWidget<LearnCourseViewModel> {
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<LearnCourseViewModel> {
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
text:LocaleKeys.take_practice.tr() ,
text: LocaleKeys.take_practice.tr(),
);
}

View File

@ -130,7 +130,9 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
);
Widget _buildProgressText() => Text(
(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<LearnLessonViewModel> {
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<LearnLessonViewModel> {
onTap: onLessonTap,
width: double.maxFinite,
foregroundColor: kcWhite,
text:LocaleKeys.start.tr() ,
text: LocaleKeys.start.tr(),
trailingIcon: Icons.play_arrow,
backgroundColor: kcPrimaryColor,
);

View File

@ -18,10 +18,7 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
final GestureTapCallback? onPracticeTap;
const LearnModuleTile(
{super.key,
this.onModuleTap,
this.onPracticeTap,
required this.module});
{super.key, this.onModuleTap, this.onPracticeTap, required this.module});
Future<void> _showSheet(
{required BuildContext context,
@ -54,7 +51,7 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
Stack(
children: [
_buildExpansionTile(context: context, viewModel: viewModel),
_buildContainerShaderState()
// _buildContainerShaderState()
],
);
@ -73,14 +70,14 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
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<LearnModuleViewModel> {
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(

View File

@ -53,7 +53,7 @@ class LearnPracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
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<LearnPracticeViewModel> {
Widget _buildActualResponse() => LearnPracticeAnswerCard(
answer: answer,
voice: Voice.recorded,
title: LocaleKeys.your_answer.tr(),
);
title: LocaleKeys.your_answer.tr(),
);
}

View File

@ -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,
);

View File

@ -41,7 +41,7 @@ class LearnPracticeTipSection extends ViewModelWidget<LearnPracticeViewModel> {
);
Widget _buildTitle() => Text(
LocaleKeys.quick_tip.tr(),
LocaleKeys.quick_tip.tr(),
style: style16B600,
);

View File

@ -13,20 +13,12 @@ import 'custom_elevated_button.dart';
class LearnProgramTile extends ViewModelWidget<LearnProgramViewModel> {
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<LearnProgramViewModel> {
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<LearnProgramViewModel> {
backgroundColor: kcPrimaryColor,
text: program.access?.progressPercent == 0
? LocaleKeys.start_learning.tr()
:LocaleKeys.continue_learning.tr() ,
: LocaleKeys.continue_learning.tr(),
);
}

View File

@ -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,
),

View File

@ -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,
);
}

View File

@ -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,
);

View File

@ -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"))

View File

@ -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:

View File

@ -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

View File

@ -22,10 +22,9 @@ import 'package:yimaru_app/services/url_launcher_service.dart';
import 'package:yimaru_app/services/phone_caller_service.dart';
import 'package:yimaru_app/services/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<UrlLauncherService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<UrlLauncherService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<PhoneCallerService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<LearnLessonService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<LocalizationService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<OnboardingService>(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<LocalizationService>(service);
return service;
}
MockOnboardingService getAndRegisterOnboardingService() {
_removeRegistrationIfExists<OnboardingService>();
final service = MockOnboardingService();
locator.registerSingleton<OnboardingService>(service);
return service;
}
// @stacked-mock-create
void _removeRegistrationIfExists<T extends Object>() {

View File

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

View File

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

View File

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