feat: Add localization according to the UAT comments
This commit is contained in:
parent
dca2ffba0e
commit
d48a6be61a
85
assets/translations/am.json
Normal file
85
assets/translations/am.json
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
{
|
||||||
|
|
||||||
|
"welcome_back": "እንኳን በደህና ተመለሱ",
|
||||||
|
"dont_have_account": "መለያ የለዎትም? ይመዝገቡ",
|
||||||
|
"email": "ኢሜይል",
|
||||||
|
"password": "የይለፍ ቃል",
|
||||||
|
"forgot_password": "የይለፍ ቃል ረሱ?",
|
||||||
|
"cont": "ቀጥል",
|
||||||
|
"register": "ይመዝገቡ",
|
||||||
|
"login_with_google": "በጉግል ይግቡ",
|
||||||
|
"or": "ወይም",
|
||||||
|
"login_with_phone": "በስልክ ቁጥር ይግቡ",
|
||||||
|
"create_account": "አዲስ መለያ ይፍጠሩ",
|
||||||
|
"already_have_account": "መለያ አለዎት?",
|
||||||
|
"login": " ይግቡ ",
|
||||||
|
"register_with_google": "በጉግል ይመዝገቡ",
|
||||||
|
"register_with_phone": "በስልክ ቁጥር ይመዝገቡ",
|
||||||
|
"enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።",
|
||||||
|
"login_with_email": "በኢሜይል ይግቡ",
|
||||||
|
"create_password": "የይለፍ ቃል ይፍጠሩ",
|
||||||
|
"confirm_password": "የይለፍ ቃል ያረጋግጡ",
|
||||||
|
"sign_up_agreement": "‘ይመዝገቡ’ የሚለውን ሲጫኑ በ‘አገልግሎት ውሎች’ እና ‘በግላዊነት ፖሊሲ’ ይስማማሉ።" ,
|
||||||
|
"reset_password": " የይለፍ ቃልን ይቀይሩ ",
|
||||||
|
"enter_email_reset_code": "ኢሜይልዎን ያስገቡ። የይለፍ ቃል መለወጫ ኮድ እንልክልዎታለን።" ,
|
||||||
|
"please_wait": "እባክዎ ይጠብቁ",
|
||||||
|
"reset_code_sent": "የመቀየሪያ ኮድ በተሳካ ሁኔታ ተልኳል" ,
|
||||||
|
"reset_code": " የመቀየሪያ ኮድ ",
|
||||||
|
"new_password": "አዲስ የይለፍ ቃል",
|
||||||
|
"logged_in_successfully": "በተሳካ ሁኔታ ገብተዋል",
|
||||||
|
"view_course": " ኮርሱን ይመልከቱ ",
|
||||||
|
"take_practice": " ልምምድ ያድርጉ ",
|
||||||
|
"your_current_level": "የአሁኑ ደረጃዎ",
|
||||||
|
"overall_progress": "አጠቃላይ እድገት",
|
||||||
|
"great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው ",
|
||||||
|
"view_module": "ሞጁሉን ይመልከቱ",
|
||||||
|
"progress": "እድገት",
|
||||||
|
"keep_going": " ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ",
|
||||||
|
"lessons_in_module": " በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ",
|
||||||
|
"practice": "ልምምድ",
|
||||||
|
"start": "ጀምር",
|
||||||
|
"in_progress": "በሂደት ላይ",
|
||||||
|
"hello": "ሰላም",
|
||||||
|
"ready_to_learn": " ዛሬ እንግሊዝኛ ለመማር ተዘጋጅተዋል? ",
|
||||||
|
"learn": "ይማሩ ",
|
||||||
|
"course": "ኮርስ",
|
||||||
|
"profile": " ፕሮፋይል ",
|
||||||
|
"speaking_partner": "የንግግር ጓደኛ ",
|
||||||
|
"practice_what_you_learned": "አሁን የተማሩትን እንለማመድ",
|
||||||
|
"practice_questions": "ጥቂት ጥያቄዎችን እጠይቃለሁ እና መልስ መስጠት ይችላሉ",
|
||||||
|
"start_practice": "ልምምድ ጀምር",
|
||||||
|
"almost_there": "ሊጨርሱ ተቃርበዋል",
|
||||||
|
"finish_session": "እድገትዎን ለማየት ክፍለ ጊዜውን ያጠናቅቁ",
|
||||||
|
"continue_practice": "ልምምዱን ይቀጥሉ",
|
||||||
|
"end_session": "ክፍለ ጊዜውን ያብቁ ",
|
||||||
|
"tap_start_to_listen": "ለማዳመጥ የጀምር ቁልፉን ይጫኑ",
|
||||||
|
"practice_speaking": "ንግግርን ይለማመዱ ",
|
||||||
|
"tap_microphone": "ለመናገር ማይክሮፎኑን ይጫኑ",
|
||||||
|
"reply": "እንደገና አዳምጥ",
|
||||||
|
"cancel": "ይቅር",
|
||||||
|
"you_are_speaking": "እየተናገሩ ነው",
|
||||||
|
"practice_completed": "ልምምዱ ተጠናቅቋል",
|
||||||
|
"great_improvement": "በዚህኛው በራስ መተማመንዎ ጨምሯል፤ ትልቅ መሻሻል ነው",
|
||||||
|
"practice_again": "እንደገና ይለማመዱ",
|
||||||
|
"conversation_review": "የንግግር ግምገማ ",
|
||||||
|
"result": "ውጤት",
|
||||||
|
"quick_tip": "ጠቃሚ ምክር",
|
||||||
|
"retry": "እንደገና ይሞክሩ",
|
||||||
|
"completed_a1": "እንኳን ደስ አለዎት! A1 ደረጃን አጠናቅቀዋል",
|
||||||
|
"analyzing_speaking": "የንግግር ችሎታዎን እየገመገምን ነው",
|
||||||
|
"view_profile": "ፕሮፋይሎን ይመልከቱ ",
|
||||||
|
"hi": "ሰላም",
|
||||||
|
"edit_profile": "መገለጫ ያስተካክሉ",
|
||||||
|
"first_name": "የመጀመሪያ ስም",
|
||||||
|
"last_name": "የአባት ስም",
|
||||||
|
"gender": "ፆታ",
|
||||||
|
"male": "ወንድ",
|
||||||
|
"female": "ሴት",
|
||||||
|
"phone_number": "የስልክ ቁጥር",
|
||||||
|
"country": "ሀገር",
|
||||||
|
"region": "ክልል",
|
||||||
|
"occupation": "የስራ መስክ ",
|
||||||
|
"save_changes": "ለውጦችን ያስቀምጡ"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
83
assets/translations/en.json
Normal file
83
assets/translations/en.json
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
|
||||||
|
{
|
||||||
|
"welcome_back": "Welcome back",
|
||||||
|
"dont_have_account": "Don't have an account? Register",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"forgot_password": "Forgot password?",
|
||||||
|
"cont": "Continue",
|
||||||
|
"register": "Register",
|
||||||
|
"login_with_google": "Login with Google",
|
||||||
|
"or": "Or",
|
||||||
|
"login_with_phone": "Login with phone number",
|
||||||
|
"create_account": "Create an account",
|
||||||
|
"already_have_account": "Already have an account?",
|
||||||
|
"login": "Login",
|
||||||
|
"register_with_google": "Register with Google",
|
||||||
|
"register_with_phone": "Register with phone number",
|
||||||
|
"enter_phone_number": "Enter your phone number. We will send you a confirmation code there.",
|
||||||
|
"login_with_email": "Login with email",
|
||||||
|
"create_password": "Create password",
|
||||||
|
"confirm_password": "Confirm password",
|
||||||
|
"sign_up_agreement": "By clicking ‘Sign Up’, you agree to our ‘Terms of Service’ and ‘Privacy Policy’",
|
||||||
|
"reset_password": "Reset Password",
|
||||||
|
"enter_email_reset_code": "Enter your email. We will send you a reset code.",
|
||||||
|
"please_wait": "Please wait",
|
||||||
|
"reset_code_sent": "Reset code sent successfully",
|
||||||
|
"reset_code": "Reset code",
|
||||||
|
"new_password": "New password",
|
||||||
|
"logged_in_successfully": "Logged in successfully",
|
||||||
|
"view_course": "View course",
|
||||||
|
"take_practice": "Take practice",
|
||||||
|
"your_current_level": "Your current level",
|
||||||
|
"overall_progress": "Overall progress",
|
||||||
|
"great_work": "Keep up the great work! You're doing amazing",
|
||||||
|
"view_module": "View module",
|
||||||
|
"progress": "Progress",
|
||||||
|
"keep_going": "Let's keep going - you're more than half there",
|
||||||
|
"lessons_in_module": "Lessons in this module",
|
||||||
|
"practice": "Practice",
|
||||||
|
"start": "Start",
|
||||||
|
"in_progress": "In Progress",
|
||||||
|
"hello": "Hello",
|
||||||
|
"ready_to_learn": "Ready to keep learning English today",
|
||||||
|
"learn": "Learn",
|
||||||
|
"course": "Course",
|
||||||
|
"profile": "Profile",
|
||||||
|
"speaking_partner": "Speaking partner",
|
||||||
|
"practice_what_you_learned": "Let's practice what you just learnt",
|
||||||
|
"practice_questions": "I will ask you a few questions and you can respond",
|
||||||
|
"start_practice": "Start practice",
|
||||||
|
"almost_there": "You're almost there",
|
||||||
|
"finish_session": "Finish the session to see your progress",
|
||||||
|
"continue_practice": "Continue practice",
|
||||||
|
"end_session": "End session",
|
||||||
|
"tap_start_to_listen": "Tap the start button to listen",
|
||||||
|
"practice_speaking": "Practice speaking",
|
||||||
|
"tap_microphone": "Tap the microphone to speak",
|
||||||
|
"reply": "Reply",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"you_are_speaking": "You're speaking",
|
||||||
|
"practice_completed": "Practice completed",
|
||||||
|
"great_improvement": "You sound more confident this time, great improvement",
|
||||||
|
"practice_again": "Practice again",
|
||||||
|
"conversation_review": "Conversation review",
|
||||||
|
"result": "Result",
|
||||||
|
"quick_tip": "Quick tip",
|
||||||
|
"retry": "Retry",
|
||||||
|
"completed_a1": "Yay, you've completed A1",
|
||||||
|
"analyzing_speaking": "We're now analyzing your speaking skill",
|
||||||
|
"view_profile": "View profile",
|
||||||
|
"hi": "Hi",
|
||||||
|
"edit_profile": "Edit profile",
|
||||||
|
"first_name": "First name",
|
||||||
|
"last_name": "Last name",
|
||||||
|
"gender": "Gender",
|
||||||
|
"male": "Male",
|
||||||
|
"female": "Female",
|
||||||
|
"phone_number": "Phone number",
|
||||||
|
"country": "Country",
|
||||||
|
"region": "Region",
|
||||||
|
"occupation": "Occupation",
|
||||||
|
"save_changes": "Save changes"
|
||||||
|
}
|
||||||
|
|
@ -58,6 +58,7 @@ import 'package:yimaru_app/ui/views/arif_pay/arif_pay_view.dart';
|
||||||
import 'package:yimaru_app/services/learn_service.dart';
|
import 'package:yimaru_app/services/learn_service.dart';
|
||||||
import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart';
|
import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart';
|
import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart';
|
||||||
|
import 'package:yimaru_app/services/localization_service.dart';
|
||||||
// @stacked-import
|
// @stacked-import
|
||||||
|
|
||||||
@StackedApp(
|
@StackedApp(
|
||||||
|
|
@ -124,6 +125,7 @@ import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart';
|
||||||
LazySingleton(classType: UrlLauncherService),
|
LazySingleton(classType: UrlLauncherService),
|
||||||
LazySingleton(classType: PhoneCallerService),
|
LazySingleton(classType: PhoneCallerService),
|
||||||
LazySingleton(classType: LearnService),
|
LazySingleton(classType: LearnService),
|
||||||
|
LazySingleton(classType: LocalizationService),
|
||||||
// @stacked-service
|
// @stacked-service
|
||||||
],
|
],
|
||||||
bottomsheets: [
|
bottomsheets: [
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import '../services/image_downloader_service.dart';
|
||||||
import '../services/image_picker_service.dart';
|
import '../services/image_picker_service.dart';
|
||||||
import '../services/in_app_update_service.dart';
|
import '../services/in_app_update_service.dart';
|
||||||
import '../services/learn_service.dart';
|
import '../services/learn_service.dart';
|
||||||
|
import '../services/localization_service.dart';
|
||||||
import '../services/notification_service.dart';
|
import '../services/notification_service.dart';
|
||||||
import '../services/permission_handler_service.dart';
|
import '../services/permission_handler_service.dart';
|
||||||
import '../services/phone_caller_service.dart';
|
import '../services/phone_caller_service.dart';
|
||||||
|
|
@ -63,4 +64,5 @@ Future<void> setupLocator(
|
||||||
locator.registerLazySingleton(() => UrlLauncherService());
|
locator.registerLazySingleton(() => UrlLauncherService());
|
||||||
locator.registerLazySingleton(() => PhoneCallerService());
|
locator.registerLazySingleton(() => PhoneCallerService());
|
||||||
locator.registerLazySingleton(() => LearnService());
|
locator.registerLazySingleton(() => LearnService());
|
||||||
|
locator.registerLazySingleton(() => LocalizationService());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||||
import 'package:flutter/material.dart' as _i39;
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/material.dart' as _i39;
|
||||||
import 'package:stacked/stacked.dart' as _i1;
|
import 'package:stacked/stacked.dart' as _i1;
|
||||||
import 'package:stacked_services/stacked_services.dart' as _i46;
|
import 'package:stacked_services/stacked_services.dart' as _i47;
|
||||||
import 'package:yimaru_app/models/course.dart' as _i44;
|
import 'package:yimaru_app/models/course.dart' as _i44;
|
||||||
|
import 'package:yimaru_app/models/course_catalog.dart' as _i46;
|
||||||
import 'package:yimaru_app/models/course_lesson.dart' as _i45;
|
import 'package:yimaru_app/models/course_lesson.dart' as _i45;
|
||||||
import 'package:yimaru_app/models/learn_course.dart' as _i40;
|
import 'package:yimaru_app/models/learn_course.dart' as _i40;
|
||||||
import 'package:yimaru_app/models/learn_lesson.dart' as _i42;
|
import 'package:yimaru_app/models/learn_lesson.dart' as _i42;
|
||||||
|
|
@ -664,11 +665,10 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i38.CourseUnitView: (data) {
|
_i38.CourseUnitView: (data) {
|
||||||
final args = data.getArgs<CourseUnitViewArguments>(
|
final args = data.getArgs<CourseUnitViewArguments>(nullOk: false);
|
||||||
orElse: () => const CourseUnitViewArguments(),
|
|
||||||
);
|
|
||||||
return _i39.MaterialPageRoute<dynamic>(
|
return _i39.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => _i38.CourseUnitView(key: args.key),
|
builder: (context) =>
|
||||||
|
_i38.CourseUnitView(key: args.key, catalog: args.catalog),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1586,28 +1586,33 @@ class CourseCatalogViewArguments {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CourseUnitViewArguments {
|
class CourseUnitViewArguments {
|
||||||
const CourseUnitViewArguments({this.key});
|
const CourseUnitViewArguments({
|
||||||
|
this.key,
|
||||||
|
required this.catalog,
|
||||||
|
});
|
||||||
|
|
||||||
final _i39.Key? key;
|
final _i39.Key? key;
|
||||||
|
|
||||||
|
final _i46.CourseCatalog catalog;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return '{"key": "$key"}';
|
return '{"key": "$key", "catalog": "$catalog"}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(covariant CourseUnitViewArguments other) {
|
bool operator ==(covariant CourseUnitViewArguments other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
return other.key == key;
|
return other.key == key && other.catalog == catalog;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
return key.hashCode;
|
return key.hashCode ^ catalog.hashCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NavigatorStateExtension on _i46.NavigationService {
|
extension NavigatorStateExtension on _i47.NavigationService {
|
||||||
Future<dynamic> navigateToHomeView({
|
Future<dynamic> navigateToHomeView({
|
||||||
_i39.Key? key,
|
_i39.Key? key,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
|
|
@ -2216,6 +2221,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
|
||||||
|
|
||||||
Future<dynamic> navigateToCourseUnitView({
|
Future<dynamic> navigateToCourseUnitView({
|
||||||
_i39.Key? key,
|
_i39.Key? key,
|
||||||
|
required _i46.CourseCatalog catalog,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -2223,7 +2229,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
|
||||||
transition,
|
transition,
|
||||||
}) async {
|
}) async {
|
||||||
return navigateTo<dynamic>(Routes.courseUnitView,
|
return navigateTo<dynamic>(Routes.courseUnitView,
|
||||||
arguments: CourseUnitViewArguments(key: key),
|
arguments: CourseUnitViewArguments(key: key, catalog: catalog),
|
||||||
id: routerId,
|
id: routerId,
|
||||||
preventDuplicates: preventDuplicates,
|
preventDuplicates: preventDuplicates,
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
|
|
@ -2838,6 +2844,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
|
||||||
|
|
||||||
Future<dynamic> replaceWithCourseUnitView({
|
Future<dynamic> replaceWithCourseUnitView({
|
||||||
_i39.Key? key,
|
_i39.Key? key,
|
||||||
|
required _i46.CourseCatalog catalog,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -2845,7 +2852,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
|
||||||
transition,
|
transition,
|
||||||
}) async {
|
}) async {
|
||||||
return replaceWith<dynamic>(Routes.courseUnitView,
|
return replaceWith<dynamic>(Routes.courseUnitView,
|
||||||
arguments: CourseUnitViewArguments(key: key),
|
arguments: CourseUnitViewArguments(key: key, catalog: catalog),
|
||||||
id: routerId,
|
id: routerId,
|
||||||
preventDuplicates: preventDuplicates,
|
preventDuplicates: preventDuplicates,
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ import 'package:yimaru_app/app/app.locator.dart';
|
||||||
import 'package:yimaru_app/app/app.router.dart';
|
import 'package:yimaru_app/app/app.router.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
import 'package:yimaru_app/services/notification_service.dart';
|
import 'package:yimaru_app/services/notification_service.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/translations/codegen_loader.g.dart';
|
||||||
import 'firebase_options.dart';
|
import 'firebase_options.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
|
|
@ -15,26 +16,42 @@ Future<void> main() async {
|
||||||
await setupLocator();
|
await setupLocator();
|
||||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||||
await locator<NotificationService>().initialize();
|
await locator<NotificationService>().initialize();
|
||||||
|
await EasyLocalization.ensureInitialized();
|
||||||
setupDialogUi();
|
setupDialogUi();
|
||||||
setupBottomSheetUi();
|
setupBottomSheetUi();
|
||||||
runApp(const MainApp());
|
runApp(
|
||||||
|
EasyLocalization(
|
||||||
|
supportedLocales: const [
|
||||||
|
Locale('en'),
|
||||||
|
Locale('am'),
|
||||||
|
],
|
||||||
|
path: 'assets/translations',
|
||||||
|
startLocale: const Locale('en'),
|
||||||
|
assetLoader: const CodegenLoader(),
|
||||||
|
fallbackLocale: const Locale('en'),
|
||||||
|
child: const MainApp(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainApp extends StatelessWidget {
|
class MainApp extends StatelessWidget {
|
||||||
const MainApp({super.key});
|
const MainApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => _buildMaterialWrapper();
|
Widget build(BuildContext context) => _buildMaterialWrapper(context);
|
||||||
|
|
||||||
Widget _buildMaterialWrapper() => ToastificationWrapper(
|
Widget _buildMaterialWrapper(BuildContext context) => ToastificationWrapper(
|
||||||
child: _buildMaterialApp(),
|
child: _buildMaterialApp(context),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildMaterialApp() => MaterialApp(
|
Widget _buildMaterialApp(BuildContext context) => MaterialApp(
|
||||||
|
locale: context.locale,
|
||||||
initialRoute: Routes.startupView,
|
initialRoute: Routes.startupView,
|
||||||
theme: ThemeData(fontFamily: 'Aeonik'),
|
theme: ThemeData(fontFamily: 'Aeonik'),
|
||||||
navigatorKey: StackedService.navigatorKey,
|
navigatorKey: StackedService.navigatorKey,
|
||||||
|
supportedLocales: context.supportedLocales,
|
||||||
onGenerateRoute: StackedRouter().onGenerateRoute,
|
onGenerateRoute: StackedRouter().onGenerateRoute,
|
||||||
navigatorObservers: [StackedService.routeObserver],
|
navigatorObservers: [StackedService.routeObserver],
|
||||||
|
localizationsDelegates: context.localizationDelegates,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
48
lib/models/course_module.dart
Normal file
48
lib/models/course_module.dart
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'course_module.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class CourseModule {
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
final String? name;
|
||||||
|
|
||||||
|
final String? icon;
|
||||||
|
|
||||||
|
final String? thumbnail;
|
||||||
|
|
||||||
|
final String? description;
|
||||||
|
|
||||||
|
@JsonKey(name: 'unit_id')
|
||||||
|
final int? unitId;
|
||||||
|
|
||||||
|
@JsonKey(name: 'sort_order')
|
||||||
|
final int? sortOrder;
|
||||||
|
|
||||||
|
@JsonKey(name: 'has_practice')
|
||||||
|
final bool? hasPractice;
|
||||||
|
|
||||||
|
@JsonKey(name: 'lessons_count')
|
||||||
|
final int? lessonsCount;
|
||||||
|
|
||||||
|
@JsonKey(name: 'practices_count')
|
||||||
|
final int? practice;
|
||||||
|
|
||||||
|
const CourseModule(
|
||||||
|
{this.id,
|
||||||
|
this.icon,
|
||||||
|
this.name,
|
||||||
|
this.unitId,
|
||||||
|
this.practice,
|
||||||
|
this.thumbnail,
|
||||||
|
this.sortOrder,
|
||||||
|
this.hasPractice,
|
||||||
|
this.description,
|
||||||
|
this.lessonsCount});
|
||||||
|
|
||||||
|
factory CourseModule.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CourseModuleFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$CourseModuleToJson(this);
|
||||||
|
}
|
||||||
34
lib/models/course_module.g.dart
Normal file
34
lib/models/course_module.g.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'course_module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
CourseModule _$CourseModuleFromJson(Map<String, dynamic> json) => CourseModule(
|
||||||
|
id: (json['id'] as num?)?.toInt(),
|
||||||
|
icon: json['icon'] as String?,
|
||||||
|
name: json['name'] as String?,
|
||||||
|
unitId: (json['unit_id'] as num?)?.toInt(),
|
||||||
|
practice: (json['practices_count'] as num?)?.toInt(),
|
||||||
|
thumbnail: json['thumbnail'] as String?,
|
||||||
|
sortOrder: (json['sort_order'] as num?)?.toInt(),
|
||||||
|
hasPractice: json['has_practice'] as bool?,
|
||||||
|
description: json['description'] as String?,
|
||||||
|
lessonsCount: (json['lessons_count'] as num?)?.toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$CourseModuleToJson(CourseModule instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'icon': instance.icon,
|
||||||
|
'thumbnail': instance.thumbnail,
|
||||||
|
'description': instance.description,
|
||||||
|
'unit_id': instance.unitId,
|
||||||
|
'sort_order': instance.sortOrder,
|
||||||
|
'has_practice': instance.hasPractice,
|
||||||
|
'lessons_count': instance.lessonsCount,
|
||||||
|
'practices_count': instance.practice,
|
||||||
|
};
|
||||||
80
lib/models/course_unit.dart
Normal file
80
lib/models/course_unit.dart
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:yimaru_app/models/course_module.dart';
|
||||||
|
|
||||||
|
part 'course_unit.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class CourseUnit {
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
final String? name;
|
||||||
|
|
||||||
|
final String? thumbnail;
|
||||||
|
|
||||||
|
final String? description;
|
||||||
|
|
||||||
|
final List<CourseModule>? modules;
|
||||||
|
|
||||||
|
@JsonKey(name: 'sort_order')
|
||||||
|
final int? sortOrder;
|
||||||
|
|
||||||
|
@JsonKey(name: 'has_practice')
|
||||||
|
final bool? hasPractice;
|
||||||
|
|
||||||
|
@JsonKey(name: 'lessons_count')
|
||||||
|
final int? lessonsCount;
|
||||||
|
|
||||||
|
@JsonKey(name: 'modules_count')
|
||||||
|
final int? modulesCount;
|
||||||
|
|
||||||
|
@JsonKey(name: 'practices_count')
|
||||||
|
final int? practice;
|
||||||
|
|
||||||
|
@JsonKey(name: 'catalog_course_id')
|
||||||
|
final int? catalogCourseId;
|
||||||
|
|
||||||
|
const CourseUnit(
|
||||||
|
{this.id,
|
||||||
|
this.name,
|
||||||
|
this.modules,
|
||||||
|
this.practice,
|
||||||
|
this.thumbnail,
|
||||||
|
this.sortOrder,
|
||||||
|
this.hasPractice,
|
||||||
|
this.description,
|
||||||
|
this.modulesCount,
|
||||||
|
this.lessonsCount,
|
||||||
|
this.catalogCourseId});
|
||||||
|
|
||||||
|
factory CourseUnit.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CourseUnitFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$CourseUnitToJson(this);
|
||||||
|
|
||||||
|
CourseUnit copyWith({
|
||||||
|
int? id,
|
||||||
|
String? name,
|
||||||
|
int? practice,
|
||||||
|
int? sortOrder,
|
||||||
|
bool? hasPractice,
|
||||||
|
int? lessonsCount,
|
||||||
|
int? modulesCount,
|
||||||
|
String? thumbnail,
|
||||||
|
String? description,
|
||||||
|
int? catalogCourseId,
|
||||||
|
List<CourseModule>? modules,
|
||||||
|
}) =>
|
||||||
|
CourseUnit(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
modules: modules ?? this.modules,
|
||||||
|
practice: practice ?? this.practice,
|
||||||
|
thumbnail: thumbnail ?? this.thumbnail,
|
||||||
|
sortOrder: sortOrder ?? this.sortOrder,
|
||||||
|
hasPractice: hasPractice ?? this.hasPractice,
|
||||||
|
description: description ?? this.description,
|
||||||
|
lessonsCount: lessonsCount ?? this.lessonsCount,
|
||||||
|
modulesCount: modulesCount ?? this.modulesCount,
|
||||||
|
catalogCourseId: catalogCourseId ?? this.catalogCourseId,
|
||||||
|
);
|
||||||
|
}
|
||||||
38
lib/models/course_unit.g.dart
Normal file
38
lib/models/course_unit.g.dart
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'course_unit.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
CourseUnit _$CourseUnitFromJson(Map<String, dynamic> json) => CourseUnit(
|
||||||
|
id: (json['id'] as num?)?.toInt(),
|
||||||
|
name: json['name'] as String?,
|
||||||
|
modules: (json['modules'] as List<dynamic>?)
|
||||||
|
?.map((e) => CourseModule.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
practice: (json['practices_count'] as num?)?.toInt(),
|
||||||
|
thumbnail: json['thumbnail'] as String?,
|
||||||
|
sortOrder: (json['sort_order'] as num?)?.toInt(),
|
||||||
|
hasPractice: json['has_practice'] as bool?,
|
||||||
|
description: json['description'] as String?,
|
||||||
|
modulesCount: (json['modules_count'] as num?)?.toInt(),
|
||||||
|
lessonsCount: (json['lessons_count'] as num?)?.toInt(),
|
||||||
|
catalogCourseId: (json['catalog_course_id'] as num?)?.toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$CourseUnitToJson(CourseUnit instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'thumbnail': instance.thumbnail,
|
||||||
|
'description': instance.description,
|
||||||
|
'modules': instance.modules,
|
||||||
|
'sort_order': instance.sortOrder,
|
||||||
|
'has_practice': instance.hasPractice,
|
||||||
|
'lessons_count': instance.lessonsCount,
|
||||||
|
'modules_count': instance.modulesCount,
|
||||||
|
'practices_count': instance.practice,
|
||||||
|
'catalog_course_id': instance.catalogCourseId,
|
||||||
|
};
|
||||||
|
|
@ -14,6 +14,8 @@ import 'package:yimaru_app/services/dio_service.dart';
|
||||||
import 'package:yimaru_app/ui/common/app_constants.dart';
|
import 'package:yimaru_app/ui/common/app_constants.dart';
|
||||||
|
|
||||||
import '../app/app.locator.dart';
|
import '../app/app.locator.dart';
|
||||||
|
import '../models/course_module.dart';
|
||||||
|
import '../models/course_unit.dart';
|
||||||
import '../models/learn_course.dart';
|
import '../models/learn_course.dart';
|
||||||
import '../models/learn_module.dart';
|
import '../models/learn_module.dart';
|
||||||
import '../models/learn_question.dart';
|
import '../models/learn_question.dart';
|
||||||
|
|
@ -729,6 +731,54 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get course units
|
||||||
|
Future<List<CourseUnit>> getCourseUnits(int id) async {
|
||||||
|
try {
|
||||||
|
List<CourseUnit> units = [];
|
||||||
|
|
||||||
|
final Response response = await _service.dio.get(
|
||||||
|
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kExamPrepUrl/$kCatalogCoursesUrl/$id/$kUnitsUrl');
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
var data = response.data;
|
||||||
|
var decodedData = data['data']['units'] as List;
|
||||||
|
units = decodedData.map(
|
||||||
|
(e) {
|
||||||
|
return CourseUnit.fromJson(e);
|
||||||
|
},
|
||||||
|
).toList();
|
||||||
|
return units;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get course modules
|
||||||
|
Future<List<CourseModule>> getCourseModules(int id) async {
|
||||||
|
try {
|
||||||
|
List<CourseModule> modules = [];
|
||||||
|
|
||||||
|
final Response response = await _service.dio.get(
|
||||||
|
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kExamPrepUrl/$kUnitsUrl/$id/$kModulesUrl');
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
var data = response.data;
|
||||||
|
var decodedData = data['data']['modules'] as List;
|
||||||
|
modules = decodedData.map(
|
||||||
|
(e) {
|
||||||
|
return CourseModule.fromJson(e);
|
||||||
|
},
|
||||||
|
).toList();
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* TO BE MODIFIED*/
|
/* TO BE MODIFIED*/
|
||||||
|
|
||||||
// Get courses
|
// Get courses
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,78 @@
|
||||||
|
import 'package:flutter_html/flutter_html.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:yimaru_app/app/app.locator.dart';
|
import 'package:yimaru_app/app/app.locator.dart';
|
||||||
import 'package:yimaru_app/models/course_progress.dart';
|
import 'package:yimaru_app/models/course_progress.dart';
|
||||||
import 'package:yimaru_app/services/api_service.dart';
|
import 'package:yimaru_app/services/api_service.dart';
|
||||||
|
|
||||||
|
import '../models/course_catalog.dart';
|
||||||
import '../models/course_detail.dart';
|
import '../models/course_detail.dart';
|
||||||
|
import '../models/course_module.dart';
|
||||||
|
import '../models/course_unit.dart';
|
||||||
|
|
||||||
class CourseService {
|
class CourseService with ListenableServiceMixin {
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
final _apiService = locator<ApiService>();
|
final _apiService = locator<ApiService>();
|
||||||
|
|
||||||
|
// Initialization
|
||||||
|
courseService() {
|
||||||
|
listenToReactiveValues([_catalogs]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Course catalogs
|
||||||
|
List<CourseCatalog> _catalogs = [];
|
||||||
|
|
||||||
|
List<CourseCatalog> get catalogs => _catalogs;
|
||||||
|
|
||||||
|
// Course units
|
||||||
|
List<CourseUnit> _units = [];
|
||||||
|
|
||||||
|
List<CourseUnit> get units => _units;
|
||||||
|
|
||||||
|
// Course modules
|
||||||
|
List<CourseModule> _modules = [];
|
||||||
|
|
||||||
|
List<CourseModule> get modules => _modules;
|
||||||
|
|
||||||
|
// Course catalogs
|
||||||
|
Future<void> getCourseCatalogs() async {
|
||||||
|
_catalogs = await _apiService.getCourseCatalogs();
|
||||||
|
_catalogs.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Course units
|
||||||
|
Future<void> getCourseUnits(int id) async {
|
||||||
|
_units = await _apiService.getCourseUnits(id);
|
||||||
|
_units.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Course modules
|
||||||
|
Future<void> getCourseUnitModule({
|
||||||
|
required int id,
|
||||||
|
required int index,
|
||||||
|
}) async {
|
||||||
|
List<CourseModule> modules = await _apiService.getCourseModules(id);
|
||||||
|
|
||||||
|
modules.sort(
|
||||||
|
(a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
final updatedUnit = _units[index].copyWith(
|
||||||
|
modules: modules,
|
||||||
|
);
|
||||||
|
|
||||||
|
_units[index] = updatedUnit;
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getCourseModules(int id) async {
|
||||||
|
_modules = await _apiService.getCourseModules(id);
|
||||||
|
_modules.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
// Get course detail
|
// Get course detail
|
||||||
Future<List<CourseDetail>> getCoursesDetail(int id) async {
|
Future<List<CourseDetail>> getCoursesDetail(int id) async {
|
||||||
final courses = await _apiService.getCourses(id);
|
final courses = await _apiService.getCourses(id);
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ class LearnService with ListenableServiceMixin {
|
||||||
final _apiService = locator<ApiService>();
|
final _apiService = locator<ApiService>();
|
||||||
|
|
||||||
// Initialization
|
// Initialization
|
||||||
LearnLessonService() {
|
learnService() {
|
||||||
listenToReactiveValues([_programs, _lessons]);
|
listenToReactiveValues([_programs, _lessons, _modules]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Learn program
|
// Learn program
|
||||||
|
|
|
||||||
47
lib/services/localization_service.dart
Normal file
47
lib/services/localization_service.dart
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
|
class LocalizationService with ListenableServiceMixin {
|
||||||
|
// Initialization
|
||||||
|
localizationService() {
|
||||||
|
listenToReactiveValues([_selectedLanguage]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Languages
|
||||||
|
Map<String, dynamic> _selectedLanguage = {
|
||||||
|
'code': 'EN',
|
||||||
|
'language': 'English'
|
||||||
|
};
|
||||||
|
|
||||||
|
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||||
|
|
||||||
|
final List<Map<String, dynamic>> _languages = [
|
||||||
|
{'code': 'አማ', 'language': 'አማርኛ'},
|
||||||
|
{'code': 'EN', 'language': 'English'},
|
||||||
|
];
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> get languages => _languages;
|
||||||
|
|
||||||
|
bool isSelectedLanguage(String title) =>
|
||||||
|
_selectedLanguage['language'] == title;
|
||||||
|
|
||||||
|
Future<void> setSelectedLanguage(
|
||||||
|
{required BuildContext context,
|
||||||
|
required Map<String, dynamic> title}) async {
|
||||||
|
_selectedLanguage = title;
|
||||||
|
|
||||||
|
if (title['code'] == 'አማ') {
|
||||||
|
await setAmharicLanguage(context);
|
||||||
|
} else {
|
||||||
|
await setAmharicLanguage(context);
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setAmharicLanguage(BuildContext context) async =>
|
||||||
|
await context.setLocale(const Locale('am'));
|
||||||
|
|
||||||
|
Future<void> setEnglishLanguage(BuildContext context) async =>
|
||||||
|
await context.setLocale(const Locale('en'));
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ String kBaseUrl = 'https://api.yimaruacademy.com';
|
||||||
|
|
||||||
String kApiUrl = 'api';
|
String kApiUrl = 'api';
|
||||||
|
|
||||||
|
String kUnitsUrl = 'units';
|
||||||
|
|
||||||
String kApiVersionUrl = 'v1';
|
String kApiVersionUrl = 'v1';
|
||||||
|
|
||||||
String kLevelsUrl = 'levels';
|
String kLevelsUrl = 'levels';
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ enum StateObjects {
|
||||||
register,
|
register,
|
||||||
verifyOtp,
|
verifyOtp,
|
||||||
resendOtp,
|
resendOtp,
|
||||||
|
courseUnits,
|
||||||
assessments,
|
assessments,
|
||||||
startupView,
|
startupView,
|
||||||
learnLessons,
|
learnLessons,
|
||||||
|
|
@ -39,6 +40,7 @@ enum StateObjects {
|
||||||
learnCourses,
|
learnCourses,
|
||||||
profileImage,
|
profileImage,
|
||||||
learnPrograms,
|
learnPrograms,
|
||||||
|
courseModules,
|
||||||
courseLessons,
|
courseLessons,
|
||||||
profileUpdate,
|
profileUpdate,
|
||||||
resetPassword,
|
resetPassword,
|
||||||
|
|
@ -49,7 +51,6 @@ enum StateObjects {
|
||||||
loginWithGoogle,
|
loginWithGoogle,
|
||||||
loadLessonVideo,
|
loadLessonVideo,
|
||||||
loadCourseVideo,
|
loadCourseVideo,
|
||||||
learnSubmodules,
|
|
||||||
requestResetCode,
|
requestResetCode,
|
||||||
profileCompletion,
|
profileCompletion,
|
||||||
learnSubscription,
|
learnSubscription,
|
||||||
|
|
|
||||||
182
lib/ui/common/translations/codegen_loader.g.dart
Normal file
182
lib/ui/common/translations/codegen_loader.g.dart
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
// DO NOT EDIT. This is code generated via package:easy_localization/generate.dart
|
||||||
|
|
||||||
|
// ignore_for_file: prefer_single_quotes, avoid_renaming_method_parameters, constant_identifier_names
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart' show AssetLoader;
|
||||||
|
|
||||||
|
class CodegenLoader extends AssetLoader{
|
||||||
|
const CodegenLoader();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>?> load(String path, Locale locale) {
|
||||||
|
return Future.value(mapLocales[locale.toString()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Map<String,dynamic> _am = {
|
||||||
|
"welcome_back": "እንኳን በደህና ተመለሱ",
|
||||||
|
"dont_have_account": "መለያ የለዎትም? ይመዝገቡ",
|
||||||
|
"email": "ኢሜይል",
|
||||||
|
"password": "የይለፍ ቃል",
|
||||||
|
"forgot_password": "የይለፍ ቃል ረሱ?",
|
||||||
|
"cont": "ቀጥል",
|
||||||
|
"register": "ይመዝገቡ",
|
||||||
|
"login_with_google": "በጉግል ይግቡ",
|
||||||
|
"or": "ወይም",
|
||||||
|
"login_with_phone": "በስልክ ቁጥር ይግቡ",
|
||||||
|
"create_account": "አዲስ መለያ ይፍጠሩ",
|
||||||
|
"already_have_account": "መለያ አለዎት?",
|
||||||
|
"login": " ይግቡ ",
|
||||||
|
"register_with_google": "በጉግል ይመዝገቡ",
|
||||||
|
"register_with_phone": "በስልክ ቁጥር ይመዝገቡ",
|
||||||
|
"enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።",
|
||||||
|
"login_with_email": "በኢሜይል ይግቡ",
|
||||||
|
"create_password": "የይለፍ ቃል ይፍጠሩ",
|
||||||
|
"confirm_password": "የይለፍ ቃል ያረጋግጡ",
|
||||||
|
"sign_up_agreement": "‘ይመዝገቡ’ የሚለውን ሲጫኑ በ‘አገልግሎት ውሎች’ እና ‘በግላዊነት ፖሊሲ’ ይስማማሉ።",
|
||||||
|
"reset_password": " የይለፍ ቃልን ይቀይሩ ",
|
||||||
|
"enter_email_reset_code": "ኢሜይልዎን ያስገቡ። የይለፍ ቃል መለወጫ ኮድ እንልክልዎታለን።",
|
||||||
|
"please_wait": "እባክዎ ይጠብቁ",
|
||||||
|
"reset_code_sent": "የመቀየሪያ ኮድ በተሳካ ሁኔታ ተልኳል",
|
||||||
|
"reset_code": " የመቀየሪያ ኮድ ",
|
||||||
|
"new_password": "አዲስ የይለፍ ቃል",
|
||||||
|
"logged_in_successfully": "በተሳካ ሁኔታ ገብተዋል",
|
||||||
|
"view_course": " ኮርሱን ይመልከቱ ",
|
||||||
|
"take_practice": " ልምምድ ያድርጉ ",
|
||||||
|
"your_current_level": "የአሁኑ ደረጃዎ",
|
||||||
|
"overall_progress": "አጠቃላይ እድገት",
|
||||||
|
"great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው ",
|
||||||
|
"view_module": "ሞጁሉን ይመልከቱ",
|
||||||
|
"progress": "እድገት",
|
||||||
|
"keep_going": " ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ",
|
||||||
|
"lessons_in_module": " በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ",
|
||||||
|
"practice": "ልምምድ",
|
||||||
|
"start": "ጀምር",
|
||||||
|
"in_progress": "በሂደት ላይ",
|
||||||
|
"hello": "ሰላም",
|
||||||
|
"ready_to_learn": " ዛሬ እንግሊዝኛ ለመማር ተዘጋጅተዋል? ",
|
||||||
|
"learn": "ይማሩ ",
|
||||||
|
"course": "ኮርስ",
|
||||||
|
"profile": " ፕሮፋይል ",
|
||||||
|
"speaking_partner": "የንግግር ጓደኛ ",
|
||||||
|
"practice_what_you_learned": "አሁን የተማሩትን እንለማመድ",
|
||||||
|
"practice_questions": "ጥቂት ጥያቄዎችን እጠይቃለሁ እና መልስ መስጠት ይችላሉ",
|
||||||
|
"start_practice": "ልምምድ ጀምር",
|
||||||
|
"almost_there": "ሊጨርሱ ተቃርበዋል",
|
||||||
|
"finish_session": "እድገትዎን ለማየት ክፍለ ጊዜውን ያጠናቅቁ",
|
||||||
|
"continue_practice": "ልምምዱን ይቀጥሉ",
|
||||||
|
"end_session": "ክፍለ ጊዜውን ያብቁ ",
|
||||||
|
"tap_start_to_listen": "ለማዳመጥ የጀምር ቁልፉን ይጫኑ",
|
||||||
|
"practice_speaking": "ንግግርን ይለማመዱ ",
|
||||||
|
"tap_microphone": "ለመናገር ማይክሮፎኑን ይጫኑ",
|
||||||
|
"reply": "እንደገና አዳምጥ",
|
||||||
|
"cancel": "ይቅር",
|
||||||
|
"you_are_speaking": "እየተናገሩ ነው",
|
||||||
|
"practice_completed": "ልምምዱ ተጠናቅቋል",
|
||||||
|
"great_improvement": "በዚህኛው በራስ መተማመንዎ ጨምሯል፤ ትልቅ መሻሻል ነው",
|
||||||
|
"practice_again": "እንደገና ይለማመዱ",
|
||||||
|
"conversation_review": "የንግግር ግምገማ ",
|
||||||
|
"result": "ውጤት",
|
||||||
|
"quick_tip": "ጠቃሚ ምክር",
|
||||||
|
"retry": "እንደገና ይሞክሩ",
|
||||||
|
"completed_a1": "እንኳን ደስ አለዎት! A1 ደረጃን አጠናቅቀዋል",
|
||||||
|
"analyzing_speaking": "የንግግር ችሎታዎን እየገመገምን ነው",
|
||||||
|
"view_profile": "ፕሮፋይሎን ይመልከቱ ",
|
||||||
|
"hi": "ሰላም",
|
||||||
|
"edit_profile": "መገለጫ ያስተካክሉ",
|
||||||
|
"first_name": "የመጀመሪያ ስም",
|
||||||
|
"last_name": "የአባት ስም",
|
||||||
|
"gender": "ፆታ",
|
||||||
|
"male": "ወንድ",
|
||||||
|
"female": "ሴት",
|
||||||
|
"phone_number": "የስልክ ቁጥር",
|
||||||
|
"country": "ሀገር",
|
||||||
|
"region": "ክልል",
|
||||||
|
"occupation": "የስራ መስክ ",
|
||||||
|
"save_changes": "ለውጦችን ያስቀምጡ"
|
||||||
|
};
|
||||||
|
static const Map<String,dynamic> _en = {
|
||||||
|
"welcome_back": "Welcome back",
|
||||||
|
"dont_have_account": "Don't have an account? Register",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"forgot_password": "Forgot password?",
|
||||||
|
"cont": "Continue",
|
||||||
|
"register": "Register",
|
||||||
|
"login_with_google": "Login with Google",
|
||||||
|
"or": "Or",
|
||||||
|
"login_with_phone": "Login with phone number",
|
||||||
|
"create_account": "Create an account",
|
||||||
|
"already_have_account": "Already have an account?",
|
||||||
|
"login": "Login",
|
||||||
|
"register_with_google": "Register with Google",
|
||||||
|
"register_with_phone": "Register with phone number",
|
||||||
|
"enter_phone_number": "Enter your phone number. We will send you a confirmation code there.",
|
||||||
|
"login_with_email": "Login with email",
|
||||||
|
"create_password": "Create password",
|
||||||
|
"confirm_password": "Confirm password",
|
||||||
|
"sign_up_agreement": "By clicking ‘Sign Up’, you agree to our ‘Terms of Service’ and ‘Privacy Policy’",
|
||||||
|
"reset_password": "Reset Password",
|
||||||
|
"enter_email_reset_code": "Enter your email. We will send you a reset code.",
|
||||||
|
"please_wait": "Please wait",
|
||||||
|
"reset_code_sent": "Reset code sent successfully",
|
||||||
|
"reset_code": "Reset code",
|
||||||
|
"new_password": "New password",
|
||||||
|
"logged_in_successfully": "Logged in successfully",
|
||||||
|
"view_course": "View course",
|
||||||
|
"take_practice": "Take practice",
|
||||||
|
"your_current_level": "Your current level",
|
||||||
|
"overall_progress": "Overall progress",
|
||||||
|
"great_work": "Keep up the great work! You're doing amazing",
|
||||||
|
"view_module": "View module",
|
||||||
|
"progress": "Progress",
|
||||||
|
"keep_going": "Let's keep going - you're more than half there",
|
||||||
|
"lessons_in_module": "Lessons in this module",
|
||||||
|
"practice": "Practice",
|
||||||
|
"start": "Start",
|
||||||
|
"in_progress": "In Progress",
|
||||||
|
"hello": "Hello",
|
||||||
|
"ready_to_learn": "Ready to keep learning English today",
|
||||||
|
"learn": "Learn",
|
||||||
|
"course": "Course",
|
||||||
|
"profile": "Profile",
|
||||||
|
"speaking_partner": "Speaking partner",
|
||||||
|
"practice_what_you_learned": "Let's practice what you just learnt",
|
||||||
|
"practice_questions": "I will ask you a few questions and you can respond",
|
||||||
|
"start_practice": "Start practice",
|
||||||
|
"almost_there": "You're almost there",
|
||||||
|
"finish_session": "Finish the session to see your progress",
|
||||||
|
"continue_practice": "Continue practice",
|
||||||
|
"end_session": "End session",
|
||||||
|
"tap_start_to_listen": "Tap the start button to listen",
|
||||||
|
"practice_speaking": "Practice speaking",
|
||||||
|
"tap_microphone": "Tap the microphone to speak",
|
||||||
|
"reply": "Reply",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"you_are_speaking": "You're speaking",
|
||||||
|
"practice_completed": "Practice completed",
|
||||||
|
"great_improvement": "You sound more confident this time, great improvement",
|
||||||
|
"practice_again": "Practice again",
|
||||||
|
"conversation_review": "Conversation review",
|
||||||
|
"result": "Result",
|
||||||
|
"quick_tip": "Quick tip",
|
||||||
|
"retry": "Retry",
|
||||||
|
"completed_a1": "Yay, you've completed A1",
|
||||||
|
"analyzing_speaking": "We're now analyzing your speaking skill",
|
||||||
|
"view_profile": "View profile",
|
||||||
|
"hi": "Hi",
|
||||||
|
"edit_profile": "Edit profile",
|
||||||
|
"first_name": "First name",
|
||||||
|
"last_name": "Last name",
|
||||||
|
"gender": "Gender",
|
||||||
|
"male": "Male",
|
||||||
|
"female": "Female",
|
||||||
|
"phone_number": "Phone number",
|
||||||
|
"country": "Country",
|
||||||
|
"region": "Region",
|
||||||
|
"occupation": "Occupation",
|
||||||
|
"save_changes": "Save changes"
|
||||||
|
};
|
||||||
|
static const Map<String, Map<String,dynamic>> mapLocales = {"am": _am, "en": _en};
|
||||||
|
}
|
||||||
85
lib/ui/common/translations/locale_keys.g.dart
Normal file
85
lib/ui/common/translations/locale_keys.g.dart
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
// DO NOT EDIT. This is code generated via package:easy_localization/generate.dart
|
||||||
|
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
|
||||||
|
abstract class LocaleKeys {
|
||||||
|
static const welcome_back = 'welcome_back';
|
||||||
|
static const dont_have_account = 'dont_have_account';
|
||||||
|
static const email = 'email';
|
||||||
|
static const password = 'password';
|
||||||
|
static const forgot_password = 'forgot_password';
|
||||||
|
static const cont = 'cont';
|
||||||
|
static const register = 'register';
|
||||||
|
static const login_with_google = 'login_with_google';
|
||||||
|
static const or = 'or';
|
||||||
|
static const login_with_phone = 'login_with_phone';
|
||||||
|
static const create_account = 'create_account';
|
||||||
|
static const already_have_account = 'already_have_account';
|
||||||
|
static const login = 'login';
|
||||||
|
static const register_with_google = 'register_with_google';
|
||||||
|
static const register_with_phone = 'register_with_phone';
|
||||||
|
static const enter_phone_number = 'enter_phone_number';
|
||||||
|
static const login_with_email = 'login_with_email';
|
||||||
|
static const create_password = 'create_password';
|
||||||
|
static const confirm_password = 'confirm_password';
|
||||||
|
static const sign_up_agreement = 'sign_up_agreement';
|
||||||
|
static const reset_password = 'reset_password';
|
||||||
|
static const enter_email_reset_code = 'enter_email_reset_code';
|
||||||
|
static const please_wait = 'please_wait';
|
||||||
|
static const reset_code_sent = 'reset_code_sent';
|
||||||
|
static const reset_code = 'reset_code';
|
||||||
|
static const new_password = 'new_password';
|
||||||
|
static const logged_in_successfully = 'logged_in_successfully';
|
||||||
|
static const view_course = 'view_course';
|
||||||
|
static const take_practice = 'take_practice';
|
||||||
|
static const your_current_level = 'your_current_level';
|
||||||
|
static const overall_progress = 'overall_progress';
|
||||||
|
static const great_work = 'great_work';
|
||||||
|
static const view_module = 'view_module';
|
||||||
|
static const progress = 'progress';
|
||||||
|
static const keep_going = 'keep_going';
|
||||||
|
static const lessons_in_module = 'lessons_in_module';
|
||||||
|
static const practice = 'practice';
|
||||||
|
static const start = 'start';
|
||||||
|
static const in_progress = 'in_progress';
|
||||||
|
static const hello = 'hello';
|
||||||
|
static const ready_to_learn = 'ready_to_learn';
|
||||||
|
static const learn = 'learn';
|
||||||
|
static const course = 'course';
|
||||||
|
static const profile = 'profile';
|
||||||
|
static const speaking_partner = 'speaking_partner';
|
||||||
|
static const practice_what_you_learned = 'practice_what_you_learned';
|
||||||
|
static const practice_questions = 'practice_questions';
|
||||||
|
static const start_practice = 'start_practice';
|
||||||
|
static const almost_there = 'almost_there';
|
||||||
|
static const finish_session = 'finish_session';
|
||||||
|
static const continue_practice = 'continue_practice';
|
||||||
|
static const end_session = 'end_session';
|
||||||
|
static const tap_start_to_listen = 'tap_start_to_listen';
|
||||||
|
static const practice_speaking = 'practice_speaking';
|
||||||
|
static const tap_microphone = 'tap_microphone';
|
||||||
|
static const reply = 'reply';
|
||||||
|
static const cancel = 'cancel';
|
||||||
|
static const you_are_speaking = 'you_are_speaking';
|
||||||
|
static const practice_completed = 'practice_completed';
|
||||||
|
static const great_improvement = 'great_improvement';
|
||||||
|
static const practice_again = 'practice_again';
|
||||||
|
static const conversation_review = 'conversation_review';
|
||||||
|
static const result = 'result';
|
||||||
|
static const quick_tip = 'quick_tip';
|
||||||
|
static const retry = 'retry';
|
||||||
|
static const completed_a1 = 'completed_a1';
|
||||||
|
static const analyzing_speaking = 'analyzing_speaking';
|
||||||
|
static const view_profile = 'view_profile';
|
||||||
|
static const hi = 'hi';
|
||||||
|
static const edit_profile = 'edit_profile';
|
||||||
|
static const first_name = 'first_name';
|
||||||
|
static const last_name = 'last_name';
|
||||||
|
static const gender = 'gender';
|
||||||
|
static const phone_number = 'phone_number';
|
||||||
|
static const country = 'country';
|
||||||
|
static const region = 'region';
|
||||||
|
static const occupation = 'occupation';
|
||||||
|
static const save_changes = 'save_changes';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -10,15 +10,25 @@ import '../../../app/app.locator.dart';
|
||||||
import '../../../app/app.router.dart';
|
import '../../../app/app.router.dart';
|
||||||
import '../../../models/assessment.dart';
|
import '../../../models/assessment.dart';
|
||||||
import '../../../services/api_service.dart';
|
import '../../../services/api_service.dart';
|
||||||
|
import '../../../services/localization_service.dart';
|
||||||
import '../../common/app_colors.dart';
|
import '../../common/app_colors.dart';
|
||||||
import '../../common/ui_helpers.dart';
|
import '../../common/ui_helpers.dart';
|
||||||
|
|
||||||
class AssessmentViewModel extends BaseViewModel {
|
class AssessmentViewModel extends ReactiveViewModel {
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
final _apiService = locator<ApiService>();
|
final _apiService = locator<ApiService>();
|
||||||
final _dialogService = locator<DialogService>();
|
final _dialogService = locator<DialogService>();
|
||||||
final _statusChecker = locator<StatusCheckerService>();
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
|
final _localizationService = locator<LocalizationService>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<ListenableServiceMixin> get listenableServices => [_localizationService];
|
||||||
|
|
||||||
|
// Languages
|
||||||
|
Map<String, dynamic> get _selectedLanguage => _localizationService.selectedLanguage;
|
||||||
|
|
||||||
|
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||||
|
|
||||||
// In-app navigation
|
// In-app navigation
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,8 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
onPop: viewModel.goBack,
|
onPop: viewModel.goBack,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,9 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
onPop: viewModel.goBack,
|
onPop: viewModel.goBack,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
|
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
onPop: viewModel.goBack,
|
onPop: viewModel.goBack,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
|
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,11 @@ class CourseViewModel extends ReactiveViewModel {
|
||||||
'description':
|
'description':
|
||||||
'Prepare for IELTS, TOEFL, or Duolingo with structured practice.'
|
'Prepare for IELTS, TOEFL, or Duolingo with structured practice.'
|
||||||
},
|
},
|
||||||
{
|
/* {
|
||||||
'title': 'Skill-Based Courses',
|
'title': 'Skill-Based Courses',
|
||||||
'description':
|
'description':
|
||||||
'Learn English for the workplace, travel, and real-life communication.'
|
'Learn English for the workplace, travel, and real-life communication.'
|
||||||
},
|
},*/
|
||||||
];
|
];
|
||||||
|
|
||||||
List<Map<String, dynamic>> get courses => _courses;
|
List<Map<String, dynamic>> get courses => _courses;
|
||||||
|
|
|
||||||
|
|
@ -101,23 +101,25 @@ class CourseCatalogView extends StackedView<CourseCatalogViewModel> {
|
||||||
|
|
||||||
Widget _buildListView(CourseCatalogViewModel viewModel) => ListView.separated(
|
Widget _buildListView(CourseCatalogViewModel viewModel) => ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: viewModel.courseCatalogs.length,
|
itemCount: viewModel.catalogs.length,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemBuilder: (context, index) => _buildTile(
|
itemBuilder: (context, index) => _buildTile(
|
||||||
courseCatalog: viewModel.courseCatalogs[index],
|
onPracticeTap: () {},
|
||||||
onCourseTap: () {},
|
catalog: viewModel.catalogs[index],
|
||||||
onPracticeTap: () {}),
|
onCourseTap: () async =>
|
||||||
|
await viewModel.navigateToCourseUnit(viewModel.catalogs[index]),
|
||||||
|
),
|
||||||
separatorBuilder: (context, index) => verticalSpaceSmall,
|
separatorBuilder: (context, index) => verticalSpaceSmall,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTile({
|
Widget _buildTile({
|
||||||
required CourseCatalog courseCatalog,
|
required CourseCatalog catalog,
|
||||||
required GestureTapCallback onCourseTap,
|
required GestureTapCallback onCourseTap,
|
||||||
required GestureTapCallback onPracticeTap,
|
required GestureTapCallback onPracticeTap,
|
||||||
}) =>
|
}) =>
|
||||||
CourseCatalogTile(
|
CourseCatalogTile(
|
||||||
|
catalog: catalog,
|
||||||
onCourseTap: onCourseTap,
|
onCourseTap: onCourseTap,
|
||||||
onPracticeTap: onPracticeTap,
|
onPracticeTap: onPracticeTap,
|
||||||
courseCatalog: courseCatalog,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,25 @@ import 'package:yimaru_app/models/course_catalog.dart';
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
import '../../../app/app.router.dart';
|
import '../../../app/app.router.dart';
|
||||||
import '../../../services/api_service.dart';
|
import '../../../services/api_service.dart';
|
||||||
|
import '../../../services/course_service.dart';
|
||||||
import '../../../services/status_checker_service.dart';
|
import '../../../services/status_checker_service.dart';
|
||||||
import '../../common/enmus.dart';
|
import '../../common/enmus.dart';
|
||||||
|
|
||||||
class CourseCatalogViewModel extends BaseViewModel {
|
class CourseCatalogViewModel extends ReactiveViewModel {
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
final _apiService = locator<ApiService>();
|
final _courseService = locator<CourseService>();
|
||||||
|
|
||||||
final _statusChecker = locator<StatusCheckerService>();
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
|
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
// Course catalogs
|
@override
|
||||||
List<CourseCatalog> _courseCatalogs = [];
|
List<ListenableServiceMixin> get listenableServices => [_courseService];
|
||||||
|
|
||||||
List<CourseCatalog> get courseCatalogs => _courseCatalogs;
|
// Learn lessons
|
||||||
|
List<CourseCatalog> get _catalogs => _courseService.catalogs;
|
||||||
|
|
||||||
|
List<CourseCatalog> get catalogs => _catalogs;
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
@ -27,19 +31,19 @@ class CourseCatalogViewModel extends BaseViewModel {
|
||||||
Future<void> navigateToCoursePractice(int id) async =>
|
Future<void> navigateToCoursePractice(int id) async =>
|
||||||
_navigationService.navigateToCoursePracticeView(id: id);
|
_navigationService.navigateToCoursePracticeView(id: id);
|
||||||
|
|
||||||
// Future<void> navigateToSubcourse(Subcategory subcategory) async =>
|
Future<void> navigateToCourseUnit(CourseCatalog catalog) async =>
|
||||||
// _navigationService.navigateToCourseView(subcategory: subcategory);
|
await _navigationService.navigateToCourseUnitView(catalog: catalog);
|
||||||
|
|
||||||
// Remote api call
|
// Remote api call
|
||||||
|
|
||||||
// Course catalogs
|
// Course catalogs
|
||||||
Future<void> getCourseCatalogs() async =>
|
Future<void> getCourseCatalogs() async =>
|
||||||
await runBusyFuture(_getSubcategories(),
|
await runBusyFuture(_getCourseCatalogs(),
|
||||||
busyObject: StateObjects.courseCatalogs);
|
busyObject: StateObjects.courseCatalogs);
|
||||||
|
|
||||||
Future<void> _getSubcategories() async {
|
Future<void> _getCourseCatalogs() async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
_courseCatalogs = await _apiService.getCourseCatalogs();
|
await _courseService.getCourseCatalogs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
|
import '../../../app/app.router.dart';
|
||||||
import '../../../models/option.dart';
|
import '../../../models/option.dart';
|
||||||
import '../../../models/assessment_question.dart';
|
import '../../../models/assessment_question.dart';
|
||||||
import '../../../services/api_service.dart';
|
import '../../../services/api_service.dart';
|
||||||
|
|
@ -153,6 +154,8 @@ class CoursePracticeQuestionViewModel extends FormViewModel {
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Remote api call
|
// Remote api call
|
||||||
|
|
||||||
// Question navigation
|
// Question navigation
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,9 @@ class QuestionLoadingScreen extends StatelessWidget {
|
||||||
Widget _buildAppBar() => LargeAppBar(
|
Widget _buildAppBar() => LargeAppBar(
|
||||||
onPop: onPop,
|
onPop: onPop,
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: false,
|
||||||
);
|
|
||||||
|
);
|
||||||
|
|
||||||
Widget _buildBody() => Expanded(child: Container());
|
Widget _buildBody() => Expanded(child: Container());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,148 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:yimaru_app/models/course_catalog.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/course_unit_tile.dart';
|
||||||
|
|
||||||
|
import '../../../models/course_unit.dart';
|
||||||
|
import '../../common/app_colors.dart';
|
||||||
|
import '../../common/enmus.dart';
|
||||||
|
import '../../common/ui_helpers.dart';
|
||||||
|
import '../../widgets/course_module_banner.dart';
|
||||||
|
import '../../widgets/custom_circular_progress_indicator.dart';
|
||||||
|
import '../../widgets/custom_elevated_button.dart';
|
||||||
|
import '../../widgets/overall_progress.dart';
|
||||||
|
import '../../widgets/small_app_bar.dart';
|
||||||
import 'course_unit_viewmodel.dart';
|
import 'course_unit_viewmodel.dart';
|
||||||
|
|
||||||
class CourseUnitView extends StackedView<CourseUnitViewModel> {
|
class CourseUnitView extends StackedView<CourseUnitViewModel> {
|
||||||
const CourseUnitView({Key? key}) : super(key: key);
|
final CourseCatalog catalog;
|
||||||
|
|
||||||
|
const CourseUnitView({Key? key, required this.catalog}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onViewModelReady(CourseUnitViewModel viewModel) async {
|
||||||
|
await viewModel.getCourseUnits(catalog.id ?? 0);
|
||||||
|
super.onViewModelReady(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
CourseUnitViewModel viewModelBuilder(BuildContext context) =>
|
||||||
|
CourseUnitViewModel();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget builder(
|
Widget builder(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
CourseUnitViewModel viewModel,
|
CourseUnitViewModel viewModel,
|
||||||
Widget? child,
|
Widget? child,
|
||||||
) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
|
||||||
body: Container(
|
|
||||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
|
||||||
child: const Center(child: Text("CourseUnitView")),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CourseUnitViewModel viewModelBuilder(
|
|
||||||
BuildContext context,
|
|
||||||
) =>
|
) =>
|
||||||
CourseUnitViewModel();
|
_buildScaffoldWrapper(viewModel);
|
||||||
|
|
||||||
|
Widget _buildScaffoldWrapper(CourseUnitViewModel viewModel) => Scaffold(
|
||||||
|
backgroundColor: kcBackgroundColor,
|
||||||
|
body: _buildScaffold(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildScaffold(CourseUnitViewModel viewModel) =>
|
||||||
|
SafeArea(child: _buildBody(viewModel));
|
||||||
|
|
||||||
|
Widget _buildBody(CourseUnitViewModel viewModel) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
child: _buildColumn(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildColumn(CourseUnitViewModel viewModel) => Column(
|
||||||
|
children: [
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildAppBar(viewModel),
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildModulesColumnWrapper(viewModel),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildAppBar(CourseUnitViewModel viewModel) => SmallAppBar(
|
||||||
|
onPop: viewModel.pop,
|
||||||
|
showBackButton: true,
|
||||||
|
title: 'Course Detail',
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildModulesColumnWrapper(CourseUnitViewModel viewModel) =>
|
||||||
|
Expanded(child: _buildLevelsColumnScrollView(viewModel));
|
||||||
|
|
||||||
|
Widget _buildLevelsColumnScrollView(CourseUnitViewModel viewModel) =>
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: _buildLevelsColumn(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildLevelsColumn(CourseUnitViewModel viewModel) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: _buildLevelsColumnChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildLevelsColumnChildren(CourseUnitViewModel viewModel) => [
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildTitle(),
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildCourseModuleBanner(),
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildOverallProgress(),
|
||||||
|
verticalSpaceTiny,
|
||||||
|
_buildContinueButton(viewModel),
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildListViewBuilder(viewModel)
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildTitle() => Text(
|
||||||
|
catalog.name ?? '',
|
||||||
|
style: style18P600,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildCourseModuleBanner() => const CourseModuleBanner();
|
||||||
|
|
||||||
|
Widget _buildOverallProgress() => const OverallProgress(
|
||||||
|
progress: 0,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
indicatorBackgroundColor: kcVeryLightGrey,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildContinueButton(CourseUnitViewModel viewModel) =>
|
||||||
|
const CustomElevatedButton(
|
||||||
|
height: 55,
|
||||||
|
borderRadius: 12,
|
||||||
|
foregroundColor: kcWhite,
|
||||||
|
text: 'Continue Course',
|
||||||
|
backgroundColor: kcPrimaryColor);
|
||||||
|
|
||||||
|
Widget _buildListViewBuilder(CourseUnitViewModel viewModel) =>
|
||||||
|
viewModel.busy(StateObjects.courseUnits)
|
||||||
|
? _buildProgressIndicator()
|
||||||
|
: _buildListView(viewModel);
|
||||||
|
|
||||||
|
Widget _buildProgressIndicator() => const Center(
|
||||||
|
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildListView(CourseUnitViewModel viewModel) => ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: viewModel.units.length,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (context, index) => _buildTile(
|
||||||
|
index: index,
|
||||||
|
unit: viewModel.units[index],
|
||||||
|
onPracticeTap: () {},
|
||||||
|
onLessonTap: () {}),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildTile({
|
||||||
|
required int index,
|
||||||
|
required CourseUnit unit,
|
||||||
|
required GestureTapCallback onLessonTap,
|
||||||
|
required GestureTapCallback onPracticeTap,
|
||||||
|
}) =>
|
||||||
|
CourseUnitTile(
|
||||||
|
unit: unit,
|
||||||
|
index: index,
|
||||||
|
onLessonTap: onLessonTap,
|
||||||
|
onPracticeTap: onPracticeTap,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,55 @@
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
import 'package:yimaru_app/models/course_module.dart';
|
||||||
|
|
||||||
class CourseUnitViewModel extends BaseViewModel {}
|
import '../../../app/app.locator.dart';
|
||||||
|
import '../../../models/course_unit.dart';
|
||||||
|
import '../../../services/api_service.dart';
|
||||||
|
import '../../../services/course_service.dart';
|
||||||
|
import '../../../services/status_checker_service.dart';
|
||||||
|
import '../../common/enmus.dart';
|
||||||
|
|
||||||
|
class CourseUnitViewModel extends ReactiveViewModel {
|
||||||
|
// Dependency injection
|
||||||
|
final _courseService = locator<CourseService>();
|
||||||
|
|
||||||
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
|
|
||||||
|
final _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<ListenableServiceMixin> get listenableServices => [_courseService];
|
||||||
|
|
||||||
|
// Course units
|
||||||
|
List<CourseUnit> get _units => _courseService.units;
|
||||||
|
|
||||||
|
List<CourseUnit> get units => _units;
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
|
// Remote api call
|
||||||
|
|
||||||
|
// Course units
|
||||||
|
Future<void> getCourseUnits(int id) async =>
|
||||||
|
await runBusyFuture(_getCourseUnits(id),
|
||||||
|
busyObject: StateObjects.courseUnits);
|
||||||
|
|
||||||
|
Future<void> _getCourseUnits(int id) async {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
await _courseService.getCourseUnits(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getCourseUnitModules(
|
||||||
|
{required int id, required int index}) async =>
|
||||||
|
await runBusyFuture(_getCourseUnitModules(id: id, index: index),
|
||||||
|
busyObject: StateObjects.courseModules);
|
||||||
|
|
||||||
|
Future<void> _getCourseUnitModules(
|
||||||
|
{required int id, required int index}) async {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
await _courseService.getCourseUnitModule(id: id, index: index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,30 @@ import 'package:stacked_services/stacked_services.dart';
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
import '../../../app/app.router.dart';
|
import '../../../app/app.router.dart';
|
||||||
import '../../../services/api_service.dart';
|
import '../../../services/api_service.dart';
|
||||||
|
import '../../../services/localization_service.dart';
|
||||||
import '../../../services/status_checker_service.dart';
|
import '../../../services/status_checker_service.dart';
|
||||||
import '../../common/enmus.dart';
|
import '../../common/enmus.dart';
|
||||||
import '../../common/ui_helpers.dart';
|
import '../../common/ui_helpers.dart';
|
||||||
|
|
||||||
class ForgetPasswordViewModel extends FormViewModel {
|
class ForgetPasswordViewModel extends ReactiveViewModel
|
||||||
|
with FormStateHelper
|
||||||
|
implements FormViewModel {
|
||||||
final _apiService = locator<ApiService>();
|
final _apiService = locator<ApiService>();
|
||||||
|
|
||||||
final _statusChecker = locator<StatusCheckerService>();
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
|
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
|
final _localizationService = locator<LocalizationService>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<ListenableServiceMixin> get listenableServices => [_localizationService];
|
||||||
|
|
||||||
|
// Languages
|
||||||
|
Map<String, dynamic> get _selectedLanguage => _localizationService.selectedLanguage;
|
||||||
|
|
||||||
|
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||||
|
|
||||||
// User data
|
// User data
|
||||||
final Map<String, dynamic> _userData = {};
|
final Map<String, dynamic> _userData = {};
|
||||||
|
|
||||||
|
|
@ -165,6 +178,10 @@ class ForgetPasswordViewModel extends FormViewModel {
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
|
Future<void> navigateToLanguage() async =>
|
||||||
|
await _navigationService.navigateToLanguageView();
|
||||||
|
|
||||||
|
|
||||||
Future<void> replaceWithLogin() async =>
|
Future<void> replaceWithLogin() async =>
|
||||||
await _navigationService.clearStackAndShow(Routes.loginView);
|
await _navigationService.clearStackAndShow(Routes.loginView);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,11 @@ class RequestCodeScreen extends ViewModelWidget<ForgetPasswordViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onPop: () => _inAppPop(viewModel),
|
onPop: () => _inAppPop(viewModel),
|
||||||
);
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(
|
Widget _buildExpandedBody(
|
||||||
{required BuildContext context,
|
{required BuildContext context,
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,11 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onPop: () => _inAppPop(viewModel),
|
onPop: () => _inAppPop(viewModel),
|
||||||
);
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(ForgetPasswordViewModel viewModel) =>
|
Widget _buildExpandedBody(ForgetPasswordViewModel viewModel) =>
|
||||||
Expanded(child: _buildColumnScroller(viewModel));
|
Expanded(child: _buildColumnScroller(viewModel));
|
||||||
|
|
|
||||||
|
|
@ -22,48 +22,72 @@ class LanguageView extends StackedView<LanguageViewModel> {
|
||||||
LanguageViewModel viewModel,
|
LanguageViewModel viewModel,
|
||||||
Widget? child,
|
Widget? child,
|
||||||
) =>
|
) =>
|
||||||
_buildScaffoldWrapper(viewModel);
|
_buildScaffoldWrapper(context: context, viewModel: viewModel);
|
||||||
|
|
||||||
Widget _buildScaffoldWrapper(LanguageViewModel viewModel) => Scaffold(
|
Widget _buildScaffoldWrapper(
|
||||||
|
{required BuildContext context,
|
||||||
|
required LanguageViewModel viewModel}) =>
|
||||||
|
Scaffold(
|
||||||
backgroundColor: kcBackgroundColor,
|
backgroundColor: kcBackgroundColor,
|
||||||
body: _buildScaffold(viewModel),
|
body: _buildScaffold(context: context, viewModel: viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildScaffold(LanguageViewModel viewModel) =>
|
Widget _buildScaffold(
|
||||||
SafeArea(child: _buildBodyWrapper(viewModel));
|
{required BuildContext context,
|
||||||
|
required LanguageViewModel viewModel}) =>
|
||||||
|
SafeArea(
|
||||||
|
child: _buildBodyWrapper(context: context, viewModel: viewModel));
|
||||||
|
|
||||||
Widget _buildBodyWrapper(LanguageViewModel viewModel) => Column(
|
Widget _buildBodyWrapper(
|
||||||
|
{required BuildContext context,
|
||||||
|
required LanguageViewModel viewModel}) =>
|
||||||
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: _buildBodyChildren(viewModel),
|
children: _buildBodyChildren(context: context, viewModel: viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
List<Widget> _buildBodyChildren(LanguageViewModel viewModel) => [
|
List<Widget> _buildBodyChildren(
|
||||||
|
{required BuildContext context,
|
||||||
|
required LanguageViewModel viewModel}) =>
|
||||||
|
[
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildAppBarWrapper(viewModel),
|
_buildAppBarWrapper(viewModel),
|
||||||
_buildExpandedBody(viewModel)
|
_buildExpandedBody(context: context, viewModel: viewModel)
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildExpandedBody(LanguageViewModel viewModel) =>
|
Widget _buildExpandedBody(
|
||||||
Expanded(child: _buildColumnWrapper(viewModel));
|
{required BuildContext context,
|
||||||
|
required LanguageViewModel viewModel}) =>
|
||||||
|
Expanded(
|
||||||
|
child: _buildColumnWrapper(context: context, viewModel: viewModel));
|
||||||
|
|
||||||
Widget _buildColumnWrapper(LanguageViewModel viewModel) => Padding(
|
Widget _buildColumnWrapper(
|
||||||
|
{required BuildContext context,
|
||||||
|
required LanguageViewModel viewModel}) =>
|
||||||
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
child: _buildColumn(viewModel),
|
child: _buildColumn(context: context, viewModel: viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildColumn(LanguageViewModel viewModel) => Column(
|
Widget _buildColumn(
|
||||||
|
{required BuildContext context,
|
||||||
|
required LanguageViewModel viewModel}) =>
|
||||||
|
Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: _buildColumnChildren(viewModel),
|
children: _buildColumnChildren(context: context, viewModel: viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
List<Widget> _buildColumnChildren(LanguageViewModel viewModel) => [
|
List<Widget> _buildColumnChildren(
|
||||||
|
{required BuildContext context,
|
||||||
|
required LanguageViewModel viewModel}) =>
|
||||||
|
[
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubtitle(),
|
_buildSubtitle(),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildLanguages(viewModel)
|
_buildLanguages(context: context, viewModel: viewModel)
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBarWrapper(LanguageViewModel viewModel) => Padding(
|
Widget _buildAppBarWrapper(LanguageViewModel viewModel) => Padding(
|
||||||
|
|
@ -87,16 +111,19 @@ class LanguageView extends StackedView<LanguageViewModel> {
|
||||||
style: style14MG400,
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLanguages(LanguageViewModel viewModel) => ListView.builder(
|
Widget _buildLanguages(
|
||||||
|
{required BuildContext context,
|
||||||
|
required LanguageViewModel viewModel}) =>
|
||||||
|
ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: viewModel.languages.length,
|
itemCount: viewModel.languages.length,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemBuilder: (context, index) => _buildLanguage(
|
itemBuilder: (context, index) => _buildLanguage(
|
||||||
title: viewModel.languages[index]['language'],
|
title: viewModel.languages[index]['language'],
|
||||||
|
onTap: () => viewModel.setSelectedLanguage(
|
||||||
|
context: context, title: viewModel.languages[index]),
|
||||||
selected: viewModel
|
selected: viewModel
|
||||||
.isSelectedLanguage(viewModel.languages[index]['language']),
|
.isSelectedLanguage(viewModel.languages[index]['language']),
|
||||||
onTap: () =>
|
|
||||||
viewModel.setSelectedLanguage(viewModel.languages[index]),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,38 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
import 'package:yimaru_app/services/localization_service.dart';
|
||||||
|
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
|
|
||||||
class LanguageViewModel extends BaseViewModel {
|
class LanguageViewModel extends ReactiveViewModel {
|
||||||
|
// Dependency injection
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
|
final _localizationService = locator<LocalizationService>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<ListenableServiceMixin> get listenableServices => [_localizationService];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Languages
|
// Languages
|
||||||
Map<String, dynamic> _selectedLanguage = {
|
List<Map<String, dynamic>> get _languages => _localizationService.languages;
|
||||||
'code': 'EN',
|
|
||||||
'language': 'English'
|
|
||||||
};
|
|
||||||
|
|
||||||
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
|
||||||
|
|
||||||
final List<Map<String, dynamic>> _languages = [
|
|
||||||
{'code': 'አማ', 'language': 'አማርኛ'},
|
|
||||||
{'code': 'EN', 'language': 'English'},
|
|
||||||
];
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> get languages => _languages;
|
List<Map<String, dynamic>> get languages => _languages;
|
||||||
|
|
||||||
|
Map<String, dynamic> get _selectedLanguage => _localizationService.selectedLanguage;
|
||||||
|
|
||||||
|
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||||
// Languages
|
// Languages
|
||||||
bool isSelectedLanguage(String title) =>
|
bool isSelectedLanguage(String title) =>
|
||||||
_selectedLanguage['language'] == title;
|
_localizationService.isSelectedLanguage(title);
|
||||||
|
|
||||||
void setSelectedLanguage(Map<String, dynamic> title) {
|
Future<void> setSelectedLanguage(
|
||||||
_selectedLanguage = title;
|
{required BuildContext context,
|
||||||
rebuildUi();
|
required Map<String, dynamic> title}) async =>
|
||||||
}
|
await _localizationService.setSelectedLanguage(
|
||||||
|
context: context, title: title);
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:yimaru_app/models/learn_module.dart';
|
import 'package:yimaru_app/models/learn_module.dart';
|
||||||
import 'package:yimaru_app/ui/widgets/learn_module_tile.dart';
|
import 'package:yimaru_app/ui/widgets/learn_module_tile.dart';
|
||||||
import 'package:yimaru_app/ui/widgets/overall_learn_progress.dart';
|
import 'package:yimaru_app/ui/widgets/overall_progress.dart';
|
||||||
|
|
||||||
import '../../../models/learn_course.dart';
|
import '../../../models/learn_course.dart';
|
||||||
import '../../common/app_colors.dart';
|
import '../../common/app_colors.dart';
|
||||||
|
|
@ -96,7 +96,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
||||||
style: style14P400,
|
style: style14P400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildOverallProgress() => OverallLearnProgress(
|
Widget _buildOverallProgress() => OverallProgress(
|
||||||
indicatorBackgroundColor: kcWhite,
|
indicatorBackgroundColor: kcWhite,
|
||||||
progress: course.access?.progressPercent ?? 0,
|
progress: course.access?.progressPercent ?? 0,
|
||||||
backgroundColor: kcPrimaryColor.withOpacity(0.1),
|
backgroundColor: kcPrimaryColor.withOpacity(0.1),
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import 'package:yimaru_app/models/user.dart';
|
||||||
import '../../../services/api_service.dart';
|
import '../../../services/api_service.dart';
|
||||||
import '../../../services/authentication_service.dart';
|
import '../../../services/authentication_service.dart';
|
||||||
import '../../../services/google_auth_service.dart';
|
import '../../../services/google_auth_service.dart';
|
||||||
|
import '../../../services/localization_service.dart';
|
||||||
import '../../../services/status_checker_service.dart';
|
import '../../../services/status_checker_service.dart';
|
||||||
import '../../common/enmus.dart';
|
import '../../common/enmus.dart';
|
||||||
import '../../common/ui_helpers.dart';
|
import '../../common/ui_helpers.dart';
|
||||||
|
|
@ -24,16 +25,23 @@ class LoginViewModel extends ReactiveViewModel
|
||||||
|
|
||||||
final _googleAuthService = locator<GoogleAuthService>();
|
final _googleAuthService = locator<GoogleAuthService>();
|
||||||
|
|
||||||
|
final _localizationService = locator<LocalizationService>();
|
||||||
|
|
||||||
final _authenticationService = locator<AuthenticationService>();
|
final _authenticationService = locator<AuthenticationService>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<ListenableServiceMixin> get listenableServices => [_googleAuthService];
|
List<ListenableServiceMixin> get listenableServices => [_googleAuthService,_localizationService];
|
||||||
|
|
||||||
// Google user
|
// Google user
|
||||||
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
|
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
|
||||||
|
|
||||||
GoogleSignInAccount? get googleUser => _googleUser;
|
GoogleSignInAccount? get googleUser => _googleUser;
|
||||||
|
|
||||||
|
// Languages
|
||||||
|
Map<String, dynamic> get _selectedLanguage => _localizationService.selectedLanguage;
|
||||||
|
|
||||||
|
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||||
|
|
||||||
// In-app navigation
|
// In-app navigation
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
|
|
||||||
|
|
@ -158,9 +166,14 @@ class LoginViewModel extends ReactiveViewModel
|
||||||
Future<void> navigateToRegister() async =>
|
Future<void> navigateToRegister() async =>
|
||||||
await _navigationService.navigateToRegisterView();
|
await _navigationService.navigateToRegisterView();
|
||||||
|
|
||||||
|
Future<void> navigateToLanguage() async =>
|
||||||
|
await _navigationService.navigateToLanguageView();
|
||||||
|
|
||||||
Future<void> navigateToForgetPassword() async =>
|
Future<void> navigateToForgetPassword() async =>
|
||||||
await _navigationService.navigateToForgetPasswordView();
|
await _navigationService.navigateToForgetPasswordView();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Future<void> replaceWithStartUp() async =>
|
Future<void> replaceWithStartUp() async =>
|
||||||
await _navigationService.clearStackAndShow(Routes.startupView);
|
await _navigationService.clearStackAndShow(Routes.startupView);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,10 +80,14 @@ class LoginOtpScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
_buildExpandedBody(context: context, viewModel: viewModel)
|
_buildExpandedBody(context: context, viewModel: viewModel)
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(LoginViewModel viewModel) => const LargeAppBar(
|
Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar(
|
||||||
showBackButton: false,
|
showBackButton: false,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
);
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(
|
Widget _buildExpandedBody(
|
||||||
{required BuildContext context, required LoginViewModel viewModel}) =>
|
{required BuildContext context, required LoginViewModel viewModel}) =>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
|
||||||
import 'package:yimaru_app/ui/views/login/login_view.form.dart';
|
import 'package:yimaru_app/ui/views/login/login_view.form.dart';
|
||||||
import 'package:yimaru_app/ui/widgets/obscure_password.dart';
|
import 'package:yimaru_app/ui/widgets/obscure_password.dart';
|
||||||
|
|
||||||
|
|
@ -76,9 +78,11 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
_buildExpandedBody(context: context, viewModel: viewModel)
|
_buildExpandedBody(context: context, viewModel: viewModel)
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(LoginViewModel viewModel) => const LargeAppBar(
|
Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar(
|
||||||
showBackButton: false,
|
showBackButton: false,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(
|
Widget _buildExpandedBody(
|
||||||
|
|
@ -140,7 +144,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
'Welcome Back',
|
LocaleKeys.welcome_back.tr(),
|
||||||
style: style25DG600,
|
style: style25DG600,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -150,10 +154,10 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
|
|
||||||
Widget _buildEmailFormField(LoginViewModel viewModel) => TextFormField(
|
Widget _buildEmailFormField(LoginViewModel viewModel) => TextFormField(
|
||||||
controller: emailController,
|
controller: emailController,
|
||||||
keyboardType: TextInputType.emailAddress,
|
|
||||||
onTap: viewModel.setEmailFocus,
|
onTap: viewModel.setEmailFocus,
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
decoration: inputDecoration(
|
decoration: inputDecoration(
|
||||||
hint: 'Email',
|
hint: LocaleKeys.email.tr(),
|
||||||
focus: viewModel.focusEmail,
|
focus: viewModel.focusEmail,
|
||||||
filled: emailController.text.isNotEmpty),
|
filled: emailController.text.isNotEmpty),
|
||||||
);
|
);
|
||||||
|
|
@ -173,7 +177,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
onTap: viewModel.setPasswordFocus,
|
onTap: viewModel.setPasswordFocus,
|
||||||
obscureText: viewModel.obscurePassword,
|
obscureText: viewModel.obscurePassword,
|
||||||
decoration: inputDecoration(
|
decoration: inputDecoration(
|
||||||
hint: 'Password',
|
hint: LocaleKeys.password.tr(),
|
||||||
focus: viewModel.focusPassword,
|
focus: viewModel.focusPassword,
|
||||||
suffix: _buildObscureButton(viewModel),
|
suffix: _buildObscureButton(viewModel),
|
||||||
filled: passwordController.text.isNotEmpty),
|
filled: passwordController.text.isNotEmpty),
|
||||||
|
|
@ -207,7 +211,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildForgetPasswordText() => Text(
|
Widget _buildForgetPasswordText() => Text(
|
||||||
'Forgot Password?',
|
LocaleKeys.forgot_password.tr(),
|
||||||
style: style14P400,
|
style: style14P400,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -227,9 +231,9 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
Widget _buildContinueButton(LoginViewModel viewModel) => CustomElevatedButton(
|
Widget _buildContinueButton(LoginViewModel viewModel) => CustomElevatedButton(
|
||||||
height: 55,
|
height: 55,
|
||||||
safe: false,
|
safe: false,
|
||||||
text: 'Continue',
|
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
foregroundColor: kcWhite,
|
foregroundColor: kcWhite,
|
||||||
|
text: LocaleKeys.cont.tr(),
|
||||||
onTap: emailController.text.isNotEmpty &&
|
onTap: emailController.text.isNotEmpty &&
|
||||||
passwordController.text.isNotEmpty
|
passwordController.text.isNotEmpty
|
||||||
? () async => await _login(viewModel)
|
? () async => await _login(viewModel)
|
||||||
|
|
@ -245,9 +249,9 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
height: 55,
|
height: 55,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
backgroundColor: kcWhite,
|
backgroundColor: kcWhite,
|
||||||
text: 'Login with Google',
|
|
||||||
borderColor: kcPrimaryColor,
|
borderColor: kcPrimaryColor,
|
||||||
foregroundColor: kcPrimaryColor,
|
foregroundColor: kcPrimaryColor,
|
||||||
|
text: LocaleKeys.login_with_google.tr(),
|
||||||
leadingImage: 'assets/icons/google.png',
|
leadingImage: 'assets/icons/google.png',
|
||||||
onTap: () async => await viewModel.signInWithGoogle(),
|
onTap: () async => await viewModel.signInWithGoogle(),
|
||||||
);
|
);
|
||||||
|
|
@ -263,7 +267,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
borderColor: kcPrimaryColor,
|
borderColor: kcPrimaryColor,
|
||||||
onTap: () => viewModel.goTo(1),
|
onTap: () => viewModel.goTo(1),
|
||||||
foregroundColor: kcPrimaryColor,
|
foregroundColor: kcPrimaryColor,
|
||||||
text: 'Login with Phone Number',
|
text: LocaleKeys.login_with_phone.tr()
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLoginWithEmailState(LoginViewModel viewModel) =>
|
Widget _buildLoginWithEmailState(LoginViewModel viewModel) =>
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,11 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onPop: () => viewModel.goTo(0),
|
onPop: () => viewModel.goTo(0),
|
||||||
);
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(
|
Widget _buildExpandedBody(
|
||||||
{required BuildContext context, required LoginViewModel viewModel}) =>
|
{required BuildContext context, required LoginViewModel viewModel}) =>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:stacked_services/stacked_services.dart';
|
||||||
import 'package:yimaru_app/app/app.router.dart';
|
import 'package:yimaru_app/app/app.router.dart';
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
import '../../../services/google_auth_service.dart';
|
import '../../../services/google_auth_service.dart';
|
||||||
|
import '../../../services/localization_service.dart';
|
||||||
|
|
||||||
class OnboardingViewModel extends ReactiveViewModel
|
class OnboardingViewModel extends ReactiveViewModel
|
||||||
with FormStateHelper
|
with FormStateHelper
|
||||||
|
|
@ -14,14 +15,24 @@ class OnboardingViewModel extends ReactiveViewModel
|
||||||
|
|
||||||
final _googleAuthService = locator<GoogleAuthService>();
|
final _googleAuthService = locator<GoogleAuthService>();
|
||||||
|
|
||||||
|
final _localizationService = locator<LocalizationService>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<ListenableServiceMixin> get listenableServices => [_googleAuthService];
|
List<ListenableServiceMixin> get listenableServices => [_googleAuthService,_localizationService];
|
||||||
|
|
||||||
// Google user
|
// Google user
|
||||||
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
|
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
|
||||||
|
|
||||||
GoogleSignInAccount? get googleUser => _googleUser;
|
GoogleSignInAccount? get googleUser => _googleUser;
|
||||||
|
|
||||||
|
|
||||||
|
// Languages
|
||||||
|
Map<String, dynamic> get _selectedLanguage => _localizationService.selectedLanguage;
|
||||||
|
|
||||||
|
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
|
|
||||||
|
|
@ -207,20 +218,6 @@ class OnboardingViewModel extends ReactiveViewModel
|
||||||
|
|
||||||
List<String> get topics => _topics;
|
List<String> get topics => _topics;
|
||||||
|
|
||||||
// Languages
|
|
||||||
final List<Map<String, dynamic>> _languages = [
|
|
||||||
{'code': 'አማ', 'language': 'አማርኛ'},
|
|
||||||
{'code': 'EN', 'language': 'English'},
|
|
||||||
];
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> get languages => _languages;
|
|
||||||
|
|
||||||
Map<String, dynamic> _selectedLanguage = {
|
|
||||||
'code': 'EN',
|
|
||||||
'language': 'English'
|
|
||||||
};
|
|
||||||
|
|
||||||
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
|
||||||
|
|
||||||
// User data
|
// User data
|
||||||
final Map<String, dynamic> _userData = {};
|
final Map<String, dynamic> _userData = {};
|
||||||
|
|
@ -548,15 +545,6 @@ class OnboardingViewModel extends ReactiveViewModel
|
||||||
|
|
||||||
bool isSelectedTopic(String value) => _selectedTopic == value;
|
bool isSelectedTopic(String value) => _selectedTopic == value;
|
||||||
|
|
||||||
// Language
|
|
||||||
void setSelectedLanguage(Map<String, dynamic> value) {
|
|
||||||
_selectedLanguage = value;
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isSelectedLanguage(String value) =>
|
|
||||||
_selectedLanguage['language'] == value;
|
|
||||||
|
|
||||||
// Add user data
|
// Add user data
|
||||||
void addUserData(Map<String, dynamic> data) {
|
void addUserData(Map<String, dynamic> data) {
|
||||||
_userData.addAll(data);
|
_userData.addAll(data);
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,9 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onPop: () => _pop(viewModel),
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,9 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onPop: () => _pop(viewModel),
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,8 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onPop: () => _pop(viewModel),
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,8 @@ class EducationalBackgroundFormScreen
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onPop: () => _pop(viewModel),
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => const Text(
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,8 @@ class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||||
showBackButton: false,
|
showBackButton: false,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => const Text(
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,9 @@ class GenderFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onPop: () => _pop(viewModel),
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,8 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onPop: () => _pop(viewModel),
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,9 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onPop: () => _pop(viewModel),
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle(OnboardingViewModel viewModel) => Text.rich(
|
Widget _buildTitle(OnboardingViewModel viewModel) => Text.rich(
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,9 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onPop: () => _pop(viewModel),
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,9 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
onPop: () => _pop(viewModel),
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
|
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
import '../../../models/user.dart';
|
import '../../../models/user.dart';
|
||||||
import '../../../services/google_auth_service.dart';
|
import '../../../services/google_auth_service.dart';
|
||||||
|
import '../../../services/localization_service.dart';
|
||||||
import '../../../services/status_checker_service.dart';
|
import '../../../services/status_checker_service.dart';
|
||||||
|
|
||||||
class RegisterViewModel extends ReactiveViewModel
|
class RegisterViewModel extends ReactiveViewModel
|
||||||
|
|
@ -24,16 +25,24 @@ class RegisterViewModel extends ReactiveViewModel
|
||||||
|
|
||||||
final _googleAuthService = locator<GoogleAuthService>();
|
final _googleAuthService = locator<GoogleAuthService>();
|
||||||
|
|
||||||
|
final _localizationService = locator<LocalizationService>();
|
||||||
|
|
||||||
final _authenticationService = locator<AuthenticationService>();
|
final _authenticationService = locator<AuthenticationService>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<ListenableServiceMixin> get listenableServices => [_googleAuthService];
|
List<ListenableServiceMixin> get listenableServices => [_googleAuthService,_localizationService];
|
||||||
|
|
||||||
// Google user
|
// Google user
|
||||||
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
|
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
|
||||||
|
|
||||||
GoogleSignInAccount? get googleUser => _googleUser;
|
GoogleSignInAccount? get googleUser => _googleUser;
|
||||||
|
|
||||||
|
// Languages
|
||||||
|
Map<String, dynamic> get _selectedLanguage => _localizationService.selectedLanguage;
|
||||||
|
|
||||||
|
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||||
|
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
|
|
||||||
|
|
@ -260,14 +269,19 @@ class RegisterViewModel extends ReactiveViewModel
|
||||||
|
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
Future<void> navigateToTermsAndConditions() async =>
|
Future<void> replaceToLogin() async =>
|
||||||
await _navigationService.navigateToTermsAndConditionsView();
|
await _navigationService.replaceWithLoginView();
|
||||||
|
|
||||||
|
Future<void> navigateToLanguage() async =>
|
||||||
|
await _navigationService.navigateToLanguageView();
|
||||||
|
|
||||||
|
|
||||||
Future<void> navigateToPrivacyPolicy() async =>
|
Future<void> navigateToPrivacyPolicy() async =>
|
||||||
await _navigationService.navigateToPrivacyPolicyView();
|
await _navigationService.navigateToPrivacyPolicyView();
|
||||||
|
|
||||||
Future<void> replaceToLogin() async =>
|
|
||||||
await _navigationService.replaceWithLoginView();
|
Future<void> navigateToTermsAndConditions() async =>
|
||||||
|
await _navigationService.navigateToTermsAndConditionsView();
|
||||||
|
|
||||||
Future<void> replaceWithStartUp() async =>
|
Future<void> replaceWithStartUp() async =>
|
||||||
await _navigationService.clearStackAndShow(Routes.startupView);
|
await _navigationService.clearStackAndShow(Routes.startupView);
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,11 @@ class CreatePasswordScreen extends ViewModelWidget<RegisterViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
onPop: viewModel.goBack,
|
onPop: viewModel.goBack,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
);
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(RegisterViewModel viewModel) =>
|
Widget _buildExpandedBody(RegisterViewModel viewModel) =>
|
||||||
Expanded(child: _buildColumnScroller(viewModel));
|
Expanded(child: _buildColumnScroller(viewModel));
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,11 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
onPop: viewModel.goBack,
|
onPop: viewModel.goBack,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
);
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(
|
Widget _buildExpandedBody(
|
||||||
{required BuildContext context,
|
{required BuildContext context,
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,11 @@ class RegisterWithPhoneNumberScreen extends ViewModelWidget<RegisterViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
onPop: viewModel.goBack,
|
onPop: viewModel.goBack,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
);
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
|
||||||
|
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(
|
Widget _buildExpandedBody(
|
||||||
{required BuildContext context,
|
{required BuildContext context,
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,8 @@ class RegistrationOtpScreen extends ViewModelWidget<RegisterViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
onPop: viewModel.goBack,
|
onPop: viewModel.goBack,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
language: viewModel.selectedLanguage['code'],
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(
|
Widget _buildExpandedBody(
|
||||||
|
|
|
||||||
|
|
@ -47,16 +47,20 @@ class StartupViewModel extends ReactiveViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
|
|
||||||
|
Future<void> replaceWithHome() async =>
|
||||||
|
await _navigationService.replaceWithHomeView();
|
||||||
|
|
||||||
Future<void> replaceWithFailure() async =>
|
Future<void> replaceWithFailure() async =>
|
||||||
await _navigationService.replaceWithFailureView(
|
await _navigationService.replaceWithFailureView(
|
||||||
label: 'Check you internet connection',
|
label: 'Check you internet connection',
|
||||||
onTap: () async => await _getProfileStatus());
|
onTap: () async => await _getProfileStatus());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Future<void> replaceWithOnboarding() async =>
|
Future<void> replaceWithOnboarding() async =>
|
||||||
await _navigationService.replaceWithOnboardingView();
|
await _navigationService.replaceWithOnboardingView();
|
||||||
|
|
||||||
Future<void> replaceWithHome() async =>
|
|
||||||
await _navigationService.replaceWithHomeView();
|
|
||||||
|
|
||||||
// Remote api calls
|
// Remote api calls
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class AssessmentLoadingScreen extends StatelessWidget {
|
||||||
Widget _buildAppBar() => LargeAppBar(
|
Widget _buildAppBar() => LargeAppBar(
|
||||||
onPop: onPop,
|
onPop: onPop,
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildBody() => Expanded(child: Container());
|
Widget _buildBody() => Expanded(child: Container());
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import '../common/ui_helpers.dart';
|
||||||
import 'custom_elevated_button.dart';
|
import 'custom_elevated_button.dart';
|
||||||
|
|
||||||
class CourseCatalogTile extends StatelessWidget {
|
class CourseCatalogTile extends StatelessWidget {
|
||||||
final CourseCatalog courseCatalog;
|
final CourseCatalog catalog;
|
||||||
final GestureTapCallback? onCourseTap;
|
final GestureTapCallback? onCourseTap;
|
||||||
final GestureTapCallback? onPracticeTap;
|
final GestureTapCallback? onPracticeTap;
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ class CourseCatalogTile extends StatelessWidget {
|
||||||
super.key,
|
super.key,
|
||||||
this.onCourseTap,
|
this.onCourseTap,
|
||||||
this.onPracticeTap,
|
this.onPracticeTap,
|
||||||
required this.courseCatalog,
|
required this.catalog,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -57,7 +57,7 @@ class CourseCatalogTile extends StatelessWidget {
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
courseCatalog.name ?? '',
|
catalog.name ?? '',
|
||||||
style: style16P600,
|
style: style16P600,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@ import 'package:yimaru_app/ui/common/app_colors.dart';
|
||||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||||
import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
||||||
|
|
||||||
class CourseTopicTile extends StatelessWidget {
|
class CourseModuleTileSmall extends StatelessWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final ProgressStatuses status;
|
final ProgressStatuses status;
|
||||||
|
|
||||||
const CourseTopicTile({super.key, required this.title, required this.status});
|
const CourseModuleTileSmall(
|
||||||
|
{super.key, required this.title, required this.status});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => _buildTile();
|
Widget build(BuildContext context) => _buildTile();
|
||||||
|
|
@ -27,7 +28,9 @@ class CourseTopicTile extends StatelessWidget {
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
title,
|
title,
|
||||||
style: style16DG600,
|
maxLines: 1,
|
||||||
|
softWrap: false,
|
||||||
|
style: style14DG600,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLeadingWrapper() => CircleAvatar(
|
Widget _buildLeadingWrapper() => CircleAvatar(
|
||||||
267
lib/ui/widgets/course_unit_tile.dart
Normal file
267
lib/ui/widgets/course_unit_tile.dart
Normal file
|
|
@ -0,0 +1,267 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:yimaru_app/models/course_unit.dart';
|
||||||
|
import 'package:yimaru_app/models/submodule.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/course_module_tile_small.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/finish_practice_sheet.dart';
|
||||||
|
|
||||||
|
import '../common/app_colors.dart';
|
||||||
|
import '../common/ui_helpers.dart';
|
||||||
|
import '../views/course_unit/course_unit_viewmodel.dart';
|
||||||
|
import 'custom_circular_progress_indicator.dart';
|
||||||
|
import 'custom_elevated_button.dart';
|
||||||
|
|
||||||
|
class CourseUnitTile extends ViewModelWidget<CourseUnitViewModel> {
|
||||||
|
final int index;
|
||||||
|
final CourseUnit unit;
|
||||||
|
final GestureTapCallback? onLessonTap;
|
||||||
|
final GestureTapCallback? onPracticeTap;
|
||||||
|
|
||||||
|
const CourseUnitTile({
|
||||||
|
super.key,
|
||||||
|
this.onLessonTap,
|
||||||
|
this.onPracticeTap,
|
||||||
|
required this.unit,
|
||||||
|
required this.index,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> _getCourseModules({
|
||||||
|
required bool expanded,
|
||||||
|
required CourseUnitViewModel viewModel,
|
||||||
|
}) async {
|
||||||
|
if (!expanded) return;
|
||||||
|
|
||||||
|
// Prevent duplicate API calls
|
||||||
|
if ((unit.modules?.isNotEmpty ?? false)) return;
|
||||||
|
|
||||||
|
await viewModel.getCourseUnitModules(index: index, id: unit.id ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showSheet(
|
||||||
|
{required BuildContext context,
|
||||||
|
required CourseUnitViewModel viewModel}) async =>
|
||||||
|
await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
backgroundColor: kcTransparent,
|
||||||
|
builder: (_) => _buildSheet(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, CourseUnitViewModel viewModel) =>
|
||||||
|
_buildExpansionTileCard(context: context, viewModel: viewModel);
|
||||||
|
|
||||||
|
Widget _buildExpansionTileCard(
|
||||||
|
{required BuildContext context,
|
||||||
|
required CourseUnitViewModel viewModel}) =>
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(color: kcVeryLightGrey),
|
||||||
|
),
|
||||||
|
child: _buildTileStack(context: context, viewModel: viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildTileStack(
|
||||||
|
{required BuildContext context,
|
||||||
|
required CourseUnitViewModel viewModel}) =>
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
_buildExpansionTile(context: context, viewModel: viewModel),
|
||||||
|
// _buildContainerShaderState()
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildExpansionTile(
|
||||||
|
{required BuildContext context,
|
||||||
|
required CourseUnitViewModel viewModel}) =>
|
||||||
|
ExpansionTile(
|
||||||
|
enabled: true,
|
||||||
|
title: _buildTitle(),
|
||||||
|
textColor: kcDarkGrey,
|
||||||
|
showTrailingIcon: true,
|
||||||
|
initiallyExpanded: false,
|
||||||
|
subtitle: _buildSubtitle(),
|
||||||
|
// key: Key(unit.id.toString()),
|
||||||
|
collapsedIconColor: kcDarkGrey,
|
||||||
|
collapsedTextColor: kcDarkGrey,
|
||||||
|
backgroundColor: kcBackgroundColor,
|
||||||
|
shape: Border.all(color: kcTransparent),
|
||||||
|
expandedAlignment: Alignment.centerLeft,
|
||||||
|
collapsedBackgroundColor: kcBackgroundColor,
|
||||||
|
|
||||||
|
controlAffinity: ListTileControlAffinity.trailing,
|
||||||
|
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
onExpansionChanged: (bool expanded) async => await _getCourseModules(
|
||||||
|
expanded: expanded,
|
||||||
|
viewModel: viewModel,
|
||||||
|
),
|
||||||
|
// enabled: status != ProgressStatuses.pending,
|
||||||
|
// showTrailingIcon: status != ProgressStatuses.pending ? true : false,
|
||||||
|
//initiallyExpanded: status == ProgressStatuses.started ? true : false,
|
||||||
|
children:
|
||||||
|
_buildExpansionTileChildren(context: context, viewModel: viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildTitle() => Text(
|
||||||
|
unit.name ?? '',
|
||||||
|
maxLines: 1,
|
||||||
|
softWrap: false,
|
||||||
|
style: style16P600,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSubtitle() => Text(
|
||||||
|
'0% completed',
|
||||||
|
style: style14DG500,
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildExpansionTileChildren(
|
||||||
|
{required BuildContext context,
|
||||||
|
required CourseUnitViewModel viewModel}) =>
|
||||||
|
[_buildExpansionTileItem(context: context, viewModel: viewModel)];
|
||||||
|
|
||||||
|
Widget _buildExpansionTileItem(
|
||||||
|
{required BuildContext context,
|
||||||
|
required CourseUnitViewModel viewModel}) =>
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: _buildExpansionTileItemChildren(
|
||||||
|
context: context, viewModel: viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildExpansionTileItemChildren(
|
||||||
|
{required BuildContext context,
|
||||||
|
required CourseUnitViewModel viewModel}) =>
|
||||||
|
[
|
||||||
|
_buildProgressRowWrapper(),
|
||||||
|
verticalSpaceSmall,
|
||||||
|
_buildActionButtonWrapper(context: context, viewModel: viewModel),
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildCourseModulesState(viewModel)
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildProgressRowWrapper() => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
child: _buildProgressRow(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildProgressRow() => Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: _buildProgressChildren(),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildProgressChildren() =>
|
||||||
|
[_buildProgressStatusWrapper(), horizontalSpaceSmall, _buildProgress()];
|
||||||
|
|
||||||
|
Widget _buildProgressStatusWrapper() => Expanded(
|
||||||
|
child: _buildProgressStatus(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildProgressStatus() => const CustomLinearProgressIndicator(
|
||||||
|
progress: 0,
|
||||||
|
activeColor: kcPrimaryColor,
|
||||||
|
backgroundColor: kcVeryLightGrey);
|
||||||
|
|
||||||
|
Widget _buildProgress() => const Text(
|
||||||
|
'0/0',
|
||||||
|
style: TextStyle(color: kcDarkGrey),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildActionButtonWrapper(
|
||||||
|
{required BuildContext context,
|
||||||
|
required CourseUnitViewModel viewModel}) =>
|
||||||
|
Container(
|
||||||
|
height: 40,
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
child: _buildActionButtons(context: context, viewModel: viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildActionButtons(
|
||||||
|
{required BuildContext context,
|
||||||
|
required CourseUnitViewModel viewModel}) =>
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
_buildLessonButtonWrapper(viewModel),
|
||||||
|
horizontalSpaceSmall,
|
||||||
|
_buildPracticeButtonWrapper(context: context, viewModel: viewModel),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildLessonButtonWrapper(CourseUnitViewModel viewModel) => Expanded(
|
||||||
|
child: _buildLessonButton(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildLessonButton(CourseUnitViewModel viewModel) =>
|
||||||
|
CustomElevatedButton(
|
||||||
|
height: 15,
|
||||||
|
borderRadius: 12,
|
||||||
|
onTap: onLessonTap,
|
||||||
|
text: 'View Module',
|
||||||
|
foregroundColor: kcWhite,
|
||||||
|
backgroundColor: kcPrimaryColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildPracticeButtonWrapper(
|
||||||
|
{required BuildContext context,
|
||||||
|
required CourseUnitViewModel viewModel}) =>
|
||||||
|
Expanded(
|
||||||
|
child: _buildPracticeButton(context: context, viewModel: viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildPracticeButton(
|
||||||
|
{required BuildContext context,
|
||||||
|
required CourseUnitViewModel viewModel}) =>
|
||||||
|
CustomElevatedButton(
|
||||||
|
height: 15,
|
||||||
|
borderRadius: 12,
|
||||||
|
onTap: onPracticeTap,
|
||||||
|
text: 'View Practices',
|
||||||
|
backgroundColor: kcWhite,
|
||||||
|
borderColor: kcPrimaryColor,
|
||||||
|
foregroundColor: kcPrimaryColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSheet(CourseUnitViewModel viewModel) => FinishPracticeSheet(
|
||||||
|
onTap: viewModel.pop,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildCourseModulesState(CourseUnitViewModel viewModel) =>
|
||||||
|
viewModel.busy(StateObjects.courseModules)
|
||||||
|
? _buildProgressIndicator()
|
||||||
|
: _buildCourseModules(viewModel);
|
||||||
|
|
||||||
|
Widget _buildProgressIndicator() => const Center(
|
||||||
|
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildCourseModules(CourseUnitViewModel viewModel) => ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: unit.modules?.length,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (context, index) =>
|
||||||
|
_buildCourseModuleCard(unit.modules?[index].name ?? ''),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildCourseModuleCard(String title) =>
|
||||||
|
CourseModuleTileSmall(title: title, status: ProgressStatuses.completed);
|
||||||
|
|
||||||
|
// Widget _buildContainerShaderState() => status == ProgressStatuses.pending
|
||||||
|
// ? _buildContainerShaderWrapper()
|
||||||
|
// : Container();
|
||||||
|
|
||||||
|
Widget _buildContainerShaderWrapper() => Positioned.fill(
|
||||||
|
child: _buildContainerShader(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildContainerShader() => Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kcWhite.withOpacity(0.5),
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:yimaru_app/ui/widgets/app_bar_pattern.dart';
|
||||||
import 'package:yimaru_app/ui/widgets/language_button.dart';
|
import 'package:yimaru_app/ui/widgets/language_button.dart';
|
||||||
|
|
||||||
class LargeAppBar extends StatelessWidget {
|
class LargeAppBar extends StatelessWidget {
|
||||||
|
final String? language;
|
||||||
final bool showBackButton;
|
final bool showBackButton;
|
||||||
final GestureTapCallback? onPop;
|
final GestureTapCallback? onPop;
|
||||||
final bool showLanguageSelection;
|
final bool showLanguageSelection;
|
||||||
|
|
@ -14,6 +15,7 @@ class LargeAppBar extends StatelessWidget {
|
||||||
{super.key,
|
{super.key,
|
||||||
this.onPop,
|
this.onPop,
|
||||||
this.onClose,
|
this.onClose,
|
||||||
|
this.language,
|
||||||
this.onLanguage,
|
this.onLanguage,
|
||||||
required this.showBackButton,
|
required this.showBackButton,
|
||||||
required this.showLanguageSelection});
|
required this.showLanguageSelection});
|
||||||
|
|
@ -72,8 +74,8 @@ class LargeAppBar extends StatelessWidget {
|
||||||
: _buildCloseButton());
|
: _buildCloseButton());
|
||||||
|
|
||||||
Widget _buildLanguageSelector() => LanguageButton(
|
Widget _buildLanguageSelector() => LanguageButton(
|
||||||
language: 'EN',
|
|
||||||
onTap: onLanguage,
|
onTap: onLanguage,
|
||||||
|
language: language ?? '',
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildCloseButton() => IconButton(
|
Widget _buildCloseButton() => IconButton(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../common/app_colors.dart';
|
import '../common/app_colors.dart';
|
||||||
|
import '../common/translations/locale_keys.g.dart';
|
||||||
import '../common/ui_helpers.dart';
|
import '../common/ui_helpers.dart';
|
||||||
|
|
||||||
class OptionTextDivider extends StatelessWidget {
|
class OptionTextDivider extends StatelessWidget {
|
||||||
|
|
@ -22,9 +24,9 @@ class OptionTextDivider extends StatelessWidget {
|
||||||
|
|
||||||
Widget _buildDivider() => const Divider(color: kcVeryLightGrey);
|
Widget _buildDivider() => const Divider(color: kcVeryLightGrey);
|
||||||
|
|
||||||
Widget _buildOrText() => const Text(
|
Widget _buildOrText() => Text(
|
||||||
'or',
|
LocaleKeys.or.tr(),
|
||||||
|
style: style14MG400,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(color: kcMediumGrey),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ import 'package:yimaru_app/ui/common/app_colors.dart';
|
||||||
import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
||||||
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
|
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
|
||||||
|
|
||||||
class OverallLearnProgress extends StatelessWidget {
|
class OverallProgress extends StatelessWidget {
|
||||||
final int progress;
|
final int progress;
|
||||||
final Color backgroundColor;
|
final Color backgroundColor;
|
||||||
final Color indicatorBackgroundColor;
|
final Color indicatorBackgroundColor;
|
||||||
const OverallLearnProgress(
|
const OverallProgress(
|
||||||
{super.key,
|
{super.key,
|
||||||
required this.progress,
|
required this.progress,
|
||||||
required this.backgroundColor,
|
required this.backgroundColor,
|
||||||
|
|
@ -63,8 +63,8 @@ class OverallLearnProgress extends StatelessWidget {
|
||||||
backgroundColor: indicatorBackgroundColor,
|
backgroundColor: indicatorBackgroundColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubtitle() => const Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'Keep up the great work! You\'re doing amazing.',
|
'Keep up the great work! You\'re doing amazing.',
|
||||||
style: TextStyle(color: kcDarkGrey),
|
style: style14DG500,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
|
||||||
|
|
||||||
import '../common/app_colors.dart';
|
import '../common/app_colors.dart';
|
||||||
import '../common/ui_helpers.dart';
|
import '../common/ui_helpers.dart';
|
||||||
|
|
@ -18,9 +20,9 @@ class RegisterForAccount extends StatelessWidget {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLeadingText() => const Text(
|
Widget _buildLeadingText() => Text(
|
||||||
'Don’t have an account? ',
|
'${LocaleKeys.dont_have_account.tr()} ',
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildRegisterTextButton() => TextButton(
|
Widget _buildRegisterTextButton() => TextButton(
|
||||||
|
|
@ -31,8 +33,8 @@ class RegisterForAccount extends StatelessWidget {
|
||||||
child: _buildRegisterText(),
|
child: _buildRegisterText(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildRegisterText() => const Text(
|
Widget _buildRegisterText() => Text(
|
||||||
'Register',
|
LocaleKeys.register.tr(),
|
||||||
style: TextStyle(color: kcPrimaryColor),
|
style:style14P400 ,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import flutter_secure_storage_darwin
|
||||||
import google_sign_in_ios
|
import google_sign_in_ios
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
import record_macos
|
import record_macos
|
||||||
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
import video_player_avfoundation
|
import video_player_avfoundation
|
||||||
|
|
@ -35,6 +36,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
|
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
|
||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
|
RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
|
||||||
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
||||||
|
|
|
||||||
77
pubspec.lock
77
pubspec.lock
|
|
@ -377,6 +377,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.2"
|
version: "6.0.2"
|
||||||
|
easy_localization:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: easy_localization
|
||||||
|
sha256: "2ccdf9db8fe4d9c5a75c122e6275674508fd0f0d49c827354967b8afcc56bbed"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.8"
|
||||||
|
easy_logger:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: easy_logger
|
||||||
|
sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.2"
|
||||||
email_validator:
|
email_validator:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -654,6 +670,11 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
|
flutter_localizations:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1472,6 +1493,62 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.28.0"
|
version: "0.28.0"
|
||||||
|
shared_preferences:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences
|
||||||
|
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.5"
|
||||||
|
shared_preferences_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_android
|
||||||
|
sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.23"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_foundation
|
||||||
|
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.6"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
shared_preferences_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_platform_interface
|
||||||
|
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2"
|
||||||
|
shared_preferences_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_web
|
||||||
|
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.3"
|
||||||
|
shared_preferences_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_windows
|
||||||
|
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
name: yimaru_app
|
name: yimaru_app
|
||||||
version: 0.1.15+17
|
version: 0.1.16+18
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
|
|
||||||
|
|
@ -9,6 +9,7 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
http: any
|
||||||
intl: any
|
intl: any
|
||||||
dio: ^5.9.0
|
dio: ^5.9.0
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
|
|
@ -37,6 +38,7 @@ dependencies:
|
||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
flutter_spinkit: ^5.2.2
|
flutter_spinkit: ^5.2.2
|
||||||
stacked_services: ^1.1.0
|
stacked_services: ^1.1.0
|
||||||
|
easy_localization: ^3.0.8
|
||||||
omni_datetime_picker: any
|
omni_datetime_picker: any
|
||||||
json_serializable: ^6.8.0
|
json_serializable: ^6.8.0
|
||||||
waveform_recorder: ^1.8.0
|
waveform_recorder: ^1.8.0
|
||||||
|
|
@ -54,7 +56,6 @@ dependencies:
|
||||||
flutter_local_notifications: ^20.1.0
|
flutter_local_notifications: ^20.1.0
|
||||||
internet_connection_checker_plus: ^2.9.1+2
|
internet_connection_checker_plus: ^2.9.1+2
|
||||||
|
|
||||||
http: any
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
@ -74,6 +75,7 @@ flutter:
|
||||||
assets:
|
assets:
|
||||||
- assets/icons/
|
- assets/icons/
|
||||||
- assets/images/
|
- assets/images/
|
||||||
|
- assets/translations/
|
||||||
fonts:
|
fonts:
|
||||||
- family: Aeonik
|
- family: Aeonik
|
||||||
fonts:
|
fonts:
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import 'package:yimaru_app/services/vimeo_service.dart';
|
||||||
import 'package:yimaru_app/services/url_launcher_service.dart';
|
import 'package:yimaru_app/services/url_launcher_service.dart';
|
||||||
import 'package:yimaru_app/services/phone_caller_service.dart';
|
import 'package:yimaru_app/services/phone_caller_service.dart';
|
||||||
import 'package:yimaru_app/services/learn_service.dart';
|
import 'package:yimaru_app/services/learn_service.dart';
|
||||||
|
import 'package:yimaru_app/services/localization_service.dart';
|
||||||
// @stacked-import
|
// @stacked-import
|
||||||
|
|
||||||
import 'test_helpers.mocks.dart';
|
import 'test_helpers.mocks.dart';
|
||||||
|
|
@ -56,6 +57,7 @@ import 'test_helpers.mocks.dart';
|
||||||
MockSpec<LearnLessonService>(onMissingStub: OnMissingStub.returnDefault),
|
MockSpec<LearnLessonService>(onMissingStub: OnMissingStub.returnDefault),
|
||||||
MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault),
|
MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault),
|
||||||
MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault),
|
MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault),
|
||||||
|
MockSpec<LocalizationService>(onMissingStub: OnMissingStub.returnDefault),
|
||||||
// @stacked-mock-spec
|
// @stacked-mock-spec
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
@ -86,6 +88,7 @@ void registerServices() {
|
||||||
getAndRegisterLearnLessonService();
|
getAndRegisterLearnLessonService();
|
||||||
getAndRegisterLearnService();
|
getAndRegisterLearnService();
|
||||||
getAndRegisterLearnService();
|
getAndRegisterLearnService();
|
||||||
|
getAndRegisterLocalizationService();
|
||||||
// @stacked-mock-register
|
// @stacked-mock-register
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -275,6 +278,13 @@ MockLearnService getAndRegisterLearnService() {
|
||||||
locator.registerSingleton<LearnService>(service);
|
locator.registerSingleton<LearnService>(service);
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MockLocalizationService getAndRegisterLocalizationService() {
|
||||||
|
_removeRegistrationIfExists<LocalizationService>();
|
||||||
|
final service = MockLocalizationService();
|
||||||
|
locator.registerSingleton<LocalizationService>(service);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
// @stacked-mock-create
|
// @stacked-mock-create
|
||||||
|
|
||||||
void _removeRegistrationIfExists<T extends Object>() {
|
void _removeRegistrationIfExists<T extends Object>() {
|
||||||
|
|
|
||||||
11
test/services/localization_service_test.dart
Normal file
11
test/services/localization_service_test.dart
Normal 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('LocalizationServiceTest -', () {
|
||||||
|
setUp(() => registerServices());
|
||||||
|
tearDown(() => locator.reset());
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user