- fix(learn): Modify overall learn hierarchy.
- fix(learn): Modify learn path flow according to the new hierarchy. - add(learn): Add additionl screens for the new hierarchy levels.
This commit is contained in:
parent
f8973c8752
commit
35baae8d92
|
|
@ -1,46 +1,49 @@
|
||||||
{
|
{
|
||||||
"project_info": {
|
"project_info": {
|
||||||
"project_number": "574860813475",
|
"project_number": "900714037062",
|
||||||
"project_id": "yimaru-lms-e834e",
|
"project_id": "yimaru-academy-5e7e2",
|
||||||
"storage_bucket": "yimaru-lms-e834e.firebasestorage.app"
|
"storage_bucket": "yimaru-academy-5e7e2.firebasestorage.app"
|
||||||
},
|
},
|
||||||
"client": [
|
"client": [
|
||||||
{
|
{
|
||||||
"client_info": {
|
"client_info": {
|
||||||
"mobilesdk_app_id": "1:574860813475:android:cd7fa6cf3a0527d97acb16",
|
"mobilesdk_app_id": "1:900714037062:android:f11e3b69315b05304e6f47",
|
||||||
"android_client_info": {
|
"android_client_info": {
|
||||||
"package_name": "com.yimaru.lms.app"
|
"package_name": "com.yimaru.lms.app"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"oauth_client": [
|
"oauth_client": [
|
||||||
{
|
{
|
||||||
"client_id": "574860813475-3p3k63lkrfd113sn6jscgvdj0aigsg5s.apps.googleusercontent.com",
|
"client_id": "900714037062-ngc0gc426sfnnjjr494g4vni46ne5uqv.apps.googleusercontent.com",
|
||||||
"client_type": 1,
|
"client_type": 1,
|
||||||
"android_info": {
|
"android_info": {
|
||||||
"package_name": "com.yimaru.lms.app",
|
"package_name": "com.yimaru.lms.app",
|
||||||
"certificate_hash": "928ead08b5e39d6a861a55ae7cceb8c402d1ee7a"
|
"certificate_hash": "139ee56ac9763191d1eee882efc440c10530e6e9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"client_id": "574860813475-m90u87plqaac4tb8oug32k41usossiod.apps.googleusercontent.com",
|
"client_id": "900714037062-mtm9rps15br603nnn3a451rr2vllopro.apps.googleusercontent.com",
|
||||||
"client_type": 1,
|
"client_type": 3
|
||||||
"android_info": {
|
|
||||||
"package_name": "com.yimaru.lms.app",
|
|
||||||
"certificate_hash": "29797902ad6a24212b9d9fad71562907956f6a6c"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"api_key": [
|
"api_key": [
|
||||||
{
|
{
|
||||||
"current_key": "AIzaSyC7QlhcuSNte49CERnRKPrQbyLbwErIRmk"
|
"current_key": "AIzaSyAi8e4PMFH8QQU11DWftHdNEu8WUP7i2ww"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"services": {
|
"services": {
|
||||||
"appinvite_service": {
|
"appinvite_service": {
|
||||||
"other_platform_oauth_client": [
|
"other_platform_oauth_client": [
|
||||||
{
|
{
|
||||||
"client_id": "574860813475-n5o17gpprdqmhcml99tiqhafb17rob0r.apps.googleusercontent.com",
|
"client_id": "900714037062-24ria5fcfet71o3vde8f6gsvsj1n68ec.apps.googleusercontent.com",
|
||||||
"client_type": 3
|
"client_type": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "900714037062-35bg0hsou56hg37mbcbpiar9uti7tcku.apps.googleusercontent.com",
|
||||||
|
"client_type": 2,
|
||||||
|
"ios_info": {
|
||||||
|
"bundle_id": "com.yimaru.lms.app"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"yimaru-lms-e834e","appId":"1:574860813475:android:cd7fa6cf3a0527d97acb16","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"yimaru-lms-e834e","configurations":{"android":"1:574860813475:android:cd7fa6cf3a0527d97acb16","ios":"1:574860813475:ios:3ac9f7c4ae1771287acb16"}}}}}}
|
{"flutter":{"platforms":{"android":{"default":{"projectId":"yimaru-academy-5e7e2","appId":"1:900714037062:android:f11e3b69315b05304e6f47","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"yimaru-academy-5e7e2","configurations":{"android":"1:900714037062:android:f11e3b69315b05304e6f47","ios":"1:900714037062:ios:1caf8f24a4333b8e4e6f47"}}}}}}
|
||||||
|
|
@ -51,6 +51,7 @@ import 'package:yimaru_app/ui/views/course/course_view.dart';
|
||||||
import 'package:yimaru_app/services/audio_player_service.dart';
|
import 'package:yimaru_app/services/audio_player_service.dart';
|
||||||
import 'package:yimaru_app/services/voice_recorder_service.dart';
|
import 'package:yimaru_app/services/voice_recorder_service.dart';
|
||||||
import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_view.dart';
|
import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_view.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/learn_subcategory/learn_subcategory_view.dart';
|
||||||
// @stacked-import
|
// @stacked-import
|
||||||
|
|
||||||
@StackedApp(
|
@StackedApp(
|
||||||
|
|
@ -90,6 +91,7 @@ import 'package:yimaru_app/ui/views/course_practice_question/course_practice_que
|
||||||
MaterialRoute(page: CourseSubcategoryView),
|
MaterialRoute(page: CourseSubcategoryView),
|
||||||
MaterialRoute(page: CourseView),
|
MaterialRoute(page: CourseView),
|
||||||
MaterialRoute(page: CoursePracticeQuestionView),
|
MaterialRoute(page: CoursePracticeQuestionView),
|
||||||
|
MaterialRoute(page: LearnSubcategoryView),
|
||||||
// @stacked-route
|
// @stacked-route
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,15 @@
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||||
import 'package:flutter/material.dart' as _i37;
|
import 'package:flutter/material.dart' as _i38;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stacked/stacked.dart' as _i1;
|
import 'package:stacked/stacked.dart' as _i1;
|
||||||
import 'package:stacked_services/stacked_services.dart' as _i42;
|
import 'package:stacked_services/stacked_services.dart' as _i44;
|
||||||
import 'package:yimaru_app/models/course.dart' as _i38;
|
import 'package:yimaru_app/models/category.dart' as _i42;
|
||||||
import 'package:yimaru_app/models/course_category.dart' as _i40;
|
import 'package:yimaru_app/models/course.dart' as _i40;
|
||||||
import 'package:yimaru_app/models/course_lesson.dart' as _i39;
|
import 'package:yimaru_app/models/course_lesson.dart' as _i41;
|
||||||
import 'package:yimaru_app/models/course_subcategory.dart' as _i41;
|
import 'package:yimaru_app/models/level.dart' as _i39;
|
||||||
|
import 'package:yimaru_app/models/subcategory.dart' as _i43;
|
||||||
import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart'
|
import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart'
|
||||||
as _i9;
|
as _i9;
|
||||||
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i22;
|
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i22;
|
||||||
|
|
@ -50,6 +51,8 @@ import 'package:yimaru_app/ui/views/learn_module/learn_module_view.dart'
|
||||||
as _i20;
|
as _i20;
|
||||||
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart'
|
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart'
|
||||||
as _i26;
|
as _i26;
|
||||||
|
import 'package:yimaru_app/ui/views/learn_subcategory/learn_subcategory_view.dart'
|
||||||
|
as _i37;
|
||||||
import 'package:yimaru_app/ui/views/login/login_view.dart' as _i17;
|
import 'package:yimaru_app/ui/views/login/login_view.dart' as _i17;
|
||||||
import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i3;
|
import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i3;
|
||||||
import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart'
|
import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart'
|
||||||
|
|
@ -138,6 +141,8 @@ class Routes {
|
||||||
|
|
||||||
static const coursePracticeQuestionView = '/course-practice-question-view';
|
static const coursePracticeQuestionView = '/course-practice-question-view';
|
||||||
|
|
||||||
|
static const learnSubcategoryView = '/learn-subcategory-view';
|
||||||
|
|
||||||
static const all = <String>{
|
static const all = <String>{
|
||||||
homeView,
|
homeView,
|
||||||
onboardingView,
|
onboardingView,
|
||||||
|
|
@ -174,6 +179,7 @@ class Routes {
|
||||||
courseSubcategoryView,
|
courseSubcategoryView,
|
||||||
courseView,
|
courseView,
|
||||||
coursePracticeQuestionView,
|
coursePracticeQuestionView,
|
||||||
|
learnSubcategoryView,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,17 +325,21 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
Routes.coursePracticeQuestionView,
|
Routes.coursePracticeQuestionView,
|
||||||
page: _i36.CoursePracticeQuestionView,
|
page: _i36.CoursePracticeQuestionView,
|
||||||
),
|
),
|
||||||
|
_i1.RouteDef(
|
||||||
|
Routes.learnSubcategoryView,
|
||||||
|
page: _i37.LearnSubcategoryView,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
final _pagesMap = <Type, _i1.StackedRouteFactory>{
|
final _pagesMap = <Type, _i1.StackedRouteFactory>{
|
||||||
_i2.HomeView: (data) {
|
_i2.HomeView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i2.HomeView(),
|
builder: (context) => const _i2.HomeView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i3.OnboardingView: (data) {
|
_i3.OnboardingView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i3.OnboardingView(),
|
builder: (context) => const _i3.OnboardingView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
|
|
@ -338,116 +348,120 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
final args = data.getArgs<StartupViewArguments>(
|
final args = data.getArgs<StartupViewArguments>(
|
||||||
orElse: () => const StartupViewArguments(),
|
orElse: () => const StartupViewArguments(),
|
||||||
);
|
);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => _i4.StartupView(key: args.key, label: args.label),
|
builder: (context) => _i4.StartupView(key: args.key, label: args.label),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i5.ProfileView: (data) {
|
_i5.ProfileView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i5.ProfileView(),
|
builder: (context) => const _i5.ProfileView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i6.ProfileDetailView: (data) {
|
_i6.ProfileDetailView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i6.ProfileDetailView(),
|
builder: (context) => const _i6.ProfileDetailView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i7.DownloadsView: (data) {
|
_i7.DownloadsView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i7.DownloadsView(),
|
builder: (context) => const _i7.DownloadsView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i8.ProgressView: (data) {
|
_i8.ProgressView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i8.ProgressView(),
|
builder: (context) => const _i8.ProgressView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i9.AccountPrivacyView: (data) {
|
_i9.AccountPrivacyView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i9.AccountPrivacyView(),
|
builder: (context) => const _i9.AccountPrivacyView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i10.SupportView: (data) {
|
_i10.SupportView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i10.SupportView(),
|
builder: (context) => const _i10.SupportView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i11.TelegramSupportView: (data) {
|
_i11.TelegramSupportView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i11.TelegramSupportView(),
|
builder: (context) => const _i11.TelegramSupportView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i12.CallSupportView: (data) {
|
_i12.CallSupportView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i12.CallSupportView(),
|
builder: (context) => const _i12.CallSupportView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i13.LanguageView: (data) {
|
_i13.LanguageView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i13.LanguageView(),
|
builder: (context) => const _i13.LanguageView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i14.PrivacyPolicyView: (data) {
|
_i14.PrivacyPolicyView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i14.PrivacyPolicyView(),
|
builder: (context) => const _i14.PrivacyPolicyView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i15.TermsAndConditionsView: (data) {
|
_i15.TermsAndConditionsView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i15.TermsAndConditionsView(),
|
builder: (context) => const _i15.TermsAndConditionsView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i16.RegisterView: (data) {
|
_i16.RegisterView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i16.RegisterView(),
|
builder: (context) => const _i16.RegisterView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i17.LoginView: (data) {
|
_i17.LoginView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i17.LoginView(),
|
builder: (context) => const _i17.LoginView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i18.LearnView: (data) {
|
_i18.LearnView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
final args = data.getArgs<LearnViewArguments>(nullOk: false);
|
||||||
builder: (context) => const _i18.LearnView(),
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
|
builder: (context) => _i18.LearnView(key: args.key, id: args.id),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i19.LearnLevelView: (data) {
|
_i19.LearnLevelView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
final args = data.getArgs<LearnLevelViewArguments>(nullOk: false);
|
||||||
builder: (context) => const _i19.LearnLevelView(),
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
|
builder: (context) => _i19.LearnLevelView(key: args.key, id: args.id),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i20.LearnModuleView: (data) {
|
_i20.LearnModuleView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
final args = data.getArgs<LearnModuleViewArguments>(nullOk: false);
|
||||||
builder: (context) => const _i20.LearnModuleView(),
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
|
builder: (context) =>
|
||||||
|
_i20.LearnModuleView(key: args.key, level: args.level),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i21.WelcomeView: (data) {
|
_i21.WelcomeView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i21.WelcomeView(),
|
builder: (context) => const _i21.WelcomeView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i22.AssessmentView: (data) {
|
_i22.AssessmentView: (data) {
|
||||||
final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
|
final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
_i22.AssessmentView(key: args.key, data: args.data),
|
_i22.AssessmentView(key: args.key, data: args.data),
|
||||||
settings: data,
|
settings: data,
|
||||||
|
|
@ -455,7 +469,7 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
},
|
},
|
||||||
_i23.LearnLessonView: (data) {
|
_i23.LearnLessonView: (data) {
|
||||||
final args = data.getArgs<LearnLessonViewArguments>(nullOk: false);
|
final args = data.getArgs<LearnLessonViewArguments>(nullOk: false);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => _i23.LearnLessonView(
|
builder: (context) => _i23.LearnLessonView(
|
||||||
key: args.key,
|
key: args.key,
|
||||||
title: args.title,
|
title: args.title,
|
||||||
|
|
@ -467,14 +481,14 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i24.ForgetPasswordView: (data) {
|
_i24.ForgetPasswordView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i24.ForgetPasswordView(),
|
builder: (context) => const _i24.ForgetPasswordView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i25.LearnLessonDetailView: (data) {
|
_i25.LearnLessonDetailView: (data) {
|
||||||
final args = data.getArgs<LearnLessonDetailViewArguments>(nullOk: false);
|
final args = data.getArgs<LearnLessonDetailViewArguments>(nullOk: false);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => _i25.LearnLessonDetailView(
|
builder: (context) => _i25.LearnLessonDetailView(
|
||||||
key: args.key,
|
key: args.key,
|
||||||
title: args.title,
|
title: args.title,
|
||||||
|
|
@ -485,7 +499,7 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
},
|
},
|
||||||
_i26.LearnPracticeView: (data) {
|
_i26.LearnPracticeView: (data) {
|
||||||
final args = data.getArgs<LearnPracticeViewArguments>(nullOk: false);
|
final args = data.getArgs<LearnPracticeViewArguments>(nullOk: false);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => _i26.LearnPracticeView(
|
builder: (context) => _i26.LearnPracticeView(
|
||||||
key: args.key,
|
key: args.key,
|
||||||
title: args.title,
|
title: args.title,
|
||||||
|
|
@ -497,7 +511,7 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
},
|
},
|
||||||
_i27.CoursePracticeView: (data) {
|
_i27.CoursePracticeView: (data) {
|
||||||
final args = data.getArgs<CoursePracticeViewArguments>(nullOk: false);
|
final args = data.getArgs<CoursePracticeViewArguments>(nullOk: false);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
_i27.CoursePracticeView(key: args.key, id: args.id),
|
_i27.CoursePracticeView(key: args.key, id: args.id),
|
||||||
settings: data,
|
settings: data,
|
||||||
|
|
@ -505,21 +519,21 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
},
|
},
|
||||||
_i28.CoursePaymentView: (data) {
|
_i28.CoursePaymentView: (data) {
|
||||||
final args = data.getArgs<CoursePaymentViewArguments>(nullOk: false);
|
final args = data.getArgs<CoursePaymentViewArguments>(nullOk: false);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
_i28.CoursePaymentView(key: args.key, course: args.course),
|
_i28.CoursePaymentView(key: args.key, course: args.course),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i29.CourseCategoryView: (data) {
|
_i29.CourseCategoryView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i29.CourseCategoryView(),
|
builder: (context) => const _i29.CourseCategoryView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i30.FailureView: (data) {
|
_i30.FailureView: (data) {
|
||||||
final args = data.getArgs<FailureViewArguments>(nullOk: false);
|
final args = data.getArgs<FailureViewArguments>(nullOk: false);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
_i30.FailureView(key: args.key, label: args.label),
|
_i30.FailureView(key: args.key, label: args.label),
|
||||||
settings: data,
|
settings: data,
|
||||||
|
|
@ -527,7 +541,7 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
},
|
},
|
||||||
_i31.CourseLessonView: (data) {
|
_i31.CourseLessonView: (data) {
|
||||||
final args = data.getArgs<CourseLessonViewArguments>(nullOk: false);
|
final args = data.getArgs<CourseLessonViewArguments>(nullOk: false);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
_i31.CourseLessonView(key: args.key, course: args.course),
|
_i31.CourseLessonView(key: args.key, course: args.course),
|
||||||
settings: data,
|
settings: data,
|
||||||
|
|
@ -535,21 +549,21 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
},
|
},
|
||||||
_i32.CourseLessonDetailView: (data) {
|
_i32.CourseLessonDetailView: (data) {
|
||||||
final args = data.getArgs<CourseLessonDetailViewArguments>(nullOk: false);
|
final args = data.getArgs<CourseLessonDetailViewArguments>(nullOk: false);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
_i32.CourseLessonDetailView(key: args.key, lesson: args.lesson),
|
_i32.CourseLessonDetailView(key: args.key, lesson: args.lesson),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i33.DuolingoView: (data) {
|
_i33.DuolingoView: (data) {
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i33.DuolingoView(),
|
builder: (context) => const _i33.DuolingoView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i34.CourseSubcategoryView: (data) {
|
_i34.CourseSubcategoryView: (data) {
|
||||||
final args = data.getArgs<CourseSubcategoryViewArguments>(nullOk: false);
|
final args = data.getArgs<CourseSubcategoryViewArguments>(nullOk: false);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
_i34.CourseSubcategoryView(key: args.key, category: args.category),
|
_i34.CourseSubcategoryView(key: args.key, category: args.category),
|
||||||
settings: data,
|
settings: data,
|
||||||
|
|
@ -557,7 +571,7 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
},
|
},
|
||||||
_i35.CourseView: (data) {
|
_i35.CourseView: (data) {
|
||||||
final args = data.getArgs<CourseViewArguments>(nullOk: false);
|
final args = data.getArgs<CourseViewArguments>(nullOk: false);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
_i35.CourseView(key: args.key, subcategory: args.subcategory),
|
_i35.CourseView(key: args.key, subcategory: args.subcategory),
|
||||||
settings: data,
|
settings: data,
|
||||||
|
|
@ -566,12 +580,18 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
_i36.CoursePracticeQuestionView: (data) {
|
_i36.CoursePracticeQuestionView: (data) {
|
||||||
final args =
|
final args =
|
||||||
data.getArgs<CoursePracticeQuestionViewArguments>(nullOk: false);
|
data.getArgs<CoursePracticeQuestionViewArguments>(nullOk: false);
|
||||||
return _i37.MaterialPageRoute<dynamic>(
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
_i36.CoursePracticeQuestionView(key: args.key, id: args.id),
|
_i36.CoursePracticeQuestionView(key: args.key, id: args.id),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
_i37.LearnSubcategoryView: (data) {
|
||||||
|
return _i38.MaterialPageRoute<dynamic>(
|
||||||
|
builder: (context) => const _i37.LearnSubcategoryView(),
|
||||||
|
settings: data,
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -587,7 +607,7 @@ class StartupViewArguments {
|
||||||
this.label = 'Loading',
|
this.label = 'Loading',
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
|
|
||||||
|
|
@ -608,13 +628,94 @@ class StartupViewArguments {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LearnViewArguments {
|
||||||
|
const LearnViewArguments({
|
||||||
|
this.key,
|
||||||
|
required this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
final _i38.Key? key;
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '{"key": "$key", "id": "$id"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant LearnViewArguments other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
return other.key == key && other.id == id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return key.hashCode ^ id.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LearnLevelViewArguments {
|
||||||
|
const LearnLevelViewArguments({
|
||||||
|
this.key,
|
||||||
|
required this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
final _i38.Key? key;
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '{"key": "$key", "id": "$id"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant LearnLevelViewArguments other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
return other.key == key && other.id == id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return key.hashCode ^ id.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LearnModuleViewArguments {
|
||||||
|
const LearnModuleViewArguments({
|
||||||
|
this.key,
|
||||||
|
required this.level,
|
||||||
|
});
|
||||||
|
|
||||||
|
final _i38.Key? key;
|
||||||
|
|
||||||
|
final _i39.Level level;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '{"key": "$key", "level": "$level"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant LearnModuleViewArguments other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
return other.key == key && other.level == level;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return key.hashCode ^ level.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class AssessmentViewArguments {
|
class AssessmentViewArguments {
|
||||||
const AssessmentViewArguments({
|
const AssessmentViewArguments({
|
||||||
this.key,
|
this.key,
|
||||||
required this.data,
|
required this.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final Map<String, dynamic> data;
|
final Map<String, dynamic> data;
|
||||||
|
|
||||||
|
|
@ -645,7 +746,7 @@ class LearnLessonViewArguments {
|
||||||
required this.description,
|
required this.description,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
|
|
@ -692,7 +793,7 @@ class LearnLessonDetailViewArguments {
|
||||||
required this.description,
|
required this.description,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
|
|
@ -732,7 +833,7 @@ class LearnPracticeViewArguments {
|
||||||
required this.buttonLabel,
|
required this.buttonLabel,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
|
|
@ -773,7 +874,7 @@ class CoursePracticeViewArguments {
|
||||||
required this.id,
|
required this.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final int id;
|
final int id;
|
||||||
|
|
||||||
|
|
@ -800,9 +901,9 @@ class CoursePaymentViewArguments {
|
||||||
required this.course,
|
required this.course,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final _i38.Course course;
|
final _i40.Course course;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -827,7 +928,7 @@ class FailureViewArguments {
|
||||||
required this.label,
|
required this.label,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
|
|
||||||
|
|
@ -854,9 +955,9 @@ class CourseLessonViewArguments {
|
||||||
required this.course,
|
required this.course,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final _i38.Course course;
|
final _i40.Course course;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -881,9 +982,9 @@ class CourseLessonDetailViewArguments {
|
||||||
required this.lesson,
|
required this.lesson,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final _i39.CourseLesson lesson;
|
final _i41.CourseLesson lesson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -908,9 +1009,9 @@ class CourseSubcategoryViewArguments {
|
||||||
required this.category,
|
required this.category,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final _i40.CourseCategory category;
|
final _i42.Category category;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -935,9 +1036,9 @@ class CourseViewArguments {
|
||||||
required this.subcategory,
|
required this.subcategory,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final _i41.CourseSubcategory subcategory;
|
final _i43.Subcategory subcategory;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -962,7 +1063,7 @@ class CoursePracticeQuestionViewArguments {
|
||||||
required this.id,
|
required this.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i37.Key? key;
|
final _i38.Key? key;
|
||||||
|
|
||||||
final int id;
|
final int id;
|
||||||
|
|
||||||
|
|
@ -983,7 +1084,7 @@ class CoursePracticeQuestionViewArguments {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NavigatorStateExtension on _i42.NavigationService {
|
extension NavigatorStateExtension on _i44.NavigationService {
|
||||||
Future<dynamic> navigateToHomeView([
|
Future<dynamic> navigateToHomeView([
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1013,7 +1114,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToStartupView({
|
Future<dynamic> navigateToStartupView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
String label = 'Loading',
|
String label = 'Loading',
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1211,42 +1312,51 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
transition: transition);
|
transition: transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToLearnView([
|
Future<dynamic> navigateToLearnView({
|
||||||
|
_i38.Key? key,
|
||||||
|
required int id,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||||
transition,
|
transition,
|
||||||
]) async {
|
}) async {
|
||||||
return navigateTo<dynamic>(Routes.learnView,
|
return navigateTo<dynamic>(Routes.learnView,
|
||||||
|
arguments: LearnViewArguments(key: key, id: id),
|
||||||
id: routerId,
|
id: routerId,
|
||||||
preventDuplicates: preventDuplicates,
|
preventDuplicates: preventDuplicates,
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
transition: transition);
|
transition: transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToLearnLevelView([
|
Future<dynamic> navigateToLearnLevelView({
|
||||||
|
_i38.Key? key,
|
||||||
|
required int id,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||||
transition,
|
transition,
|
||||||
]) async {
|
}) async {
|
||||||
return navigateTo<dynamic>(Routes.learnLevelView,
|
return navigateTo<dynamic>(Routes.learnLevelView,
|
||||||
|
arguments: LearnLevelViewArguments(key: key, id: id),
|
||||||
id: routerId,
|
id: routerId,
|
||||||
preventDuplicates: preventDuplicates,
|
preventDuplicates: preventDuplicates,
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
transition: transition);
|
transition: transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToLearnModuleView([
|
Future<dynamic> navigateToLearnModuleView({
|
||||||
|
_i38.Key? key,
|
||||||
|
required _i39.Level level,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||||
transition,
|
transition,
|
||||||
]) async {
|
}) async {
|
||||||
return navigateTo<dynamic>(Routes.learnModuleView,
|
return navigateTo<dynamic>(Routes.learnModuleView,
|
||||||
|
arguments: LearnModuleViewArguments(key: key, level: level),
|
||||||
id: routerId,
|
id: routerId,
|
||||||
preventDuplicates: preventDuplicates,
|
preventDuplicates: preventDuplicates,
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
|
|
@ -1268,7 +1378,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToAssessmentView({
|
Future<dynamic> navigateToAssessmentView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required Map<String, dynamic> data,
|
required Map<String, dynamic> data,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1285,7 +1395,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToLearnLessonView({
|
Future<dynamic> navigateToLearnLessonView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required String title,
|
required String title,
|
||||||
required String topics,
|
required String topics,
|
||||||
required String subtitle,
|
required String subtitle,
|
||||||
|
|
@ -1326,7 +1436,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToLearnLessonDetailView({
|
Future<dynamic> navigateToLearnLessonDetailView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required String title,
|
required String title,
|
||||||
required List<Map<String, dynamic>> practices,
|
required List<Map<String, dynamic>> practices,
|
||||||
required String description,
|
required String description,
|
||||||
|
|
@ -1349,7 +1459,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToLearnPracticeView({
|
Future<dynamic> navigateToLearnPracticeView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required String title,
|
required String title,
|
||||||
required String subtitle,
|
required String subtitle,
|
||||||
required List<Map<String, dynamic>> practices,
|
required List<Map<String, dynamic>> practices,
|
||||||
|
|
@ -1374,7 +1484,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCoursePracticeView({
|
Future<dynamic> navigateToCoursePracticeView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required int id,
|
required int id,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1391,8 +1501,8 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCoursePaymentView({
|
Future<dynamic> navigateToCoursePaymentView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required _i38.Course course,
|
required _i40.Course course,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1422,7 +1532,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToFailureView({
|
Future<dynamic> navigateToFailureView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required String label,
|
required String label,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1439,8 +1549,8 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCourseLessonView({
|
Future<dynamic> navigateToCourseLessonView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required _i38.Course course,
|
required _i40.Course course,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1456,8 +1566,8 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCourseLessonDetailView({
|
Future<dynamic> navigateToCourseLessonDetailView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required _i39.CourseLesson lesson,
|
required _i41.CourseLesson lesson,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1487,8 +1597,8 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCourseSubcategoryView({
|
Future<dynamic> navigateToCourseSubcategoryView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required _i40.CourseCategory category,
|
required _i42.Category category,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1504,8 +1614,8 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCourseView({
|
Future<dynamic> navigateToCourseView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required _i41.CourseSubcategory subcategory,
|
required _i43.Subcategory subcategory,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1521,7 +1631,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCoursePracticeQuestionView({
|
Future<dynamic> navigateToCoursePracticeQuestionView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required int id,
|
required int id,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1537,6 +1647,20 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
transition: transition);
|
transition: transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<dynamic> navigateToLearnSubcategoryView([
|
||||||
|
int? routerId,
|
||||||
|
bool preventDuplicates = true,
|
||||||
|
Map<String, String>? parameters,
|
||||||
|
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||||
|
transition,
|
||||||
|
]) async {
|
||||||
|
return navigateTo<dynamic>(Routes.learnSubcategoryView,
|
||||||
|
id: routerId,
|
||||||
|
preventDuplicates: preventDuplicates,
|
||||||
|
parameters: parameters,
|
||||||
|
transition: transition);
|
||||||
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithHomeView([
|
Future<dynamic> replaceWithHomeView([
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1566,7 +1690,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithStartupView({
|
Future<dynamic> replaceWithStartupView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
String label = 'Loading',
|
String label = 'Loading',
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1764,42 +1888,51 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
transition: transition);
|
transition: transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithLearnView([
|
Future<dynamic> replaceWithLearnView({
|
||||||
|
_i38.Key? key,
|
||||||
|
required int id,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||||
transition,
|
transition,
|
||||||
]) async {
|
}) async {
|
||||||
return replaceWith<dynamic>(Routes.learnView,
|
return replaceWith<dynamic>(Routes.learnView,
|
||||||
|
arguments: LearnViewArguments(key: key, id: id),
|
||||||
id: routerId,
|
id: routerId,
|
||||||
preventDuplicates: preventDuplicates,
|
preventDuplicates: preventDuplicates,
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
transition: transition);
|
transition: transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithLearnLevelView([
|
Future<dynamic> replaceWithLearnLevelView({
|
||||||
|
_i38.Key? key,
|
||||||
|
required int id,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||||
transition,
|
transition,
|
||||||
]) async {
|
}) async {
|
||||||
return replaceWith<dynamic>(Routes.learnLevelView,
|
return replaceWith<dynamic>(Routes.learnLevelView,
|
||||||
|
arguments: LearnLevelViewArguments(key: key, id: id),
|
||||||
id: routerId,
|
id: routerId,
|
||||||
preventDuplicates: preventDuplicates,
|
preventDuplicates: preventDuplicates,
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
transition: transition);
|
transition: transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithLearnModuleView([
|
Future<dynamic> replaceWithLearnModuleView({
|
||||||
|
_i38.Key? key,
|
||||||
|
required _i39.Level level,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||||
transition,
|
transition,
|
||||||
]) async {
|
}) async {
|
||||||
return replaceWith<dynamic>(Routes.learnModuleView,
|
return replaceWith<dynamic>(Routes.learnModuleView,
|
||||||
|
arguments: LearnModuleViewArguments(key: key, level: level),
|
||||||
id: routerId,
|
id: routerId,
|
||||||
preventDuplicates: preventDuplicates,
|
preventDuplicates: preventDuplicates,
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
|
|
@ -1821,7 +1954,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithAssessmentView({
|
Future<dynamic> replaceWithAssessmentView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required Map<String, dynamic> data,
|
required Map<String, dynamic> data,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1838,7 +1971,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithLearnLessonView({
|
Future<dynamic> replaceWithLearnLessonView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required String title,
|
required String title,
|
||||||
required String topics,
|
required String topics,
|
||||||
required String subtitle,
|
required String subtitle,
|
||||||
|
|
@ -1879,7 +2012,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithLearnLessonDetailView({
|
Future<dynamic> replaceWithLearnLessonDetailView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required String title,
|
required String title,
|
||||||
required List<Map<String, dynamic>> practices,
|
required List<Map<String, dynamic>> practices,
|
||||||
required String description,
|
required String description,
|
||||||
|
|
@ -1902,7 +2035,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithLearnPracticeView({
|
Future<dynamic> replaceWithLearnPracticeView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required String title,
|
required String title,
|
||||||
required String subtitle,
|
required String subtitle,
|
||||||
required List<Map<String, dynamic>> practices,
|
required List<Map<String, dynamic>> practices,
|
||||||
|
|
@ -1927,7 +2060,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCoursePracticeView({
|
Future<dynamic> replaceWithCoursePracticeView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required int id,
|
required int id,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1944,8 +2077,8 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCoursePaymentView({
|
Future<dynamic> replaceWithCoursePaymentView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required _i38.Course course,
|
required _i40.Course course,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1975,7 +2108,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithFailureView({
|
Future<dynamic> replaceWithFailureView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required String label,
|
required String label,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1992,8 +2125,8 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCourseLessonView({
|
Future<dynamic> replaceWithCourseLessonView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required _i38.Course course,
|
required _i40.Course course,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -2009,8 +2142,8 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCourseLessonDetailView({
|
Future<dynamic> replaceWithCourseLessonDetailView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required _i39.CourseLesson lesson,
|
required _i41.CourseLesson lesson,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -2040,8 +2173,8 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCourseSubcategoryView({
|
Future<dynamic> replaceWithCourseSubcategoryView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required _i40.CourseCategory category,
|
required _i42.Category category,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -2057,8 +2190,8 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCourseView({
|
Future<dynamic> replaceWithCourseView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required _i41.CourseSubcategory subcategory,
|
required _i43.Subcategory subcategory,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -2074,7 +2207,7 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCoursePracticeQuestionView({
|
Future<dynamic> replaceWithCoursePracticeQuestionView({
|
||||||
_i37.Key? key,
|
_i38.Key? key,
|
||||||
required int id,
|
required int id,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -2089,4 +2222,18 @@ extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
transition: transition);
|
transition: transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<dynamic> replaceWithLearnSubcategoryView([
|
||||||
|
int? routerId,
|
||||||
|
bool preventDuplicates = true,
|
||||||
|
Map<String, String>? parameters,
|
||||||
|
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||||
|
transition,
|
||||||
|
]) async {
|
||||||
|
return replaceWith<dynamic>(Routes.learnSubcategoryView,
|
||||||
|
id: routerId,
|
||||||
|
preventDuplicates: preventDuplicates,
|
||||||
|
parameters: parameters,
|
||||||
|
transition: transition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,21 +50,23 @@ class DefaultFirebaseOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
static const FirebaseOptions android = FirebaseOptions(
|
static const FirebaseOptions android = FirebaseOptions(
|
||||||
apiKey: 'AIzaSyC7QlhcuSNte49CERnRKPrQbyLbwErIRmk',
|
apiKey: 'AIzaSyAi8e4PMFH8QQU11DWftHdNEu8WUP7i2ww',
|
||||||
appId: '1:574860813475:android:cd7fa6cf3a0527d97acb16',
|
appId: '1:900714037062:android:f11e3b69315b05304e6f47',
|
||||||
messagingSenderId: '574860813475',
|
messagingSenderId: '900714037062',
|
||||||
projectId: 'yimaru-lms-e834e',
|
projectId: 'yimaru-academy-5e7e2',
|
||||||
storageBucket: 'yimaru-lms-e834e.firebasestorage.app',
|
storageBucket: 'yimaru-academy-5e7e2.firebasestorage.app',
|
||||||
);
|
);
|
||||||
|
|
||||||
static const FirebaseOptions ios = FirebaseOptions(
|
static const FirebaseOptions ios = FirebaseOptions(
|
||||||
apiKey: 'AIzaSyBBcQ17JB6RBTjD7G7mh6Xf_FMUGxP5cC8',
|
apiKey: 'AIzaSyDbaGD47oUJOyn9n3b0pbH6ozmbGyIlOKk',
|
||||||
appId: '1:574860813475:ios:3ac9f7c4ae1771287acb16',
|
appId: '1:900714037062:ios:1caf8f24a4333b8e4e6f47',
|
||||||
messagingSenderId: '574860813475',
|
messagingSenderId: '900714037062',
|
||||||
projectId: 'yimaru-lms-e834e',
|
projectId: 'yimaru-academy-5e7e2',
|
||||||
storageBucket: 'yimaru-lms-e834e.firebasestorage.app',
|
storageBucket: 'yimaru-academy-5e7e2.firebasestorage.app',
|
||||||
androidClientId:
|
androidClientId:
|
||||||
'574860813475-3p3k63lkrfd113sn6jscgvdj0aigsg5s.apps.googleusercontent.com',
|
'900714037062-ngc0gc426sfnnjjr494g4vni46ne5uqv.apps.googleusercontent.com',
|
||||||
|
iosClientId:
|
||||||
|
'900714037062-35bg0hsou56hg37mbcbpiar9uti7tcku.apps.googleusercontent.com',
|
||||||
iosBundleId: 'com.yimaru.lms.app',
|
iosBundleId: 'com.yimaru.lms.app',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
23
lib/models/category.dart
Normal file
23
lib/models/category.dart
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'category.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Category {
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
final String? name;
|
||||||
|
|
||||||
|
@JsonKey(name: 'is_active')
|
||||||
|
final bool? isActive;
|
||||||
|
|
||||||
|
@JsonKey(name: 'total_count')
|
||||||
|
final int? totalCount;
|
||||||
|
|
||||||
|
const Category({this.id, this.name, this.isActive, this.totalCount});
|
||||||
|
|
||||||
|
factory Category.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CategoryFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$CategoryToJson(this);
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'course_category.dart';
|
part of 'category.dart';
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
CourseCategory _$CourseCategoryFromJson(Map<String, dynamic> json) =>
|
Category _$CategoryFromJson(Map<String, dynamic> json) => Category(
|
||||||
CourseCategory(
|
|
||||||
id: (json['id'] as num?)?.toInt(),
|
id: (json['id'] as num?)?.toInt(),
|
||||||
name: json['name'] as String?,
|
name: json['name'] as String?,
|
||||||
isActive: json['is_active'] as bool?,
|
isActive: json['is_active'] as bool?,
|
||||||
|
totalCount: (json['total_count'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$CourseCategoryToJson(CourseCategory instance) =>
|
Map<String, dynamic> _$CategoryToJson(Category instance) => <String, dynamic>{
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'name': instance.name,
|
'name': instance.name,
|
||||||
'is_active': instance.isActive,
|
'is_active': instance.isActive,
|
||||||
|
'total_count': instance.totalCount,
|
||||||
};
|
};
|
||||||
|
|
@ -6,32 +6,38 @@ part 'course.g.dart';
|
||||||
class Course {
|
class Course {
|
||||||
final int? id;
|
final int? id;
|
||||||
|
|
||||||
final String? level;
|
|
||||||
|
|
||||||
final String? title;
|
final String? title;
|
||||||
|
|
||||||
final String? thumbnail;
|
final String? thumbnail;
|
||||||
|
|
||||||
final String? description;
|
final String? description;
|
||||||
|
|
||||||
@JsonKey(name: 'course_id')
|
|
||||||
final int? courseId;
|
|
||||||
|
|
||||||
@JsonKey(name: 'is_active')
|
@JsonKey(name: 'is_active')
|
||||||
final bool? isActive;
|
final bool? isActive;
|
||||||
|
|
||||||
@JsonKey(name: 'display_order')
|
@JsonKey(name: 'category_id')
|
||||||
final int? displayOrder;
|
final int? categoryId;
|
||||||
|
|
||||||
const Course(
|
@JsonKey(name: 'total_count')
|
||||||
{this.id,
|
final int? totalCount;
|
||||||
this.level,
|
|
||||||
|
@JsonKey(name: 'intro_video_url')
|
||||||
|
final String? introVideoUrl;
|
||||||
|
|
||||||
|
@JsonKey(name: 'sub_category_id')
|
||||||
|
final int? subCategoryId;
|
||||||
|
|
||||||
|
const Course({
|
||||||
|
this.id,
|
||||||
this.title,
|
this.title,
|
||||||
this.isActive,
|
this.isActive,
|
||||||
this.courseId,
|
|
||||||
this.thumbnail,
|
this.thumbnail,
|
||||||
|
this.totalCount,
|
||||||
|
this.categoryId,
|
||||||
this.description,
|
this.description,
|
||||||
this.displayOrder});
|
this.introVideoUrl,
|
||||||
|
this.subCategoryId,
|
||||||
|
});
|
||||||
|
|
||||||
factory Course.fromJson(Map<String, dynamic> json) => _$CourseFromJson(json);
|
factory Course.fromJson(Map<String, dynamic> json) => _$CourseFromJson(json);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,22 +8,24 @@ part of 'course.dart';
|
||||||
|
|
||||||
Course _$CourseFromJson(Map<String, dynamic> json) => Course(
|
Course _$CourseFromJson(Map<String, dynamic> json) => Course(
|
||||||
id: (json['id'] as num?)?.toInt(),
|
id: (json['id'] as num?)?.toInt(),
|
||||||
level: json['level'] as String?,
|
|
||||||
title: json['title'] as String?,
|
title: json['title'] as String?,
|
||||||
isActive: json['is_active'] as bool?,
|
isActive: json['is_active'] as bool?,
|
||||||
courseId: (json['course_id'] as num?)?.toInt(),
|
|
||||||
thumbnail: json['thumbnail'] as String?,
|
thumbnail: json['thumbnail'] as String?,
|
||||||
|
totalCount: (json['total_count'] as num?)?.toInt(),
|
||||||
|
categoryId: (json['category_id'] as num?)?.toInt(),
|
||||||
description: json['description'] as String?,
|
description: json['description'] as String?,
|
||||||
displayOrder: (json['display_order'] as num?)?.toInt(),
|
introVideoUrl: json['intro_video_url'] as String?,
|
||||||
|
subCategoryId: (json['sub_category_id'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$CourseToJson(Course instance) => <String, dynamic>{
|
Map<String, dynamic> _$CourseToJson(Course instance) => <String, dynamic>{
|
||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'level': instance.level,
|
|
||||||
'title': instance.title,
|
'title': instance.title,
|
||||||
'thumbnail': instance.thumbnail,
|
'thumbnail': instance.thumbnail,
|
||||||
'description': instance.description,
|
'description': instance.description,
|
||||||
'course_id': instance.courseId,
|
|
||||||
'is_active': instance.isActive,
|
'is_active': instance.isActive,
|
||||||
'display_order': instance.displayOrder,
|
'category_id': instance.categoryId,
|
||||||
|
'total_count': instance.totalCount,
|
||||||
|
'intro_video_url': instance.introVideoUrl,
|
||||||
|
'sub_category_id': instance.subCategoryId,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'course_category.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class CourseCategory {
|
|
||||||
final int? id;
|
|
||||||
|
|
||||||
final String? name;
|
|
||||||
|
|
||||||
@JsonKey(name: 'is_active')
|
|
||||||
final bool? isActive;
|
|
||||||
|
|
||||||
const CourseCategory({this.id, this.name, this.isActive});
|
|
||||||
|
|
||||||
factory CourseCategory.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$CourseCategoryFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$CourseCategoryToJson(this);
|
|
||||||
}
|
|
||||||
|
|
@ -33,8 +33,8 @@ Map<String, dynamic> _$CourseLessonToJson(CourseLesson instance) =>
|
||||||
'visibility': instance.visibility,
|
'visibility': instance.visibility,
|
||||||
'description': instance.description,
|
'description': instance.description,
|
||||||
'video_url': instance.videoUrl,
|
'video_url': instance.videoUrl,
|
||||||
|
'vimeo_status': instance.vimeoStatus,
|
||||||
'instructor_id': instance.instructorId,
|
'instructor_id': instance.instructorId,
|
||||||
'sub_course_id': instance.courseId,
|
'sub_course_id': instance.courseId,
|
||||||
'vimeo_status': instance.vimeoStatus,
|
|
||||||
'display_order': instance.displayOrder,
|
'display_order': instance.displayOrder,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'course_subcategory.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class CourseSubcategory {
|
|
||||||
final int? id;
|
|
||||||
|
|
||||||
final String? title;
|
|
||||||
|
|
||||||
final String? thumbnail;
|
|
||||||
|
|
||||||
final String? description;
|
|
||||||
|
|
||||||
@JsonKey(name: 'is_active')
|
|
||||||
final bool? isActive;
|
|
||||||
|
|
||||||
@JsonKey(name: 'category_id')
|
|
||||||
final int? categoryId;
|
|
||||||
|
|
||||||
const CourseSubcategory(
|
|
||||||
{this.id,
|
|
||||||
this.title,
|
|
||||||
this.isActive,
|
|
||||||
this.thumbnail,
|
|
||||||
this.categoryId,
|
|
||||||
this.description});
|
|
||||||
|
|
||||||
factory CourseSubcategory.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$CourseSubcategoryFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$CourseSubcategoryToJson(this);
|
|
||||||
}
|
|
||||||
41
lib/models/level.dart
Normal file
41
lib/models/level.dart
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'level.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Level {
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
final String? title;
|
||||||
|
|
||||||
|
final String? thumbnail;
|
||||||
|
|
||||||
|
final String? description;
|
||||||
|
|
||||||
|
@JsonKey(name: 'is_active')
|
||||||
|
final bool? isActive;
|
||||||
|
|
||||||
|
@JsonKey(name: 'cefr_level')
|
||||||
|
final String? cerfLevel;
|
||||||
|
|
||||||
|
@JsonKey(name: 'total_count')
|
||||||
|
final int? totalCount;
|
||||||
|
|
||||||
|
@JsonKey(name: 'display_order')
|
||||||
|
final int? displayOrder;
|
||||||
|
|
||||||
|
const Level({
|
||||||
|
this.id,
|
||||||
|
this.title,
|
||||||
|
this.isActive,
|
||||||
|
this.cerfLevel,
|
||||||
|
this.thumbnail,
|
||||||
|
this.totalCount,
|
||||||
|
this.description,
|
||||||
|
this.displayOrder,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Level.fromJson(Map<String, dynamic> json) => _$LevelFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$LevelToJson(this);
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,29 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'course_subcategory.dart';
|
part of 'level.dart';
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
CourseSubcategory _$CourseSubcategoryFromJson(Map<String, dynamic> json) =>
|
Level _$LevelFromJson(Map<String, dynamic> json) => Level(
|
||||||
CourseSubcategory(
|
|
||||||
id: (json['id'] as num?)?.toInt(),
|
id: (json['id'] as num?)?.toInt(),
|
||||||
title: json['title'] as String?,
|
title: json['title'] as String?,
|
||||||
isActive: json['is_active'] as bool?,
|
isActive: json['is_active'] as bool?,
|
||||||
|
cerfLevel: json['cefr_level'] as String?,
|
||||||
thumbnail: json['thumbnail'] as String?,
|
thumbnail: json['thumbnail'] as String?,
|
||||||
categoryId: (json['category_id'] as num?)?.toInt(),
|
totalCount: (json['total_count'] as num?)?.toInt(),
|
||||||
description: json['description'] as String?,
|
description: json['description'] as String?,
|
||||||
|
displayOrder: (json['display_order'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$CourseSubcategoryToJson(CourseSubcategory instance) =>
|
Map<String, dynamic> _$LevelToJson(Level instance) => <String, dynamic>{
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'title': instance.title,
|
'title': instance.title,
|
||||||
'thumbnail': instance.thumbnail,
|
'thumbnail': instance.thumbnail,
|
||||||
'description': instance.description,
|
'description': instance.description,
|
||||||
'is_active': instance.isActive,
|
'is_active': instance.isActive,
|
||||||
'category_id': instance.categoryId,
|
'cefr_level': instance.cerfLevel,
|
||||||
|
'total_count': instance.totalCount,
|
||||||
|
'display_order': instance.displayOrder,
|
||||||
};
|
};
|
||||||
38
lib/models/module.dart
Normal file
38
lib/models/module.dart
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'module.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Module {
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
final String? title;
|
||||||
|
|
||||||
|
final String? description;
|
||||||
|
|
||||||
|
@JsonKey(name: 'icon_url')
|
||||||
|
final String? iconUrl;
|
||||||
|
|
||||||
|
@JsonKey(name: 'level_id')
|
||||||
|
final int? levelId;
|
||||||
|
|
||||||
|
@JsonKey(name: 'is_active')
|
||||||
|
final bool? isActive;
|
||||||
|
|
||||||
|
@JsonKey(name: 'display_order')
|
||||||
|
final int? displayOrder;
|
||||||
|
|
||||||
|
const Module({
|
||||||
|
this.id,
|
||||||
|
this.title,
|
||||||
|
this.iconUrl,
|
||||||
|
this.levelId,
|
||||||
|
this.isActive,
|
||||||
|
this.description,
|
||||||
|
this.displayOrder,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Module.fromJson(Map<String, dynamic> json) => _$ModuleFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$ModuleToJson(this);
|
||||||
|
}
|
||||||
27
lib/models/module.g.dart
Normal file
27
lib/models/module.g.dart
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'module.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
Module _$ModuleFromJson(Map<String, dynamic> json) => Module(
|
||||||
|
id: (json['id'] as num?)?.toInt(),
|
||||||
|
title: json['title'] as String?,
|
||||||
|
iconUrl: json['icon_url'] as String?,
|
||||||
|
levelId: (json['level_id'] as num?)?.toInt(),
|
||||||
|
isActive: json['is_active'] as bool?,
|
||||||
|
description: json['description'] as String?,
|
||||||
|
displayOrder: (json['display_order'] as num?)?.toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ModuleToJson(Module instance) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'title': instance.title,
|
||||||
|
'description': instance.description,
|
||||||
|
'icon_url': instance.iconUrl,
|
||||||
|
'level_id': instance.levelId,
|
||||||
|
'is_active': instance.isActive,
|
||||||
|
'display_order': instance.displayOrder,
|
||||||
|
};
|
||||||
|
|
@ -14,6 +14,6 @@ Option _$OptionFromJson(Map<String, dynamic> json) => Option(
|
||||||
|
|
||||||
Map<String, dynamic> _$OptionToJson(Option instance) => <String, dynamic>{
|
Map<String, dynamic> _$OptionToJson(Option instance) => <String, dynamic>{
|
||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'option_text': instance.optionText,
|
|
||||||
'is_correct': instance.isCorrect,
|
'is_correct': instance.isCorrect,
|
||||||
|
'option_text': instance.optionText,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
42
lib/models/subcategory.dart
Normal file
42
lib/models/subcategory.dart
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'subcategory.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Subcategory {
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
final String? name;
|
||||||
|
|
||||||
|
final String? description;
|
||||||
|
|
||||||
|
@JsonKey(name: 'is_active')
|
||||||
|
final bool? isActive;
|
||||||
|
|
||||||
|
@JsonKey(name: 'total_count')
|
||||||
|
final int? totalCount;
|
||||||
|
|
||||||
|
@JsonKey(name: 'category_id')
|
||||||
|
final int? categoryId;
|
||||||
|
|
||||||
|
@JsonKey(name: 'category_name')
|
||||||
|
final String? categoryName;
|
||||||
|
|
||||||
|
@JsonKey(name: 'display_order')
|
||||||
|
final int? displayOrder;
|
||||||
|
|
||||||
|
const Subcategory(
|
||||||
|
{this.id,
|
||||||
|
this.name,
|
||||||
|
this.isActive,
|
||||||
|
this.totalCount,
|
||||||
|
this.categoryId,
|
||||||
|
this.description,
|
||||||
|
this.categoryName,
|
||||||
|
this.displayOrder});
|
||||||
|
|
||||||
|
factory Subcategory.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SubcategoryFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$SubcategoryToJson(this);
|
||||||
|
}
|
||||||
30
lib/models/subcategory.g.dart
Normal file
30
lib/models/subcategory.g.dart
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'subcategory.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
Subcategory _$SubcategoryFromJson(Map<String, dynamic> json) => Subcategory(
|
||||||
|
id: (json['id'] as num?)?.toInt(),
|
||||||
|
name: json['name'] as String?,
|
||||||
|
isActive: json['is_active'] as bool?,
|
||||||
|
totalCount: (json['total_count'] as num?)?.toInt(),
|
||||||
|
categoryId: (json['category_id'] as num?)?.toInt(),
|
||||||
|
description: json['description'] as String?,
|
||||||
|
categoryName: json['category_name'] as String?,
|
||||||
|
displayOrder: (json['display_order'] as num?)?.toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SubcategoryToJson(Subcategory instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'description': instance.description,
|
||||||
|
'is_active': instance.isActive,
|
||||||
|
'total_count': instance.totalCount,
|
||||||
|
'category_id': instance.categoryId,
|
||||||
|
'category_name': instance.categoryName,
|
||||||
|
'display_order': instance.displayOrder,
|
||||||
|
};
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'user_model.g.dart';
|
part 'user.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class UserModel {
|
class User {
|
||||||
final String? email;
|
final String? email;
|
||||||
|
|
||||||
final String? gender;
|
final String? gender;
|
||||||
|
|
@ -40,7 +40,7 @@ class UserModel {
|
||||||
@JsonKey(name: 'profile_picture_url')
|
@JsonKey(name: 'profile_picture_url')
|
||||||
final String? profilePicture;
|
final String? profilePicture;
|
||||||
|
|
||||||
const UserModel({
|
const User({
|
||||||
this.email,
|
this.email,
|
||||||
this.region,
|
this.region,
|
||||||
this.gender,
|
this.gender,
|
||||||
|
|
@ -57,7 +57,7 @@ class UserModel {
|
||||||
this.profileCompleted,
|
this.profileCompleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
UserModel copyWith(
|
User copyWith(
|
||||||
{int? userId,
|
{int? userId,
|
||||||
String? email,
|
String? email,
|
||||||
String? gender,
|
String? gender,
|
||||||
|
|
@ -72,7 +72,7 @@ class UserModel {
|
||||||
bool? userInfoLoaded,
|
bool? userInfoLoaded,
|
||||||
bool? profileCompleted,
|
bool? profileCompleted,
|
||||||
String? profilePicture}) =>
|
String? profilePicture}) =>
|
||||||
UserModel(
|
User(
|
||||||
email: email ?? this.email,
|
email: email ?? this.email,
|
||||||
userId: userId ?? this.userId,
|
userId: userId ?? this.userId,
|
||||||
gender: gender ?? this.gender,
|
gender: gender ?? this.gender,
|
||||||
|
|
@ -88,8 +88,7 @@ class UserModel {
|
||||||
profilePicture: profilePicture ?? this.profilePicture,
|
profilePicture: profilePicture ?? this.profilePicture,
|
||||||
profileCompleted: profileCompleted ?? this.profileCompleted);
|
profileCompleted: profileCompleted ?? this.profileCompleted);
|
||||||
|
|
||||||
factory UserModel.fromJson(Map<String, dynamic> json) =>
|
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
|
||||||
_$UserModelFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$UserModelToJson(this);
|
Map<String, dynamic> toJson() => _$UserToJson(this);
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'user_model.dart';
|
part of 'user.dart';
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel(
|
User _$UserFromJson(Map<String, dynamic> json) => User(
|
||||||
email: json['email'] as String?,
|
email: json['email'] as String?,
|
||||||
region: json['region'] as String?,
|
region: json['region'] as String?,
|
||||||
gender: json['gender'] as String?,
|
gender: json['gender'] as String?,
|
||||||
|
|
@ -23,7 +23,7 @@ UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel(
|
||||||
profileCompleted: json['profile_completed'] as bool?,
|
profileCompleted: json['profile_completed'] as bool?,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
|
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
|
||||||
'email': instance.email,
|
'email': instance.email,
|
||||||
'gender': instance.gender,
|
'gender': instance.gender,
|
||||||
'region': instance.region,
|
'region': instance.region,
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:yimaru_app/models/level.dart';
|
||||||
import 'package:yimaru_app/models/question.dart';
|
import 'package:yimaru_app/models/question.dart';
|
||||||
import 'package:yimaru_app/models/course_subcategory.dart';
|
import 'package:yimaru_app/models/subcategory.dart';
|
||||||
import 'package:yimaru_app/models/course_category.dart';
|
import 'package:yimaru_app/models/category.dart';
|
||||||
import 'package:yimaru_app/models/course_lesson.dart';
|
import 'package:yimaru_app/models/course_lesson.dart';
|
||||||
import 'package:yimaru_app/models/course_progress.dart';
|
import 'package:yimaru_app/models/course_progress.dart';
|
||||||
import 'package:yimaru_app/models/course.dart';
|
import 'package:yimaru_app/models/course.dart';
|
||||||
import 'package:yimaru_app/models/practice.dart';
|
import 'package:yimaru_app/models/practice.dart';
|
||||||
import 'package:yimaru_app/models/practice_question.dart';
|
import 'package:yimaru_app/models/practice_question.dart';
|
||||||
import 'package:yimaru_app/models/user_model.dart';
|
import 'package:yimaru_app/models/user.dart';
|
||||||
import 'package:yimaru_app/services/dio_service.dart';
|
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/module.dart';
|
||||||
import '../ui/common/enmus.dart';
|
import '../ui/common/enmus.dart';
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
|
|
@ -58,7 +60,7 @@ class ApiService {
|
||||||
return {
|
return {
|
||||||
'status': ResponseStatus.success,
|
'status': ResponseStatus.success,
|
||||||
'message': 'Logged in successfully',
|
'message': 'Logged in successfully',
|
||||||
'data': UserModel.fromJson(response.data['data']),
|
'data': User.fromJson(response.data['data']),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
|
|
@ -86,7 +88,7 @@ class ApiService {
|
||||||
return {
|
return {
|
||||||
'status': ResponseStatus.success,
|
'status': ResponseStatus.success,
|
||||||
'message': 'Logged in successfully',
|
'message': 'Logged in successfully',
|
||||||
'data': UserModel.fromJson(response.data['data']),
|
'data': User.fromJson(response.data['data']),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
|
|
@ -113,7 +115,7 @@ class ApiService {
|
||||||
return {
|
return {
|
||||||
'status': ResponseStatus.success,
|
'status': ResponseStatus.success,
|
||||||
'message': 'Otp verified successfully',
|
'message': 'Otp verified successfully',
|
||||||
'data': UserModel.fromJson(response.data['data']),
|
'data': User.fromJson(response.data['data']),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
|
|
@ -212,7 +214,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GEt profile completion status
|
// GEt profile completion status
|
||||||
Future<Map<String, dynamic>> getProfileStatus(UserModel? user) async {
|
Future<Map<String, dynamic>> getProfileStatus(User? user) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.get(
|
Response response = await _service.dio.get(
|
||||||
'$kBaseUrl/$kUserBaseUrl/${user?.userId}/$kProfileStatusUrl',
|
'$kBaseUrl/$kUserBaseUrl/${user?.userId}/$kProfileStatusUrl',
|
||||||
|
|
@ -249,7 +251,7 @@ class ApiService {
|
||||||
return {
|
return {
|
||||||
'status': ResponseStatus.success,
|
'status': ResponseStatus.success,
|
||||||
'message': 'Profile fetched successfully',
|
'message': 'Profile fetched successfully',
|
||||||
'data': UserModel.fromJson(response.data['data']),
|
'data': User.fromJson(response.data['data']),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
|
|
@ -369,20 +371,20 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get course categories
|
// Get categories
|
||||||
Future<List<CourseCategory>> getCourseCategories() async {
|
Future<List<Category>> getCategories() async {
|
||||||
try {
|
try {
|
||||||
List<CourseCategory> categories = [];
|
List<Category> categories = [];
|
||||||
|
|
||||||
final Response response = await _service.dio
|
final Response response = await _service.dio.get(
|
||||||
.get('$kBaseUrl/$kCourseBaseUrl/$kCourseCategoryUrl');
|
'$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kCategoryUrl');
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var data = response.data;
|
var data = response.data;
|
||||||
var decodedData = data['data']['categories'] as List;
|
var decodedData = data['data']['categories'] as List;
|
||||||
categories = decodedData.map(
|
categories = decodedData.map(
|
||||||
(e) {
|
(e) {
|
||||||
return CourseCategory.fromJson(e);
|
return Category.fromJson(e);
|
||||||
},
|
},
|
||||||
).toList();
|
).toList();
|
||||||
return categories;
|
return categories;
|
||||||
|
|
@ -394,19 +396,19 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get course subcategory
|
// Get course subcategory
|
||||||
Future<List<CourseSubcategory>> getCourseSubcategories(int id) async {
|
Future<List<Subcategory>> getSubcategories(int id) async {
|
||||||
try {
|
try {
|
||||||
List<CourseSubcategory> subcategories = [];
|
List<Subcategory> subcategories = [];
|
||||||
|
|
||||||
final Response response = await _service.dio.get(
|
final Response response = await _service.dio
|
||||||
'$kBaseUrl/$kCourseBaseUrl/$kCourseCategoryUrl/$id/$kCoursesUrl');
|
.get('$kBaseUrl/$kCourseBaseUrl/$kCategoryUrl/$id/$kCoursesUrl');
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var data = response.data;
|
var data = response.data;
|
||||||
var decodedData = data['data']['courses'] as List;
|
var decodedData = data['data']['courses'] as List;
|
||||||
subcategories = decodedData.map(
|
subcategories = decodedData.map(
|
||||||
(e) {
|
(e) {
|
||||||
return CourseSubcategory.fromJson(e);
|
return Subcategory.fromJson(e);
|
||||||
},
|
},
|
||||||
).toList();
|
).toList();
|
||||||
return subcategories;
|
return subcategories;
|
||||||
|
|
@ -418,28 +420,28 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get courses
|
// Get courses
|
||||||
Future<List<Course>> getCourses(int id) async {
|
// Future<List<Course>> getCourses(int id) async {
|
||||||
try {
|
// try {
|
||||||
List<Course> courses = [];
|
// List<Course> courses = [];
|
||||||
|
//
|
||||||
final Response response = await _service.dio
|
// final Response response = await _service.dio
|
||||||
.get('$kBaseUrl/$kCourseBaseUrl/$kCoursesUrl/$id/$kSubcoursesUrl');
|
// .get('$kBaseUrl/$kCourseBaseUrl/$kCoursesUrl/$id/$kSubcoursesUrl');
|
||||||
|
//
|
||||||
if (response.statusCode == 200) {
|
// if (response.statusCode == 200) {
|
||||||
var data = response.data;
|
// var data = response.data;
|
||||||
var decodedData = data['data']['sub_courses'] as List;
|
// var decodedData = data['data']['sub_courses'] as List;
|
||||||
courses = decodedData.map(
|
// courses = decodedData.map(
|
||||||
(e) {
|
// (e) {
|
||||||
return Course.fromJson(e);
|
// return Course.fromJson(e);
|
||||||
},
|
// },
|
||||||
).toList();
|
// ).toList();
|
||||||
return courses;
|
// return courses;
|
||||||
}
|
// }
|
||||||
return [];
|
// return [];
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
return [];
|
// return [];
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Get course progress
|
// Get course progress
|
||||||
Future<List<CourseProgress>> getCourseProgress(int id) async {
|
Future<List<CourseProgress>> getCourseProgress(int id) async {
|
||||||
|
|
@ -576,4 +578,100 @@ class ApiService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get learn subcategories
|
||||||
|
Future<List<Subcategory>> getLearnSubcategories() async {
|
||||||
|
try {
|
||||||
|
List<Subcategory> learnSubcategories = [];
|
||||||
|
|
||||||
|
final Response response = await _service.dio.get(
|
||||||
|
'$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kLearnSubcategoriesUrl');
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
var data = response.data;
|
||||||
|
var decodedData = data['data']['sub_categories'] as List;
|
||||||
|
learnSubcategories = decodedData.map(
|
||||||
|
(e) {
|
||||||
|
return Subcategory.fromJson(e);
|
||||||
|
},
|
||||||
|
).toList();
|
||||||
|
return learnSubcategories;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get courses
|
||||||
|
Future<List<Course>> getCourses(int id) async {
|
||||||
|
try {
|
||||||
|
List<Course> courses = [];
|
||||||
|
|
||||||
|
final Response response = await _service.dio.get(
|
||||||
|
'$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kSubcategoriesUrl/$id/$kCoursesUrl');
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
var data = response.data;
|
||||||
|
var decodedData = data['data']['courses'] as List;
|
||||||
|
courses = decodedData.map(
|
||||||
|
(e) {
|
||||||
|
return Course.fromJson(e);
|
||||||
|
},
|
||||||
|
).toList();
|
||||||
|
return courses;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get levels
|
||||||
|
Future<List<Level>> getLevels(int id) async {
|
||||||
|
try {
|
||||||
|
List<Level> levels = [];
|
||||||
|
|
||||||
|
final Response response = await _service.dio.get(
|
||||||
|
'$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kCoursesUrl/$id/$kLevelsUrl');
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
var data = response.data;
|
||||||
|
var decodedData = data['data']['levels'] as List;
|
||||||
|
levels = decodedData.map(
|
||||||
|
(e) {
|
||||||
|
return Level.fromJson(e);
|
||||||
|
},
|
||||||
|
).toList();
|
||||||
|
return levels;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get modules
|
||||||
|
Future<List<Module>> getModules(int id) async {
|
||||||
|
try {
|
||||||
|
List<Module> modules = [];
|
||||||
|
|
||||||
|
final Response response = await _service.dio.get(
|
||||||
|
'$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kLevelsUrl/$id/$kModulesUrl');
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
var data = response.data;
|
||||||
|
var decodedData = data['data']['modules'] as List;
|
||||||
|
modules = decodedData.map(
|
||||||
|
(e) {
|
||||||
|
return Module.fromJson(e);
|
||||||
|
},
|
||||||
|
).toList();
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:stacked/stacked.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/user_model.dart';
|
import 'package:yimaru_app/models/user.dart';
|
||||||
import 'package:yimaru_app/services/secure_storage_service.dart';
|
import 'package:yimaru_app/services/secure_storage_service.dart';
|
||||||
|
|
||||||
class AuthenticationService with ListenableServiceMixin {
|
class AuthenticationService with ListenableServiceMixin {
|
||||||
|
|
@ -8,9 +8,9 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
final _secureService = locator<SecureStorageService>();
|
final _secureService = locator<SecureStorageService>();
|
||||||
|
|
||||||
// User data
|
// User data
|
||||||
UserModel? _user;
|
User? _user;
|
||||||
|
|
||||||
UserModel? get user => _user;
|
User? get user => _user;
|
||||||
|
|
||||||
// Initialization
|
// Initialization
|
||||||
AuthenticationService() {
|
AuthenticationService() {
|
||||||
|
|
@ -51,7 +51,7 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
await _secureService.setString('accessToken', data['accessToken']);
|
await _secureService.setString('accessToken', data['accessToken']);
|
||||||
await _secureService.setString('refreshToken', data['refreshToken']);
|
await _secureService.setString('refreshToken', data['refreshToken']);
|
||||||
|
|
||||||
_user = UserModel(
|
_user = User(
|
||||||
userId: await _secureService.getInt('userId'),
|
userId: await _secureService.getInt('userId'),
|
||||||
accessToken: await _secureService.getString('accessToken'),
|
accessToken: await _secureService.getString('accessToken'),
|
||||||
refreshToken: await _secureService.getString('refreshToken'),
|
refreshToken: await _secureService.getString('refreshToken'),
|
||||||
|
|
@ -67,7 +67,6 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
profileCompleted: await _secureService.getBool('profileCompleted'),
|
profileCompleted: await _secureService.getBool('profileCompleted'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,7 +82,7 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save user data
|
// Save user data
|
||||||
Future<void> saveUserData(UserModel data) async {
|
Future<void> saveUserData(User data) async {
|
||||||
await _secureService.setBool('userInfoLoaded', true);
|
await _secureService.setBool('userInfoLoaded', true);
|
||||||
await _secureService.setBool(
|
await _secureService.setBool(
|
||||||
'profileCompleted', data.profileCompleted ?? false);
|
'profileCompleted', data.profileCompleted ?? false);
|
||||||
|
|
@ -96,7 +95,7 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
await _secureService.setString('firstName', data.firstName ?? '');
|
await _secureService.setString('firstName', data.firstName ?? '');
|
||||||
await _secureService.setString('occupation', data.occupation ?? '');
|
await _secureService.setString('occupation', data.occupation ?? '');
|
||||||
|
|
||||||
_user = UserModel(
|
_user = User(
|
||||||
email: data.email,
|
email: data.email,
|
||||||
gender: data.gender,
|
gender: data.gender,
|
||||||
region: data.region,
|
region: data.region,
|
||||||
|
|
@ -148,8 +147,8 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user data
|
// Get user data
|
||||||
Future<UserModel?> getUser() async {
|
Future<User?> getUser() async {
|
||||||
_user = UserModel(
|
_user = User(
|
||||||
userId: await _secureService.getInt('userId'),
|
userId: await _secureService.getInt('userId'),
|
||||||
email: await _secureService.getString('email'),
|
email: await _secureService.getString('email'),
|
||||||
region: await _secureService.getString('region'),
|
region: await _secureService.getString('region'),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
import 'package:yimaru_app/app/app.router.dart';
|
import 'package:yimaru_app/app/app.router.dart';
|
||||||
import 'package:yimaru_app/models/user_model.dart';
|
import 'package:yimaru_app/models/user.dart';
|
||||||
import 'package:yimaru_app/services/authentication_service.dart';
|
import 'package:yimaru_app/services/authentication_service.dart';
|
||||||
|
|
||||||
import '../app/app.locator.dart';
|
import '../app/app.locator.dart';
|
||||||
|
|
@ -135,7 +135,7 @@ class DioService {
|
||||||
|
|
||||||
// Refresh token
|
// Refresh token
|
||||||
Future<bool> _refreshToken() async {
|
Future<bool> _refreshToken() async {
|
||||||
final UserModel? user = await _authenticationService.getUser();
|
final User? user = await _authenticationService.getUser();
|
||||||
|
|
||||||
if (user?.refreshToken == null) return false;
|
if (user?.refreshToken == null) return false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
String kBaseUrl = 'https://api.yimaruacademy.com';
|
String kBaseUrl = 'https://api.yimaruacademy.com';
|
||||||
|
|
||||||
|
String kApiUrl = 'api';
|
||||||
|
|
||||||
|
String kApiVersionUrl = 'v1';
|
||||||
|
|
||||||
|
String kLevelsUrl = 'levels';
|
||||||
|
|
||||||
String kCoursesUrl = 'courses';
|
String kCoursesUrl = 'courses';
|
||||||
|
|
||||||
|
String kModulesUrl = 'modules';
|
||||||
|
|
||||||
String kRegisterUrl = 'register';
|
String kRegisterUrl = 'register';
|
||||||
|
|
||||||
|
String kCategoryUrl = 'categories';
|
||||||
|
|
||||||
String kCoursePractice = 'by-owner';
|
String kCoursePractice = 'by-owner';
|
||||||
|
|
||||||
String kUserBaseUrl = 'api/v1/user';
|
String kUserBaseUrl = 'api/v1/user';
|
||||||
|
|
@ -20,10 +30,10 @@ String kCompleteLessonUrl = 'complete';
|
||||||
|
|
||||||
String kResetPassword = 'resetPassword';
|
String kResetPassword = 'resetPassword';
|
||||||
|
|
||||||
String kCourseCategoryUrl = 'categories';
|
|
||||||
|
|
||||||
String kRequestResetCode = 'sendResetCode';
|
String kRequestResetCode = 'sendResetCode';
|
||||||
|
|
||||||
|
String kSubcategoriesUrl = 'sub-categories';
|
||||||
|
|
||||||
String kPublishedVideos = 'videos/published';
|
String kPublishedVideos = 'videos/published';
|
||||||
|
|
||||||
String kCoursePracticeQuestions = 'questions';
|
String kCoursePracticeQuestions = 'questions';
|
||||||
|
|
@ -36,6 +46,8 @@ String kLoginUrl = 'api/v1/auth/customer-login';
|
||||||
|
|
||||||
String kPracticeBaseUrl = 'api/v1/question-sets';
|
String kPracticeBaseUrl = 'api/v1/question-sets';
|
||||||
|
|
||||||
|
String kCourseManagementUrl = 'course-management';
|
||||||
|
|
||||||
String kProfileStatusUrl = 'is-profile-completed';
|
String kProfileStatusUrl = 'is-profile-completed';
|
||||||
|
|
||||||
String kCourseBaseUrl = 'api/v1/course-management';
|
String kCourseBaseUrl = 'api/v1/course-management';
|
||||||
|
|
@ -50,10 +62,12 @@ String kCourseProgressUrl = 'api/v1/progress/courses';
|
||||||
|
|
||||||
String kAssessmentsUrl = 'api/v1/assessment/questions';
|
String kAssessmentsUrl = 'api/v1/assessment/questions';
|
||||||
|
|
||||||
|
String kLearnSubcategoriesUrl = 'human-language/sub-categories';
|
||||||
|
|
||||||
String kEmptyImagePath = '/data/user/0/com.yimaru.lms.app/app_flutter';
|
String kEmptyImagePath = '/data/user/0/com.yimaru.lms.app/app_flutter';
|
||||||
|
|
||||||
String kSampleVideoUrl =
|
String kSampleVideoUrl =
|
||||||
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4';
|
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4';
|
||||||
|
|
||||||
String kServerClientId =
|
String kServerClientId =
|
||||||
'574860813475-n5o17gpprdqmhcml99tiqhafb17rob0r.apps.googleusercontent.com';
|
'900714037062-24ria5fcfet71o3vde8f6gsvsj1n68ec.apps.googleusercontent.com';
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ const String ksHomeBottomSheetDescription =
|
||||||
const String ksPrivacyPolicy =
|
const String ksPrivacyPolicy =
|
||||||
'A brief, simple overview of Yimaru’s commitment to user privacy. Our goal is to be transparent about the data we collect and how we use it to enhance your learning experience.';
|
'A brief, simple overview of Yimaru’s commitment to user privacy. Our goal is to be transparent about the data we collect and how we use it to enhance your learning experience.';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const String ksTerms = """
|
const String ksTerms = """
|
||||||
<p style="color:#9C2C91;font-size:13px;">
|
<p style="color:#9C2C91;font-size:13px;">
|
||||||
Last updated: October 26, 2025
|
Last updated: October 26, 2025
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ enum StateObjects {
|
||||||
register,
|
register,
|
||||||
verifyOtp,
|
verifyOtp,
|
||||||
resendOtp,
|
resendOtp,
|
||||||
|
learnLevels,
|
||||||
|
learnModules,
|
||||||
|
learnCourses,
|
||||||
profileImage,
|
profileImage,
|
||||||
courseLessons,
|
courseLessons,
|
||||||
profileUpdate,
|
profileUpdate,
|
||||||
|
|
@ -41,6 +44,7 @@ enum StateObjects {
|
||||||
courseCategories,
|
courseCategories,
|
||||||
profileCompletion,
|
profileCompletion,
|
||||||
registerWithGoogle,
|
registerWithGoogle,
|
||||||
|
learnSubcategories,
|
||||||
learnPracticeSample,
|
learnPracticeSample,
|
||||||
learnPracticeAnswer,
|
learnPracticeAnswer,
|
||||||
loginWithPhoneNumber,
|
loginWithPhoneNumber,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:math';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'app_colors.dart';
|
import 'app_colors.dart';
|
||||||
|
|
||||||
// Split full name
|
// Split full name
|
||||||
Map<String, String> splitFullName(String fullName) {
|
Map<String, String> splitFullName(String fullName) {
|
||||||
final parts = fullName.trim().split(RegExp(r'\s+'));
|
final parts = fullName.trim().split(RegExp(r'\s+'));
|
||||||
|
|
|
||||||
|
|
@ -127,8 +127,8 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
|
||||||
|
|
||||||
Widget _buildPrivacyPolicy(AccountPrivacyViewModel viewModel) =>
|
Widget _buildPrivacyPolicy(AccountPrivacyViewModel viewModel) =>
|
||||||
CustomListTile(
|
CustomListTile(
|
||||||
icon: Icons.shield_moon_outlined,
|
|
||||||
title: 'Privacy Policy',
|
title: 'Privacy Policy',
|
||||||
|
icon: Icons.shield_moon_outlined,
|
||||||
onTap: () async => await viewModel.navigateToPrivacyPolicy(),
|
onTap: () async => await viewModel.navigateToPrivacyPolicy(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,12 @@ class AccountPrivacyViewModel extends BaseViewModel {
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
|
Future<void> navigateToTerms() async =>
|
||||||
|
await _navigationService.navigateToTermsAndConditionsView();
|
||||||
|
|
||||||
Future<void> navigateToLanguage() async =>
|
Future<void> navigateToLanguage() async =>
|
||||||
await _navigationService.navigateToLanguageView();
|
await _navigationService.navigateToLanguageView();
|
||||||
|
|
||||||
Future<void> navigateToPrivacyPolicy() async =>
|
Future<void> navigateToPrivacyPolicy() async =>
|
||||||
await _navigationService.navigateToPrivacyPolicyView();
|
await _navigationService.navigateToPrivacyPolicyView();
|
||||||
|
|
||||||
Future<void> navigateToTerms() async =>
|
|
||||||
await _navigationService.navigateToTermsAndConditionsView();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
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/views/assessment/screens/Assessment_form_screen.dart';
|
import 'package:yimaru_app/ui/views/assessment/screens/assessment_questions_screen.dart';
|
||||||
import 'package:yimaru_app/ui/views/assessment/screens/assessment_intro_screen.dart';
|
import 'package:yimaru_app/ui/views/assessment/screens/assessment_intro_screen.dart';
|
||||||
import 'package:yimaru_app/ui/views/assessment/screens/assessment_result_screen.dart';
|
import 'package:yimaru_app/ui/views/assessment/screens/assessment_result_screen.dart';
|
||||||
import 'package:yimaru_app/ui/views/assessment/screens/start_lesson_screen.dart';
|
import 'package:yimaru_app/ui/views/assessment/screens/start_lesson_screen.dart';
|
||||||
|
|
@ -51,7 +51,7 @@ class AssessmentView extends StackedView<AssessmentViewModel> {
|
||||||
|
|
||||||
Widget _buildAssessmentIntro() => const AssessmentIntroScreen();
|
Widget _buildAssessmentIntro() => const AssessmentIntroScreen();
|
||||||
|
|
||||||
Widget _buildAssessment() => const AssessmentFormScreen();
|
Widget _buildAssessment() => const AssessmentQuestionsScreen();
|
||||||
|
|
||||||
Widget _buildAssessmentResult() => const AssessmentResultScreen();
|
Widget _buildAssessmentResult() => const AssessmentResultScreen();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,14 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
|
|
||||||
int get currentQuestion => _currentQuestion;
|
int get currentQuestion => _currentQuestion;
|
||||||
|
|
||||||
ProficiencyLevels _proficiencyLevel = ProficiencyLevels.none;
|
|
||||||
|
|
||||||
ProficiencyLevels get proficiencyLevel => _proficiencyLevel;
|
|
||||||
|
|
||||||
List<Question> _assessments = [];
|
List<Question> _assessments = [];
|
||||||
|
|
||||||
List<Question> get assessments => _assessments;
|
List<Question> get assessments => _assessments;
|
||||||
|
|
||||||
|
ProficiencyLevels _proficiencyLevel = ProficiencyLevels.none;
|
||||||
|
|
||||||
|
ProficiencyLevels get proficiencyLevel => _proficiencyLevel;
|
||||||
|
|
||||||
final Map<String, dynamic> _selectedAnswers = {};
|
final Map<String, dynamic> _selectedAnswers = {};
|
||||||
|
|
||||||
Map<String, dynamic> get selectedAnswers => _selectedAnswers;
|
Map<String, dynamic> get selectedAnswers => _selectedAnswers;
|
||||||
|
|
@ -55,20 +55,6 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
Map<String, dynamic> get userData => _userData;
|
Map<String, dynamic> get userData => _userData;
|
||||||
|
|
||||||
// Assessment
|
// Assessment
|
||||||
int countCorrectAnswersUntil(int untilQuestion) {
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
for (int i = 1; i <= untilQuestion; i++) {
|
|
||||||
final answer = _selectedAnswers[i.toString()];
|
|
||||||
|
|
||||||
if (answer is Map<String, dynamic> && answer['correct'] == true) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> evaluateAssessment() {
|
Map<String, dynamic> evaluateAssessment() {
|
||||||
if (_currentQuestion == 5) {
|
if (_currentQuestion == 5) {
|
||||||
// A1
|
// A1
|
||||||
|
|
@ -111,6 +97,24 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int countCorrectAnswersUntil(int untilQuestion) {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i <= untilQuestion; i++) {
|
||||||
|
final answer = _selectedAnswers[i.toString()];
|
||||||
|
|
||||||
|
if (answer is Map<String, dynamic> && answer['correct'] == true) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSelectedAnswer({required int question, required String answer}) {
|
||||||
|
return _selectedAnswers[question.toString()]?['option'] == answer;
|
||||||
|
}
|
||||||
|
|
||||||
void setSelectedAnswer({required int question, required Option? option}) {
|
void setSelectedAnswer({required int question, required Option? option}) {
|
||||||
bool correct = false;
|
bool correct = false;
|
||||||
if (option?.isCorrect ?? false) {
|
if (option?.isCorrect ?? false) {
|
||||||
|
|
@ -133,43 +137,18 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSelectedAnswer({required int question, required String answer}) {
|
// User data
|
||||||
return _selectedAnswers[question.toString()]?['option'] == answer;
|
void clearUserData() {
|
||||||
}
|
_userData.clear();
|
||||||
|
|
||||||
// Add user data
|
|
||||||
void initUserData(Map<String, dynamic> data) {
|
|
||||||
clearUserData();
|
|
||||||
_userData.addAll(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void addUserData(Map<String, dynamic> data) {
|
void addUserData(Map<String, dynamic> data) {
|
||||||
_userData.addAll(data);
|
_userData.addAll(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearUserData() {
|
void initUserData(Map<String, dynamic> data) {
|
||||||
_userData.clear();
|
clearUserData();
|
||||||
}
|
_userData.addAll(data);
|
||||||
|
|
||||||
// Dialog
|
|
||||||
Future<bool?> showAbortDialog() async {
|
|
||||||
DialogResponse? response = await _dialogService.showDialog(
|
|
||||||
cancelTitle: 'No',
|
|
||||||
buttonTitle: 'Yes',
|
|
||||||
barrierDismissible: true,
|
|
||||||
title: 'Abort Assessment',
|
|
||||||
cancelTitleColor: kcDarkGrey,
|
|
||||||
buttonTitleColor: kcPrimaryColor,
|
|
||||||
description: 'Are you sure to abort the assessment ?',
|
|
||||||
);
|
|
||||||
return response?.confirmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> abort() async {
|
|
||||||
bool? response = await showAbortDialog();
|
|
||||||
if (response != null && response) {
|
|
||||||
next(page: 3);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Question navigation
|
// Question navigation
|
||||||
|
|
@ -206,19 +185,6 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// In-app navigation
|
// In-app navigation
|
||||||
void next({int? page}) async {
|
|
||||||
if (page == null) {
|
|
||||||
if (_previousPage != 0) {
|
|
||||||
_currentPage = _previousPage;
|
|
||||||
} else {
|
|
||||||
_currentPage++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_previousPage = _currentPage;
|
|
||||||
_currentPage = page;
|
|
||||||
}
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
void goBack() {
|
void goBack() {
|
||||||
if (_currentPage == 0) {
|
if (_currentPage == 0) {
|
||||||
|
|
@ -236,23 +202,51 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void next({int? page}) async {
|
||||||
|
if (page == null) {
|
||||||
|
if (_previousPage != 0) {
|
||||||
|
_currentPage = _previousPage;
|
||||||
|
} else {
|
||||||
|
_currentPage++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_previousPage = _currentPage;
|
||||||
|
_currentPage = page;
|
||||||
|
}
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dialog
|
||||||
|
Future<void> abort() async {
|
||||||
|
bool? response = await showAbortDialog();
|
||||||
|
if (response != null && response) {
|
||||||
|
next(page: 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool?> showAbortDialog() async {
|
||||||
|
DialogResponse? response = await _dialogService.showDialog(
|
||||||
|
cancelTitle: 'No',
|
||||||
|
buttonTitle: 'Yes',
|
||||||
|
barrierDismissible: true,
|
||||||
|
title: 'Abort Assessment',
|
||||||
|
cancelTitleColor: kcDarkGrey,
|
||||||
|
buttonTitleColor: kcPrimaryColor,
|
||||||
|
description: 'Are you sure to abort the assessment ?',
|
||||||
|
);
|
||||||
|
return response?.confirmed;
|
||||||
|
}
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
Future<void> navigateToLanguage() async =>
|
|
||||||
await _navigationService.navigateToLanguageView();
|
|
||||||
|
|
||||||
Future<void> replaceWithHome() async =>
|
Future<void> replaceWithHome() async =>
|
||||||
await _navigationService.clearStackAndShow(Routes.homeView);
|
await _navigationService.clearStackAndShow(Routes.homeView);
|
||||||
|
|
||||||
// Remote api call
|
Future<void> navigateToLanguage() async =>
|
||||||
Future<void> getAssessments() async => await runBusyFuture(_getAssessments());
|
await _navigationService.navigateToLanguageView();
|
||||||
|
|
||||||
Future<void> _getAssessments() async {
|
// Remote api call
|
||||||
if (await _statusChecker.checkConnection()) {
|
|
||||||
_assessments = await _apiService.getAssessments();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete profile
|
// Complete profile
|
||||||
Future<void> completeProfile() async =>
|
Future<void> completeProfile() async =>
|
||||||
|
|
@ -272,4 +266,13 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assessments
|
||||||
|
Future<void> _getAssessments() async {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
_assessments = await _apiService.getAssessments();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getAssessments() async => await runBusyFuture(_getAssessments());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
|
||||||
import '../assessment_viewmodel.dart';
|
import '../assessment_viewmodel.dart';
|
||||||
import 'assessment_loading_screen.dart';
|
import 'assessment_loading_screen.dart';
|
||||||
|
|
||||||
class AssessmentFormScreen extends ViewModelWidget<AssessmentViewModel> {
|
class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
const AssessmentFormScreen({super.key});
|
const AssessmentQuestionsScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
|
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
|
||||||
|
|
@ -109,14 +109,14 @@ class AssessmentFormScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemCount: viewModel.assessments[index].options?.length,
|
itemCount: viewModel.assessments[index].options?.length,
|
||||||
itemBuilder: (context, inner) => _buildAnswer(
|
itemBuilder: (context, inner) => _buildAnswer(
|
||||||
|
onTap: () => viewModel.setSelectedAnswer(
|
||||||
|
question: index + 1,
|
||||||
|
option: viewModel.assessments[index].options?[inner]),
|
||||||
title: viewModel.assessments[index].options?[inner].optionText ?? '',
|
title: viewModel.assessments[index].options?[inner].optionText ?? '',
|
||||||
selected: viewModel.isSelectedAnswer(
|
selected: viewModel.isSelectedAnswer(
|
||||||
question: index + 1,
|
question: index + 1,
|
||||||
answer: viewModel.assessments[index].options?[inner].optionText ??
|
answer: viewModel.assessments[index].options?[inner].optionText ??
|
||||||
''),
|
''),
|
||||||
onTap: () => viewModel.setSelectedAnswer(
|
|
||||||
question: index + 1,
|
|
||||||
option: viewModel.assessments[index].options?[inner]),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
import '../../../models/course_detail.dart';
|
import '../../../models/course_detail.dart';
|
||||||
import '../../../models/course_subcategory.dart';
|
import '../../../models/subcategory.dart';
|
||||||
import '../../common/app_colors.dart';
|
import '../../common/app_colors.dart';
|
||||||
import '../../common/enmus.dart';
|
import '../../common/enmus.dart';
|
||||||
import '../../common/ui_helpers.dart';
|
import '../../common/ui_helpers.dart';
|
||||||
|
|
@ -12,7 +12,7 @@ import '../../widgets/small_app_bar.dart';
|
||||||
import 'course_viewmodel.dart';
|
import 'course_viewmodel.dart';
|
||||||
|
|
||||||
class CourseView extends StackedView<CourseViewModel> {
|
class CourseView extends StackedView<CourseViewModel> {
|
||||||
final CourseSubcategory subcategory;
|
final Subcategory subcategory;
|
||||||
|
|
||||||
const CourseView({Key? key, required this.subcategory}) : super(key: key);
|
const CourseView({Key? key, required this.subcategory}) : super(key: key);
|
||||||
|
|
||||||
|
|
@ -83,12 +83,12 @@ class CourseView extends StackedView<CourseViewModel> {
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
'${subcategory.title ?? ''} courses',
|
'${subcategory.name ?? ''} courses',
|
||||||
style: style18DG700,
|
style: style18DG700,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubtitle() => Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'Explore variety of courses on ${subcategory.title ?? ''}.',
|
'Explore variety of courses on ${subcategory.name ?? ''}.',
|
||||||
style: style14DG400,
|
style: style14DG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -105,6 +105,7 @@ class CourseView extends StackedView<CourseViewModel> {
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: viewModel.courseDetail.length,
|
itemCount: viewModel.courseDetail.length,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
separatorBuilder: (context, index) => verticalSpaceSmall,
|
||||||
itemBuilder: (context, index) => _buildTile(
|
itemBuilder: (context, index) => _buildTile(
|
||||||
courseDetail: viewModel.courseDetail[index],
|
courseDetail: viewModel.courseDetail[index],
|
||||||
onCourseTap: () async => await viewModel
|
onCourseTap: () async => await viewModel
|
||||||
|
|
@ -112,7 +113,6 @@ class CourseView extends StackedView<CourseViewModel> {
|
||||||
onPracticeTap: () async => await viewModel.navigateToCoursePractice(
|
onPracticeTap: () async => await viewModel.navigateToCoursePractice(
|
||||||
viewModel.courseDetail[index].course?.id ?? 0),
|
viewModel.courseDetail[index].course?.id ?? 0),
|
||||||
),
|
),
|
||||||
separatorBuilder: (context, index) => verticalSpaceSmall,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTile({
|
Widget _buildTile({
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import '../../common/enmus.dart';
|
||||||
|
|
||||||
class CourseViewModel extends BaseViewModel {
|
class CourseViewModel extends BaseViewModel {
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
|
|
||||||
final _courseService = locator<CourseService>();
|
final _courseService = locator<CourseService>();
|
||||||
|
|
||||||
final _statusChecker = locator<StatusCheckerService>();
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
|
|
@ -26,15 +25,15 @@ class CourseViewModel extends BaseViewModel {
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
Future<void> navigateToCoursePayment(Course course) async =>
|
|
||||||
_navigationService.navigateToCoursePaymentView(course: course);
|
|
||||||
|
|
||||||
Future<void> navigateToCoursePractice(int id) =>
|
Future<void> navigateToCoursePractice(int id) =>
|
||||||
_navigationService.navigateToCoursePracticeView(id: id);
|
_navigationService.navigateToCoursePracticeView(id: id);
|
||||||
|
|
||||||
|
Future<void> navigateToCoursePayment(Course course) async =>
|
||||||
|
_navigationService.navigateToCoursePaymentView(course: course);
|
||||||
|
|
||||||
// Remote api call
|
// Remote api call
|
||||||
|
|
||||||
// Sub course
|
// Course detail
|
||||||
Future<void> getCourseDetails(int id) async =>
|
Future<void> getCourseDetails(int id) async =>
|
||||||
await runBusyFuture(_getCourseDetails(id),
|
await runBusyFuture(_getCourseDetails(id),
|
||||||
busyObject: StateObjects.courses);
|
busyObject: StateObjects.courses);
|
||||||
|
|
@ -42,8 +41,8 @@ class CourseViewModel extends BaseViewModel {
|
||||||
Future<void> _getCourseDetails(int id) async {
|
Future<void> _getCourseDetails(int id) async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
_courseDetail = await _courseService.getCoursesDetail(id);
|
_courseDetail = await _courseService.getCoursesDetail(id);
|
||||||
_courseDetail.sort((a, b) =>
|
// _courseDetail.sort((a, b) =>
|
||||||
(a.course?.displayOrder ?? 0).compareTo(b.course?.displayOrder ?? 0));
|
// (a.course?.displayOrder ?? 0).compareTo(b.course?.displayOrder ?? 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
import '../../../models/course_category.dart';
|
import '../../../models/category.dart';
|
||||||
import '../../common/app_colors.dart';
|
import '../../common/app_colors.dart';
|
||||||
import '../../common/enmus.dart';
|
import '../../common/enmus.dart';
|
||||||
import '../../common/ui_helpers.dart';
|
import '../../common/ui_helpers.dart';
|
||||||
|
|
@ -15,7 +15,7 @@ class CourseCategoryView extends StackedView<CourseCategoryViewModel> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onViewModelReady(CourseCategoryViewModel viewModel) async {
|
void onViewModelReady(CourseCategoryViewModel viewModel) async {
|
||||||
await viewModel.getCourseCategories();
|
await viewModel.getCategories();
|
||||||
super.onViewModelReady(viewModel);
|
super.onViewModelReady(viewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@ class CourseCategoryView extends StackedView<CourseCategoryViewModel> {
|
||||||
|
|
||||||
//
|
//
|
||||||
Widget _buildTile({
|
Widget _buildTile({
|
||||||
required CourseCategory category,
|
required Category category,
|
||||||
required GestureTapCallback onTap,
|
required GestureTapCallback onTap,
|
||||||
}) =>
|
}) =>
|
||||||
CourseCategoryCard(
|
CourseCategoryCard(
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ 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 '../../../models/course_category.dart';
|
import '../../../models/category.dart';
|
||||||
import '../../../models/user_model.dart';
|
import '../../../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/status_checker_service.dart';
|
import '../../../services/status_checker_service.dart';
|
||||||
|
|
@ -25,31 +25,30 @@ class CourseCategoryViewModel extends ReactiveViewModel {
|
||||||
[_authenticationService];
|
[_authenticationService];
|
||||||
|
|
||||||
// Current user
|
// Current user
|
||||||
UserModel? get _user => _authenticationService.user;
|
User? get _user => _authenticationService.user;
|
||||||
|
|
||||||
UserModel? get user => _user;
|
User? get user => _user;
|
||||||
|
|
||||||
// Course categories
|
// Course categories
|
||||||
List<CourseCategory> _categories = [];
|
List<Category> _categories = [];
|
||||||
|
|
||||||
List<CourseCategory> get categories => _categories;
|
List<Category> get categories => _categories;
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
Future<void> navigateToCourseCategory(CourseCategory category) async =>
|
Future<void> navigateToCourseCategory(Category category) async =>
|
||||||
_navigationService.navigateToCourseSubcategoryView(category: category);
|
_navigationService.navigateToCourseSubcategoryView(category: category);
|
||||||
|
|
||||||
// Remote api call
|
// Remote api call
|
||||||
|
|
||||||
// Course categories
|
// Course categories
|
||||||
Future<void> getCourseCategories() async =>
|
Future<void> getCategories() async =>
|
||||||
await runBusyFuture(_getCourseCategories(),
|
await runBusyFuture(_getCategories(),
|
||||||
busyObject: StateObjects.courseCategories);
|
busyObject: StateObjects.courseCategories);
|
||||||
|
|
||||||
Future<void> _getCourseCategories() async {
|
Future<void> _getCategories() async {
|
||||||
if (categories.isEmpty) {
|
if (categories.isEmpty) {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
_categories = await _apiService.getCourseCategories();
|
_categories = await _apiService.getCategories();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,12 +76,8 @@ class CourseLessonView extends StackedView<CourseLessonViewModel> {
|
||||||
children: _buildLessonColumnChildren(viewModel),
|
children: _buildLessonColumnChildren(viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
List<Widget> _buildLessonColumnChildren(CourseLessonViewModel viewModel) => [
|
List<Widget> _buildLessonColumnChildren(CourseLessonViewModel viewModel) =>
|
||||||
// verticalSpaceLarge,
|
[_buildTitle(), verticalSpaceMedium, _buildListViewBuilder(viewModel)];
|
||||||
_buildTitle(),
|
|
||||||
verticalSpaceMedium,
|
|
||||||
_buildListViewBuilder(viewModel)
|
|
||||||
];
|
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
'${course.title} course lessons',
|
'${course.title} course lessons',
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,38 @@ class CourseLessonDetailViewModel extends BaseViewModel {
|
||||||
VideoPlayerController? get videoPlayerController => _videoPlayerController;
|
VideoPlayerController? get videoPlayerController => _videoPlayerController;
|
||||||
|
|
||||||
// Video player
|
// Video player
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_videoPlayerController?.dispose();
|
||||||
|
_chewieController?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> pause() async {
|
||||||
|
await _chewieController?.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _videoListener(CourseLesson lesson) async {
|
||||||
|
final controller = _videoPlayerController;
|
||||||
|
|
||||||
|
if (controller == null || !controller.value.isInitialized) return;
|
||||||
|
|
||||||
|
final position = controller.value.position;
|
||||||
|
final duration = controller.value.duration;
|
||||||
|
|
||||||
|
if (duration.inSeconds == 0) return;
|
||||||
|
|
||||||
|
double progress = position.inSeconds / duration.inSeconds;
|
||||||
|
|
||||||
|
print("Video progress: ${(progress * 100).toStringAsFixed(2)}%");
|
||||||
|
|
||||||
|
// Example: mark completion at 95%
|
||||||
|
if (progress >= 0.95) {
|
||||||
|
await _onVideoCompleted(lesson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> initializePlayer(CourseLesson lesson) async =>
|
Future<void> initializePlayer(CourseLesson lesson) async =>
|
||||||
await runBusyFuture(_initializePlayer(lesson),
|
await runBusyFuture(_initializePlayer(lesson),
|
||||||
busyObject: StateObjects.loadCourseVideo);
|
busyObject: StateObjects.loadCourseVideo);
|
||||||
|
|
@ -59,26 +91,6 @@ class CourseLessonDetailViewModel extends BaseViewModel {
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _videoListener(CourseLesson lesson) async {
|
|
||||||
final controller = _videoPlayerController;
|
|
||||||
|
|
||||||
if (controller == null || !controller.value.isInitialized) return;
|
|
||||||
|
|
||||||
final position = controller.value.position;
|
|
||||||
final duration = controller.value.duration;
|
|
||||||
|
|
||||||
if (duration.inSeconds == 0) return;
|
|
||||||
|
|
||||||
double progress = position.inSeconds / duration.inSeconds;
|
|
||||||
|
|
||||||
print("Video progress: ${(progress * 100).toStringAsFixed(2)}%");
|
|
||||||
|
|
||||||
// Example: mark completion at 95%
|
|
||||||
if (progress >= 0.95) {
|
|
||||||
await _onVideoCompleted(lesson);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onVideoCompleted(CourseLesson lesson) async {
|
Future<void> _onVideoCompleted(CourseLesson lesson) async {
|
||||||
if (_isCompleted) return;
|
if (_isCompleted) return;
|
||||||
|
|
||||||
|
|
@ -89,17 +101,6 @@ class CourseLessonDetailViewModel extends BaseViewModel {
|
||||||
await _apiService.completeLesson(lesson.id ?? 0);
|
await _apiService.completeLesson(lesson.id ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pause() async {
|
|
||||||
await _chewieController?.pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_videoPlayerController?.dispose();
|
|
||||||
_chewieController?.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,17 +95,17 @@ class CoursePracticeView extends StackedView<CoursePracticeViewModel> {
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: viewModel.coursePractices.length,
|
itemCount: viewModel.coursePractices.length,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemBuilder: (context, index) => _buildCard(
|
|
||||||
title: viewModel.coursePractices[index].title ?? '',
|
|
||||||
onTap: () async =>
|
|
||||||
await viewModel.navigateToCoursePracticeQuestion(viewModel.coursePractices[index].id ?? 0),
|
|
||||||
),
|
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
mainAxisSpacing: 15,
|
mainAxisSpacing: 15,
|
||||||
crossAxisSpacing: 15,
|
crossAxisSpacing: 15,
|
||||||
childAspectRatio: 1.2,
|
childAspectRatio: 1.2,
|
||||||
),
|
),
|
||||||
|
itemBuilder: (context, index) => _buildCard(
|
||||||
|
title: viewModel.coursePractices[index].title ?? '',
|
||||||
|
onTap: () async => await viewModel.navigateToCoursePracticeQuestion(
|
||||||
|
viewModel.coursePractices[index].id ?? 0),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildCard({
|
Widget _buildCard({
|
||||||
|
|
|
||||||
|
|
@ -56,13 +56,80 @@ class CoursePracticeQuestionViewModel extends FormViewModel {
|
||||||
|
|
||||||
Map<String, dynamic> get selectedAnswers => _selectedAnswers;
|
Map<String, dynamic> get selectedAnswers => _selectedAnswers;
|
||||||
|
|
||||||
// Question
|
// Question navigation
|
||||||
|
void previousQuestion() {
|
||||||
|
if (_currentQuestionIndex != 0) {
|
||||||
|
_currentQuestionIndex--;
|
||||||
|
_pageController.previousPage(
|
||||||
|
duration: const Duration(microseconds: 100), curve: Curves.linear);
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In-app navigation
|
||||||
|
void goBack() {
|
||||||
|
if (_currentPage == 0) {
|
||||||
|
pop();
|
||||||
|
} else {
|
||||||
|
_currentPage = 0;
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void goTo(int page) {
|
||||||
|
_currentPage = page;
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void next({int? page}) async {
|
||||||
|
if (page == null) {
|
||||||
|
if (_previousPage != 0) {
|
||||||
|
_currentPage = _previousPage;
|
||||||
|
} else {
|
||||||
|
_currentPage++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_previousPage = _currentPage;
|
||||||
|
_currentPage = page;
|
||||||
|
}
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Answer
|
||||||
|
void reset() {
|
||||||
|
_selectedAnswers.clear();
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
void setAnswerFocus() {
|
void setAnswerFocus() {
|
||||||
_focusAnswer = true;
|
_focusAnswer = true;
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> abort() async {
|
||||||
|
bool? response = await showAbortDialog();
|
||||||
|
if (response != null && response) {
|
||||||
|
next(page: 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool?> showAbortDialog() async {
|
||||||
|
DialogResponse? response = await _dialogService.showDialog(
|
||||||
|
cancelTitle: 'No',
|
||||||
|
buttonTitle: 'Yes',
|
||||||
|
title: 'Abort Practice',
|
||||||
|
barrierDismissible: true,
|
||||||
|
cancelTitleColor: kcDarkGrey,
|
||||||
|
buttonTitleColor: kcPrimaryColor,
|
||||||
|
description: 'Are you sure to abort the practice ?',
|
||||||
|
);
|
||||||
|
return response?.confirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSelectedAnswer({required int question, required String answer}) {
|
||||||
|
return _selectedAnswers[question.toString()]?['option'] == answer;
|
||||||
|
}
|
||||||
|
|
||||||
void setSelectedAnswer({required int question, required Option? option}) {
|
void setSelectedAnswer({required int question, required Option? option}) {
|
||||||
bool correct = false;
|
bool correct = false;
|
||||||
if (option?.isCorrect ?? false) {
|
if (option?.isCorrect ?? false) {
|
||||||
|
|
@ -84,81 +151,29 @@ class CoursePracticeQuestionViewModel extends FormViewModel {
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSelectedAnswer({required int question, required String answer}) {
|
|
||||||
return _selectedAnswers[question.toString()]?['option'] == answer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dialog
|
|
||||||
Future<bool?> showAbortDialog() async {
|
|
||||||
DialogResponse? response = await _dialogService.showDialog(
|
|
||||||
cancelTitle: 'No',
|
|
||||||
buttonTitle: 'Yes',
|
|
||||||
title: 'Abort Practice',
|
|
||||||
barrierDismissible: true,
|
|
||||||
cancelTitleColor: kcDarkGrey,
|
|
||||||
buttonTitleColor: kcPrimaryColor,
|
|
||||||
description: 'Are you sure to abort the practice ?',
|
|
||||||
);
|
|
||||||
return response?.confirmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> abort() async {
|
|
||||||
bool? response = await showAbortDialog();
|
|
||||||
if (response != null && response) {
|
|
||||||
next(page: 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset practice
|
|
||||||
void reset() {
|
|
||||||
_selectedAnswers.clear();
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Question navigation
|
|
||||||
void previousQuestion() {
|
|
||||||
if (_currentQuestionIndex != 0) {
|
|
||||||
_currentQuestionIndex--;
|
|
||||||
_pageController.previousPage(
|
|
||||||
duration: const Duration(microseconds: 100), curve: Curves.linear);
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In-app navigation
|
|
||||||
void goTo(int page) {
|
|
||||||
_currentPage = page;
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
void next({int? page}) async {
|
|
||||||
if (page == null) {
|
|
||||||
if (_previousPage != 0) {
|
|
||||||
_currentPage = _previousPage;
|
|
||||||
} else {
|
|
||||||
_currentPage++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_previousPage = _currentPage;
|
|
||||||
_currentPage = page;
|
|
||||||
}
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
void goBack() {
|
|
||||||
if (_currentPage == 0) {
|
|
||||||
pop();
|
|
||||||
} else {
|
|
||||||
_currentPage = 0;
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
// Remote api call
|
// Remote api call
|
||||||
|
|
||||||
|
// Question navigation
|
||||||
|
Future<void> _nextQuestion(int id) async {
|
||||||
|
_currentQuestionIndex++;
|
||||||
|
|
||||||
|
if (_currentQuestionIndex == _coursePracticeQuestions.length) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
_currentQuestion = await _apiService.getCoursePracticeQuestion(id);
|
||||||
|
_pageController.jumpToPage(_currentQuestionIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> nextQuestion(int id) async =>
|
||||||
|
await runBusyFuture(_nextQuestion(id),
|
||||||
|
busyObject: StateObjects.coursePractice);
|
||||||
|
|
||||||
// Course practice questions
|
// Course practice questions
|
||||||
Future<void> getCoursePracticeQuestions(int id) async =>
|
Future<void> getCoursePracticeQuestions(int id) async =>
|
||||||
await runBusyFuture(_getCoursePracticeQuestions(id),
|
await runBusyFuture(_getCoursePracticeQuestions(id),
|
||||||
|
|
@ -185,25 +200,4 @@ class CoursePracticeQuestionViewModel extends FormViewModel {
|
||||||
_currentQuestion = await _apiService.getCoursePracticeQuestion(id);
|
_currentQuestion = await _apiService.getCoursePracticeQuestion(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Question navigation
|
|
||||||
Future<void> nextQuestion(int id) async =>
|
|
||||||
await runBusyFuture(_nextQuestion(id),
|
|
||||||
busyObject: StateObjects.coursePractice);
|
|
||||||
|
|
||||||
Future<void> _nextQuestion(int id)async{
|
|
||||||
_currentQuestionIndex++;
|
|
||||||
|
|
||||||
if (_currentQuestionIndex == _coursePracticeQuestions.length) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
if (await _statusChecker.checkConnection()) {
|
|
||||||
_currentQuestion = await _apiService.getCoursePracticeQuestion(id);
|
|
||||||
_pageController.jumpToPage(_currentQuestionIndex);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
import '../../../models/course_category.dart';
|
import '../../../models/category.dart';
|
||||||
import '../../../models/course_subcategory.dart';
|
import '../../../models/subcategory.dart';
|
||||||
import '../../common/app_colors.dart';
|
import '../../common/app_colors.dart';
|
||||||
import '../../common/enmus.dart';
|
import '../../common/enmus.dart';
|
||||||
import '../../common/ui_helpers.dart';
|
import '../../common/ui_helpers.dart';
|
||||||
|
|
@ -12,14 +12,14 @@ import '../../widgets/small_app_bar.dart';
|
||||||
import 'course_subcategory_viewmodel.dart';
|
import 'course_subcategory_viewmodel.dart';
|
||||||
|
|
||||||
class CourseSubcategoryView extends StackedView<CourseSubcategoryViewModel> {
|
class CourseSubcategoryView extends StackedView<CourseSubcategoryViewModel> {
|
||||||
final CourseCategory category;
|
final Category category;
|
||||||
|
|
||||||
const CourseSubcategoryView({Key? key, required this.category})
|
const CourseSubcategoryView({Key? key, required this.category})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onViewModelReady(CourseSubcategoryViewModel viewModel) async {
|
void onViewModelReady(CourseSubcategoryViewModel viewModel) async {
|
||||||
await viewModel.getCourseSubcategories(category.id ?? 0);
|
await viewModel.getSubcategories(category.id ?? 0);
|
||||||
super.onViewModelReady(viewModel);
|
super.onViewModelReady(viewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,7 +121,7 @@ class CourseSubcategoryView extends StackedView<CourseSubcategoryViewModel> {
|
||||||
Widget _buildTile({
|
Widget _buildTile({
|
||||||
GestureTapCallback? onCourseTap,
|
GestureTapCallback? onCourseTap,
|
||||||
GestureTapCallback? onPracticeTap,
|
GestureTapCallback? onPracticeTap,
|
||||||
required CourseSubcategory subcategory,
|
required Subcategory subcategory,
|
||||||
}) =>
|
}) =>
|
||||||
CourseSubcategoryTile(
|
CourseSubcategoryTile(
|
||||||
subcategory: subcategory,
|
subcategory: subcategory,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ 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 '../../../models/course_subcategory.dart';
|
import '../../../models/subcategory.dart';
|
||||||
import '../../../services/api_service.dart';
|
import '../../../services/api_service.dart';
|
||||||
import '../../../services/status_checker_service.dart';
|
import '../../../services/status_checker_service.dart';
|
||||||
import '../../common/enmus.dart';
|
import '../../common/enmus.dart';
|
||||||
|
|
@ -16,10 +16,10 @@ class CourseSubcategoryViewModel extends BaseViewModel {
|
||||||
|
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
// Courses
|
// Course subcategories
|
||||||
List<CourseSubcategory> _subcategories = [];
|
List<Subcategory> _subcategories = [];
|
||||||
|
|
||||||
List<CourseSubcategory> get subcategories => _subcategories;
|
List<Subcategory> get subcategories => _subcategories;
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
@ -27,20 +27,19 @@ class CourseSubcategoryViewModel 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(CourseSubcategory subcategory) async =>
|
Future<void> navigateToSubcourse(Subcategory subcategory) async =>
|
||||||
_navigationService.navigateToCourseView(subcategory: subcategory);
|
_navigationService.navigateToCourseView(subcategory: subcategory);
|
||||||
|
|
||||||
// Remote api call
|
// Remote api call
|
||||||
|
|
||||||
// Courses
|
// Course subcategories
|
||||||
Future<void> getCourseSubcategories(int id) async =>
|
Future<void> getSubcategories(int id) async =>
|
||||||
await runBusyFuture(_getCourseSubcategories(id),
|
await runBusyFuture(_getSubcategories(id),
|
||||||
busyObject: StateObjects.subcategories);
|
busyObject: StateObjects.subcategories);
|
||||||
|
|
||||||
Future<void> _getCourseSubcategories(int id) async {
|
Future<void> _getSubcategories(int id) async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
_subcategories = await _apiService.getCourseSubcategories(id);
|
_subcategories = await _apiService.getSubcategories(id);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,41 +169,32 @@ class DuolingoViewModel extends FormViewModel {
|
||||||
|
|
||||||
List<String> get listeningAssessments => _listeningAssessments;
|
List<String> get listeningAssessments => _listeningAssessments;
|
||||||
|
|
||||||
// Topics
|
// Assessment
|
||||||
void setAssessmentFocus() {
|
|
||||||
_assessmentFocus = true;
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Speaking state
|
|
||||||
void setSpeakingState() {
|
void setSpeakingState() {
|
||||||
_isSpeaking = !_isSpeaking;
|
_isSpeaking = !_isSpeaking;
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set assessment
|
void setAssessmentFocus() {
|
||||||
|
_assessmentFocus = true;
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
void setSelectedAssessment(
|
void setSelectedAssessment(
|
||||||
{required int page, required Map<String, dynamic> assessment}) {
|
{required int page, required Map<String, dynamic> assessment}) {
|
||||||
_selectedAssessment = assessment;
|
_selectedAssessment = assessment;
|
||||||
goTo(page);
|
goTo(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listening assessment
|
bool isSelectedListeningAssessment(String value) =>
|
||||||
|
_selectedListeningAssessment == value;
|
||||||
|
|
||||||
void setSelectedListeningAssessment(String value) {
|
void setSelectedListeningAssessment(String value) {
|
||||||
_selectedListeningAssessment = value;
|
_selectedListeningAssessment = value;
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSelectedListeningAssessment(String value) =>
|
|
||||||
_selectedListeningAssessment == value;
|
|
||||||
|
|
||||||
// In-app navigation
|
// In-app navigation
|
||||||
void goTo(int page) {
|
|
||||||
_currentPage = page;
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
void goBack() {
|
void goBack() {
|
||||||
if (_currentPage == 0) {
|
if (_currentPage == 0) {
|
||||||
pop();
|
pop();
|
||||||
|
|
@ -213,6 +204,11 @@ class DuolingoViewModel extends FormViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void goTo(int page) {
|
||||||
|
_currentPage = page;
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,8 @@ class DuolingoRetakeScreen extends ViewModelWidget<DuolingoViewModel> {
|
||||||
height: 55,
|
height: 55,
|
||||||
safe: false,
|
safe: false,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
foregroundColor: kcWhite,
|
|
||||||
text: 'Practice Again',
|
text: 'Practice Again',
|
||||||
|
foregroundColor: kcWhite,
|
||||||
onTap: () => viewModel.goTo(0),
|
onTap: () => viewModel.goTo(0),
|
||||||
backgroundColor: kcPrimaryColor,
|
backgroundColor: kcPrimaryColor,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -89,14 +89,11 @@ class DuolingoSpeakingAssessment1Question
|
||||||
[_buildTimer(), _buildCountdownTime()];
|
[_buildTimer(), _buildCountdownTime()];
|
||||||
|
|
||||||
Widget _buildTimer() => const CircularProgressIndicator(
|
Widget _buildTimer() => const CircularProgressIndicator(
|
||||||
constraints: BoxConstraints(
|
|
||||||
minWidth: 50,
|
|
||||||
minHeight: 50,
|
|
||||||
),
|
|
||||||
value: 1.0,
|
value: 1.0,
|
||||||
strokeWidth: 5,
|
strokeWidth: 5,
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
color: kcPrimaryColor,
|
color: kcPrimaryColor,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: BoxConstraints(minWidth: 50, minHeight: 50),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildCountdownTime() => Text(
|
Widget _buildCountdownTime() => Text(
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ import '../../common/validators/form_validator.dart';
|
||||||
import 'forget_password_viewmodel.dart';
|
import 'forget_password_viewmodel.dart';
|
||||||
|
|
||||||
@FormView(fields: [
|
@FormView(fields: [
|
||||||
FormTextField(name: 'email', validator: FormValidator.validateEmailForm),
|
|
||||||
FormTextField(name: 'resetCode', validator: FormValidator.validateForm),
|
|
||||||
FormTextField(name: 'password', validator: FormValidator.validateForm),
|
FormTextField(name: 'password', validator: FormValidator.validateForm),
|
||||||
|
FormTextField(name: 'resetCode', validator: FormValidator.validateForm),
|
||||||
|
FormTextField(name: 'email', validator: FormValidator.validateEmailForm),
|
||||||
FormTextField(name: 'confirmPassword', validator: FormValidator.validateForm)
|
FormTextField(name: 'confirmPassword', validator: FormValidator.validateForm)
|
||||||
])
|
])
|
||||||
class ForgetPasswordView extends StackedView<ForgetPasswordViewModel>
|
class ForgetPasswordView extends StackedView<ForgetPasswordViewModel>
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ import 'package:yimaru_app/ui/common/validators/form_validator.dart';
|
||||||
|
|
||||||
const bool _autoTextFieldValidation = true;
|
const bool _autoTextFieldValidation = true;
|
||||||
|
|
||||||
const String EmailValueKey = 'email';
|
|
||||||
const String ResetCodeValueKey = 'resetCode';
|
|
||||||
const String PasswordValueKey = 'password';
|
const String PasswordValueKey = 'password';
|
||||||
|
const String ResetCodeValueKey = 'resetCode';
|
||||||
|
const String EmailValueKey = 'email';
|
||||||
const String ConfirmPasswordValueKey = 'confirmPassword';
|
const String ConfirmPasswordValueKey = 'confirmPassword';
|
||||||
|
|
||||||
final Map<String, TextEditingController>
|
final Map<String, TextEditingController>
|
||||||
|
|
@ -24,25 +24,25 @@ final Map<String, FocusNode> _ForgetPasswordViewFocusNodes = {};
|
||||||
|
|
||||||
final Map<String, String? Function(String?)?>
|
final Map<String, String? Function(String?)?>
|
||||||
_ForgetPasswordViewTextValidations = {
|
_ForgetPasswordViewTextValidations = {
|
||||||
EmailValueKey: FormValidator.validateEmailForm,
|
|
||||||
ResetCodeValueKey: FormValidator.validateForm,
|
|
||||||
PasswordValueKey: FormValidator.validateForm,
|
PasswordValueKey: FormValidator.validateForm,
|
||||||
|
ResetCodeValueKey: FormValidator.validateForm,
|
||||||
|
EmailValueKey: FormValidator.validateEmailForm,
|
||||||
ConfirmPasswordValueKey: FormValidator.validateForm,
|
ConfirmPasswordValueKey: FormValidator.validateForm,
|
||||||
};
|
};
|
||||||
|
|
||||||
mixin $ForgetPasswordView {
|
mixin $ForgetPasswordView {
|
||||||
TextEditingController get emailController =>
|
|
||||||
_getFormTextEditingController(EmailValueKey);
|
|
||||||
TextEditingController get resetCodeController =>
|
|
||||||
_getFormTextEditingController(ResetCodeValueKey);
|
|
||||||
TextEditingController get passwordController =>
|
TextEditingController get passwordController =>
|
||||||
_getFormTextEditingController(PasswordValueKey);
|
_getFormTextEditingController(PasswordValueKey);
|
||||||
|
TextEditingController get resetCodeController =>
|
||||||
|
_getFormTextEditingController(ResetCodeValueKey);
|
||||||
|
TextEditingController get emailController =>
|
||||||
|
_getFormTextEditingController(EmailValueKey);
|
||||||
TextEditingController get confirmPasswordController =>
|
TextEditingController get confirmPasswordController =>
|
||||||
_getFormTextEditingController(ConfirmPasswordValueKey);
|
_getFormTextEditingController(ConfirmPasswordValueKey);
|
||||||
|
|
||||||
FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey);
|
|
||||||
FocusNode get resetCodeFocusNode => _getFormFocusNode(ResetCodeValueKey);
|
|
||||||
FocusNode get passwordFocusNode => _getFormFocusNode(PasswordValueKey);
|
FocusNode get passwordFocusNode => _getFormFocusNode(PasswordValueKey);
|
||||||
|
FocusNode get resetCodeFocusNode => _getFormFocusNode(ResetCodeValueKey);
|
||||||
|
FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey);
|
||||||
FocusNode get confirmPasswordFocusNode =>
|
FocusNode get confirmPasswordFocusNode =>
|
||||||
_getFormFocusNode(ConfirmPasswordValueKey);
|
_getFormFocusNode(ConfirmPasswordValueKey);
|
||||||
|
|
||||||
|
|
@ -70,9 +70,9 @@ mixin $ForgetPasswordView {
|
||||||
/// Registers a listener on every generated controller that calls [model.setData()]
|
/// Registers a listener on every generated controller that calls [model.setData()]
|
||||||
/// with the latest textController values
|
/// with the latest textController values
|
||||||
void syncFormWithViewModel(FormStateHelper model) {
|
void syncFormWithViewModel(FormStateHelper model) {
|
||||||
emailController.addListener(() => _updateFormData(model));
|
|
||||||
resetCodeController.addListener(() => _updateFormData(model));
|
|
||||||
passwordController.addListener(() => _updateFormData(model));
|
passwordController.addListener(() => _updateFormData(model));
|
||||||
|
resetCodeController.addListener(() => _updateFormData(model));
|
||||||
|
emailController.addListener(() => _updateFormData(model));
|
||||||
confirmPasswordController.addListener(() => _updateFormData(model));
|
confirmPasswordController.addListener(() => _updateFormData(model));
|
||||||
|
|
||||||
_updateFormData(model, forceValidate: _autoTextFieldValidation);
|
_updateFormData(model, forceValidate: _autoTextFieldValidation);
|
||||||
|
|
@ -85,9 +85,9 @@ mixin $ForgetPasswordView {
|
||||||
'This feature was deprecated after 3.1.0.',
|
'This feature was deprecated after 3.1.0.',
|
||||||
)
|
)
|
||||||
void listenToFormUpdated(FormViewModel model) {
|
void listenToFormUpdated(FormViewModel model) {
|
||||||
emailController.addListener(() => _updateFormData(model));
|
|
||||||
resetCodeController.addListener(() => _updateFormData(model));
|
|
||||||
passwordController.addListener(() => _updateFormData(model));
|
passwordController.addListener(() => _updateFormData(model));
|
||||||
|
resetCodeController.addListener(() => _updateFormData(model));
|
||||||
|
emailController.addListener(() => _updateFormData(model));
|
||||||
confirmPasswordController.addListener(() => _updateFormData(model));
|
confirmPasswordController.addListener(() => _updateFormData(model));
|
||||||
|
|
||||||
_updateFormData(model, forceValidate: _autoTextFieldValidation);
|
_updateFormData(model, forceValidate: _autoTextFieldValidation);
|
||||||
|
|
@ -98,9 +98,9 @@ mixin $ForgetPasswordView {
|
||||||
model.setData(
|
model.setData(
|
||||||
model.formValueMap
|
model.formValueMap
|
||||||
..addAll({
|
..addAll({
|
||||||
EmailValueKey: emailController.text,
|
|
||||||
ResetCodeValueKey: resetCodeController.text,
|
|
||||||
PasswordValueKey: passwordController.text,
|
PasswordValueKey: passwordController.text,
|
||||||
|
ResetCodeValueKey: resetCodeController.text,
|
||||||
|
EmailValueKey: emailController.text,
|
||||||
ConfirmPasswordValueKey: confirmPasswordController.text,
|
ConfirmPasswordValueKey: confirmPasswordController.text,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -143,19 +143,20 @@ extension ValueProperties on FormStateHelper {
|
||||||
return !hasAnyValidationMessage;
|
return !hasAnyValidationMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? get emailValue => this.formValueMap[EmailValueKey] as String?;
|
|
||||||
String? get resetCodeValue => this.formValueMap[ResetCodeValueKey] as String?;
|
|
||||||
String? get passwordValue => this.formValueMap[PasswordValueKey] as String?;
|
String? get passwordValue => this.formValueMap[PasswordValueKey] as String?;
|
||||||
|
String? get resetCodeValue => this.formValueMap[ResetCodeValueKey] as String?;
|
||||||
|
String? get emailValue => this.formValueMap[EmailValueKey] as String?;
|
||||||
String? get confirmPasswordValue =>
|
String? get confirmPasswordValue =>
|
||||||
this.formValueMap[ConfirmPasswordValueKey] as String?;
|
this.formValueMap[ConfirmPasswordValueKey] as String?;
|
||||||
|
|
||||||
set emailValue(String? value) {
|
set passwordValue(String? value) {
|
||||||
this.setData(
|
this.setData(
|
||||||
this.formValueMap..addAll({EmailValueKey: value}),
|
this.formValueMap..addAll({PasswordValueKey: value}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (_ForgetPasswordViewTextEditingControllers.containsKey(EmailValueKey)) {
|
if (_ForgetPasswordViewTextEditingControllers.containsKey(
|
||||||
_ForgetPasswordViewTextEditingControllers[EmailValueKey]?.text =
|
PasswordValueKey)) {
|
||||||
|
_ForgetPasswordViewTextEditingControllers[PasswordValueKey]?.text =
|
||||||
value ?? '';
|
value ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -172,14 +173,13 @@ extension ValueProperties on FormStateHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set passwordValue(String? value) {
|
set emailValue(String? value) {
|
||||||
this.setData(
|
this.setData(
|
||||||
this.formValueMap..addAll({PasswordValueKey: value}),
|
this.formValueMap..addAll({EmailValueKey: value}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (_ForgetPasswordViewTextEditingControllers.containsKey(
|
if (_ForgetPasswordViewTextEditingControllers.containsKey(EmailValueKey)) {
|
||||||
PasswordValueKey)) {
|
_ForgetPasswordViewTextEditingControllers[EmailValueKey]?.text =
|
||||||
_ForgetPasswordViewTextEditingControllers[PasswordValueKey]?.text =
|
|
||||||
value ?? '';
|
value ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -196,64 +196,64 @@ extension ValueProperties on FormStateHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get hasEmail =>
|
|
||||||
this.formValueMap.containsKey(EmailValueKey) &&
|
|
||||||
(emailValue?.isNotEmpty ?? false);
|
|
||||||
bool get hasResetCode =>
|
|
||||||
this.formValueMap.containsKey(ResetCodeValueKey) &&
|
|
||||||
(resetCodeValue?.isNotEmpty ?? false);
|
|
||||||
bool get hasPassword =>
|
bool get hasPassword =>
|
||||||
this.formValueMap.containsKey(PasswordValueKey) &&
|
this.formValueMap.containsKey(PasswordValueKey) &&
|
||||||
(passwordValue?.isNotEmpty ?? false);
|
(passwordValue?.isNotEmpty ?? false);
|
||||||
|
bool get hasResetCode =>
|
||||||
|
this.formValueMap.containsKey(ResetCodeValueKey) &&
|
||||||
|
(resetCodeValue?.isNotEmpty ?? false);
|
||||||
|
bool get hasEmail =>
|
||||||
|
this.formValueMap.containsKey(EmailValueKey) &&
|
||||||
|
(emailValue?.isNotEmpty ?? false);
|
||||||
bool get hasConfirmPassword =>
|
bool get hasConfirmPassword =>
|
||||||
this.formValueMap.containsKey(ConfirmPasswordValueKey) &&
|
this.formValueMap.containsKey(ConfirmPasswordValueKey) &&
|
||||||
(confirmPasswordValue?.isNotEmpty ?? false);
|
(confirmPasswordValue?.isNotEmpty ?? false);
|
||||||
|
|
||||||
bool get hasEmailValidationMessage =>
|
|
||||||
this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false;
|
|
||||||
bool get hasResetCodeValidationMessage =>
|
|
||||||
this.fieldsValidationMessages[ResetCodeValueKey]?.isNotEmpty ?? false;
|
|
||||||
bool get hasPasswordValidationMessage =>
|
bool get hasPasswordValidationMessage =>
|
||||||
this.fieldsValidationMessages[PasswordValueKey]?.isNotEmpty ?? false;
|
this.fieldsValidationMessages[PasswordValueKey]?.isNotEmpty ?? false;
|
||||||
|
bool get hasResetCodeValidationMessage =>
|
||||||
|
this.fieldsValidationMessages[ResetCodeValueKey]?.isNotEmpty ?? false;
|
||||||
|
bool get hasEmailValidationMessage =>
|
||||||
|
this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false;
|
||||||
bool get hasConfirmPasswordValidationMessage =>
|
bool get hasConfirmPasswordValidationMessage =>
|
||||||
this.fieldsValidationMessages[ConfirmPasswordValueKey]?.isNotEmpty ??
|
this.fieldsValidationMessages[ConfirmPasswordValueKey]?.isNotEmpty ??
|
||||||
false;
|
false;
|
||||||
|
|
||||||
String? get emailValidationMessage =>
|
|
||||||
this.fieldsValidationMessages[EmailValueKey];
|
|
||||||
String? get resetCodeValidationMessage =>
|
|
||||||
this.fieldsValidationMessages[ResetCodeValueKey];
|
|
||||||
String? get passwordValidationMessage =>
|
String? get passwordValidationMessage =>
|
||||||
this.fieldsValidationMessages[PasswordValueKey];
|
this.fieldsValidationMessages[PasswordValueKey];
|
||||||
|
String? get resetCodeValidationMessage =>
|
||||||
|
this.fieldsValidationMessages[ResetCodeValueKey];
|
||||||
|
String? get emailValidationMessage =>
|
||||||
|
this.fieldsValidationMessages[EmailValueKey];
|
||||||
String? get confirmPasswordValidationMessage =>
|
String? get confirmPasswordValidationMessage =>
|
||||||
this.fieldsValidationMessages[ConfirmPasswordValueKey];
|
this.fieldsValidationMessages[ConfirmPasswordValueKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Methods on FormStateHelper {
|
extension Methods on FormStateHelper {
|
||||||
setEmailValidationMessage(String? validationMessage) =>
|
|
||||||
this.fieldsValidationMessages[EmailValueKey] = validationMessage;
|
|
||||||
setResetCodeValidationMessage(String? validationMessage) =>
|
|
||||||
this.fieldsValidationMessages[ResetCodeValueKey] = validationMessage;
|
|
||||||
setPasswordValidationMessage(String? validationMessage) =>
|
setPasswordValidationMessage(String? validationMessage) =>
|
||||||
this.fieldsValidationMessages[PasswordValueKey] = validationMessage;
|
this.fieldsValidationMessages[PasswordValueKey] = validationMessage;
|
||||||
|
setResetCodeValidationMessage(String? validationMessage) =>
|
||||||
|
this.fieldsValidationMessages[ResetCodeValueKey] = validationMessage;
|
||||||
|
setEmailValidationMessage(String? validationMessage) =>
|
||||||
|
this.fieldsValidationMessages[EmailValueKey] = validationMessage;
|
||||||
setConfirmPasswordValidationMessage(String? validationMessage) =>
|
setConfirmPasswordValidationMessage(String? validationMessage) =>
|
||||||
this.fieldsValidationMessages[ConfirmPasswordValueKey] =
|
this.fieldsValidationMessages[ConfirmPasswordValueKey] =
|
||||||
validationMessage;
|
validationMessage;
|
||||||
|
|
||||||
/// Clears text input fields on the Form
|
/// Clears text input fields on the Form
|
||||||
void clearForm() {
|
void clearForm() {
|
||||||
emailValue = '';
|
|
||||||
resetCodeValue = '';
|
|
||||||
passwordValue = '';
|
passwordValue = '';
|
||||||
|
resetCodeValue = '';
|
||||||
|
emailValue = '';
|
||||||
confirmPasswordValue = '';
|
confirmPasswordValue = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates text input fields on the Form
|
/// Validates text input fields on the Form
|
||||||
void validateForm() {
|
void validateForm() {
|
||||||
this.setValidationMessages({
|
this.setValidationMessages({
|
||||||
EmailValueKey: getValidationMessage(EmailValueKey),
|
|
||||||
ResetCodeValueKey: getValidationMessage(ResetCodeValueKey),
|
|
||||||
PasswordValueKey: getValidationMessage(PasswordValueKey),
|
PasswordValueKey: getValidationMessage(PasswordValueKey),
|
||||||
|
ResetCodeValueKey: getValidationMessage(ResetCodeValueKey),
|
||||||
|
EmailValueKey: getValidationMessage(EmailValueKey),
|
||||||
ConfirmPasswordValueKey: getValidationMessage(ConfirmPasswordValueKey),
|
ConfirmPasswordValueKey: getValidationMessage(ConfirmPasswordValueKey),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -274,8 +274,8 @@ String? getValidationMessage(String key) {
|
||||||
/// Updates the fieldsValidationMessages on the FormViewModel
|
/// Updates the fieldsValidationMessages on the FormViewModel
|
||||||
void updateValidationData(FormStateHelper model) =>
|
void updateValidationData(FormStateHelper model) =>
|
||||||
model.setValidationMessages({
|
model.setValidationMessages({
|
||||||
EmailValueKey: getValidationMessage(EmailValueKey),
|
|
||||||
ResetCodeValueKey: getValidationMessage(ResetCodeValueKey),
|
|
||||||
PasswordValueKey: getValidationMessage(PasswordValueKey),
|
PasswordValueKey: getValidationMessage(PasswordValueKey),
|
||||||
|
ResetCodeValueKey: getValidationMessage(ResetCodeValueKey),
|
||||||
|
EmailValueKey: getValidationMessage(EmailValueKey),
|
||||||
ConfirmPasswordValueKey: getValidationMessage(ConfirmPasswordValueKey),
|
ConfirmPasswordValueKey: getValidationMessage(ConfirmPasswordValueKey),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class ForgetPasswordViewModel extends FormViewModel {
|
||||||
|
|
||||||
Map<String, dynamic> get userData => _userData;
|
Map<String, dynamic> get userData => _userData;
|
||||||
|
|
||||||
// Navigation
|
// In-app navigation
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
|
|
||||||
int get currentPage => _currentPage;
|
int get currentPage => _currentPage;
|
||||||
|
|
@ -69,15 +69,6 @@ class ForgetPasswordViewModel extends FormViewModel {
|
||||||
|
|
||||||
bool get obscureConfirmPassword => _obscureConfirmPassword;
|
bool get obscureConfirmPassword => _obscureConfirmPassword;
|
||||||
|
|
||||||
// Add user data
|
|
||||||
void addUserData(Map<String, dynamic> data) {
|
|
||||||
_userData.addAll(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearUserData() {
|
|
||||||
_userData.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Email
|
// Email
|
||||||
void setEmailFocus() {
|
void setEmailFocus() {
|
||||||
_focusEmail = true;
|
_focusEmail = true;
|
||||||
|
|
@ -90,12 +81,37 @@ class ForgetPasswordViewModel extends FormViewModel {
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User data
|
||||||
|
void clearUserData() {
|
||||||
|
_userData.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addUserData(Map<String, dynamic> data) {
|
||||||
|
_userData.addAll(data);
|
||||||
|
}
|
||||||
|
|
||||||
// Password
|
// Password
|
||||||
void setPasswordFocus() {
|
void setPasswordFocus() {
|
||||||
_focusPassword = true;
|
_focusPassword = true;
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setObscurePassword() {
|
||||||
|
_obscurePassword = !_obscurePassword;
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
double validationProgress() {
|
||||||
|
int completed = 0;
|
||||||
|
|
||||||
|
if (_length) completed++;
|
||||||
|
if (_number) completed++;
|
||||||
|
if (_specialChar) completed++;
|
||||||
|
if (_passwordMatch) completed++;
|
||||||
|
|
||||||
|
return completed / 4; // returns 0.0 → 1.0
|
||||||
|
}
|
||||||
|
|
||||||
void validatePassword(
|
void validatePassword(
|
||||||
{required String password, required String confirmPassword}) {
|
{required String password, required String confirmPassword}) {
|
||||||
if (password.length > 8) {
|
if (password.length > 8) {
|
||||||
|
|
@ -124,22 +140,6 @@ class ForgetPasswordViewModel extends FormViewModel {
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
double validationProgress() {
|
|
||||||
int completed = 0;
|
|
||||||
|
|
||||||
if (_length) completed++;
|
|
||||||
if (_number) completed++;
|
|
||||||
if (_specialChar) completed++;
|
|
||||||
if (_passwordMatch) completed++;
|
|
||||||
|
|
||||||
return completed / 4; // returns 0.0 → 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
void setObscurePassword() {
|
|
||||||
_obscurePassword = !_obscurePassword;
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm password
|
// Confirm password
|
||||||
void setConfirmPasswordFocus() {
|
void setConfirmPasswordFocus() {
|
||||||
_focusConfirmPassword = true;
|
_focusConfirmPassword = true;
|
||||||
|
|
@ -172,11 +172,6 @@ class ForgetPasswordViewModel extends FormViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// In-app navigation
|
// In-app navigation
|
||||||
void goTo(int page) {
|
|
||||||
_currentPage = page;
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
void goBack() {
|
void goBack() {
|
||||||
if (_currentPage == 1) {
|
if (_currentPage == 1) {
|
||||||
_currentPage = 0;
|
_currentPage = 0;
|
||||||
|
|
@ -186,6 +181,11 @@ class ForgetPasswordViewModel extends FormViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void goTo(int page) {
|
||||||
|
_currentPage = page;
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
|
|
@ -194,6 +194,23 @@ class ForgetPasswordViewModel extends FormViewModel {
|
||||||
|
|
||||||
// Remote api calls
|
// Remote api calls
|
||||||
|
|
||||||
|
// Request reset code
|
||||||
|
Future<void> resetPassword() async => await runBusyFuture(_resetPassword(),
|
||||||
|
busyObject: StateObjects.resetPassword);
|
||||||
|
|
||||||
|
Future<void> _resetPassword() async {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
Map<String, dynamic> response =
|
||||||
|
await _apiService.resetPassword(_userData);
|
||||||
|
if (response['status'] == ResponseStatus.success) {
|
||||||
|
showSuccessToast(response['message']);
|
||||||
|
await replaceWithLogin();
|
||||||
|
} else {
|
||||||
|
showErrorToast(response['message']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Request reset code
|
// Request reset code
|
||||||
Future<void> requestResetCode() async =>
|
Future<void> requestResetCode() async =>
|
||||||
await runBusyFuture(_requestResetCode(),
|
await runBusyFuture(_requestResetCode(),
|
||||||
|
|
@ -211,21 +228,4 @@ class ForgetPasswordViewModel extends FormViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request reset code
|
|
||||||
Future<void> resetPassword() async => await runBusyFuture(_resetPassword(),
|
|
||||||
busyObject: StateObjects.resetPassword);
|
|
||||||
|
|
||||||
Future<void> _resetPassword() async {
|
|
||||||
if (await _statusChecker.checkConnection()) {
|
|
||||||
Map<String, dynamic> response =
|
|
||||||
await _apiService.resetPassword(_userData);
|
|
||||||
if (response['status'] == ResponseStatus.success) {
|
|
||||||
showSuccessToast(response['message']);
|
|
||||||
await replaceWithLogin();
|
|
||||||
} else {
|
|
||||||
showErrorToast(response['message']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,7 @@ class RequestCodeScreen extends ViewModelWidget<ForgetPasswordViewModel> {
|
||||||
|
|
||||||
Widget getPadding(context) {
|
Widget getPadding(context) {
|
||||||
double half = screenHeight(context) / 2;
|
double half = screenHeight(context) / 2;
|
||||||
return SizedBox(
|
return SizedBox(height: half + 375 - half);
|
||||||
height: half + 375 - half,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _inAppPop(ForgetPasswordViewModel viewModel) {
|
void _inAppPop(ForgetPasswordViewModel viewModel) {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import 'package:stacked/stacked.dart';
|
||||||
import 'package:yimaru_app/ui/common/app_colors.dart';
|
import 'package:yimaru_app/ui/common/app_colors.dart';
|
||||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||||
import 'package:yimaru_app/ui/views/course_category/course_category_view.dart';
|
import 'package:yimaru_app/ui/views/course_category/course_category_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/learn/learn_view.dart';
|
import 'package:yimaru_app/ui/views/learn_subcategory/learn_subcategory_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/profile/profile_view.dart';
|
import 'package:yimaru_app/ui/views/profile/profile_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/startup/startup_view.dart';
|
import 'package:yimaru_app/ui/views/startup/startup_view.dart';
|
||||||
|
|
||||||
|
|
@ -18,6 +18,7 @@ class HomeView extends StackedView<HomeViewModel> {
|
||||||
@override
|
@override
|
||||||
void onViewModelReady(HomeViewModel viewModel) async {
|
void onViewModelReady(HomeViewModel viewModel) async {
|
||||||
// Removable
|
// Removable
|
||||||
|
print('HERE');
|
||||||
await _init(viewModel);
|
await _init(viewModel);
|
||||||
super.onViewModelReady(viewModel);
|
super.onViewModelReady(viewModel);
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +83,7 @@ Widget _buildProfileIcon() => const Icon(Icons.person);
|
||||||
Widget getViewForIndex(int index) {
|
Widget getViewForIndex(int index) {
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
return const LearnView();
|
return const LearnSubcategoryView();
|
||||||
case 1:
|
case 1:
|
||||||
return const CourseCategoryView();
|
return const CourseCategoryView();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import 'package:yimaru_app/app/app.bottomsheets.dart';
|
import 'package:yimaru_app/app/app.bottomsheets.dart';
|
||||||
import 'package:yimaru_app/app/app.dialogs.dart';
|
|
||||||
import 'package:yimaru_app/app/app.locator.dart';
|
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:yimaru_app/models/user_model.dart';
|
import 'package:yimaru_app/models/user.dart';
|
||||||
import 'package:yimaru_app/services/status_checker_service.dart';
|
import 'package:yimaru_app/services/status_checker_service.dart';
|
||||||
import 'package:yimaru_app/ui/common/app_strings.dart';
|
import 'package:yimaru_app/ui/common/app_strings.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
@ -16,7 +15,6 @@ import '../../common/ui_helpers.dart';
|
||||||
|
|
||||||
class HomeViewModel extends ReactiveViewModel {
|
class HomeViewModel extends ReactiveViewModel {
|
||||||
final _apiService = locator<ApiService>();
|
final _apiService = locator<ApiService>();
|
||||||
final _dialogService = locator<DialogService>();
|
|
||||||
final _statusChecker = locator<StatusCheckerService>();
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
final _bottomSheetService = locator<BottomSheetService>();
|
final _bottomSheetService = locator<BottomSheetService>();
|
||||||
|
|
@ -28,9 +26,9 @@ class HomeViewModel extends ReactiveViewModel {
|
||||||
[_authenticationService];
|
[_authenticationService];
|
||||||
|
|
||||||
// Current user
|
// Current user
|
||||||
UserModel? get _user => _authenticationService.user;
|
User? get _user => _authenticationService.user;
|
||||||
|
|
||||||
UserModel? get user => _user;
|
User? get user => _user;
|
||||||
|
|
||||||
// Bottom navigation
|
// Bottom navigation
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
|
|
@ -38,19 +36,6 @@ class HomeViewModel extends ReactiveViewModel {
|
||||||
int get currentPage => _currentPage;
|
int get currentPage => _currentPage;
|
||||||
|
|
||||||
// Bottom navigation
|
// Bottom navigation
|
||||||
void setCurrentIndex(int index) {
|
|
||||||
_currentPage = index;
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
void showDialog() {
|
|
||||||
_dialogService.showCustomDialog(
|
|
||||||
title: 'Stacked Rocks!',
|
|
||||||
variant: DialogType.infoAlert,
|
|
||||||
description: 'Give stacked stars on Github',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void showBottomSheet() {
|
void showBottomSheet() {
|
||||||
_bottomSheetService.showCustomSheet(
|
_bottomSheetService.showCustomSheet(
|
||||||
title: ksHomeBottomSheetTitle,
|
title: ksHomeBottomSheetTitle,
|
||||||
|
|
@ -59,6 +44,12 @@ class HomeViewModel extends ReactiveViewModel {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setCurrentIndex(int index) {
|
||||||
|
_currentPage = index;
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save profile status
|
||||||
Future<void> saveProfileStatus(bool value) async =>
|
Future<void> saveProfileStatus(bool value) async =>
|
||||||
await _authenticationService.saveProfileStatus(value);
|
await _authenticationService.saveProfileStatus(value);
|
||||||
|
|
||||||
|
|
@ -80,7 +71,34 @@ class HomeViewModel extends ReactiveViewModel {
|
||||||
await _getProfileData();
|
await _getProfileData();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile status
|
// Get profile data
|
||||||
|
Future<void> _getProfileData() async {
|
||||||
|
if (!(_user?.userInfoLoaded ?? false)) {
|
||||||
|
Map<String, dynamic> response = {};
|
||||||
|
|
||||||
|
if (_user?.profileCompleted != null &&
|
||||||
|
(_user?.profileCompleted ?? false)) {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
response = await _apiService.getProfileData(_user?.userId);
|
||||||
|
|
||||||
|
if (response['status'] == ResponseStatus.success) {
|
||||||
|
User user = response['data'] as User;
|
||||||
|
|
||||||
|
await _authenticationService.saveUserData(user);
|
||||||
|
|
||||||
|
String image =
|
||||||
|
await _imageDownloaderService.downloader(user.profilePicture);
|
||||||
|
|
||||||
|
await _authenticationService.saveProfilePicture(image);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await replaceWithFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get profile status
|
||||||
Future<void> _getProfileStatus() async {
|
Future<void> _getProfileStatus() async {
|
||||||
Map<String, dynamic> response = {};
|
Map<String, dynamic> response = {};
|
||||||
if (_user?.profileCompleted == null) {
|
if (_user?.profileCompleted == null) {
|
||||||
|
|
@ -102,32 +120,4 @@ class HomeViewModel extends ReactiveViewModel {
|
||||||
await saveProfileStatus(response['data']);
|
await saveProfileStatus(response['data']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile data
|
|
||||||
|
|
||||||
Future<void> _getProfileData() async {
|
|
||||||
if (!(_user?.userInfoLoaded ?? false)) {
|
|
||||||
Map<String, dynamic> response = {};
|
|
||||||
|
|
||||||
if (_user?.profileCompleted != null &&
|
|
||||||
(_user?.profileCompleted ?? false)) {
|
|
||||||
if (await _statusChecker.checkConnection()) {
|
|
||||||
response = await _apiService.getProfileData(_user?.userId);
|
|
||||||
|
|
||||||
if (response['status'] == ResponseStatus.success) {
|
|
||||||
UserModel user = response['data'] as UserModel;
|
|
||||||
|
|
||||||
await _authenticationService.saveUserData(user);
|
|
||||||
|
|
||||||
String image =
|
|
||||||
await _imageDownloaderService.downloader(user.profilePicture);
|
|
||||||
|
|
||||||
await _authenticationService.saveProfilePicture(image);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await replaceWithFailure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ class LanguageViewModel extends BaseViewModel {
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
// Languages
|
// Languages
|
||||||
|
|
||||||
Map<String, dynamic> _selectedLanguage = {
|
Map<String, dynamic> _selectedLanguage = {
|
||||||
'code': 'EN',
|
'code': 'EN',
|
||||||
'language': 'English'
|
'language': 'English'
|
||||||
|
|
@ -23,13 +22,14 @@ class LanguageViewModel extends BaseViewModel {
|
||||||
List<Map<String, dynamic>> get languages => _languages;
|
List<Map<String, dynamic>> get languages => _languages;
|
||||||
|
|
||||||
// Languages
|
// Languages
|
||||||
|
bool isSelectedLanguage(String title) =>
|
||||||
|
_selectedLanguage['language'] == title;
|
||||||
|
|
||||||
void setSelectedLanguage(Map<String, dynamic> title) {
|
void setSelectedLanguage(Map<String, dynamic> title) {
|
||||||
_selectedLanguage = title;
|
_selectedLanguage = title;
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSelectedLanguage(String title) =>
|
|
||||||
_selectedLanguage['language'] == title;
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,25 @@
|
||||||
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/widgets/profile_app_bar.dart';
|
import 'package:yimaru_app/ui/widgets/learn_tile.dart';
|
||||||
import 'package:yimaru_app/ui/widgets/learn_level_tile.dart';
|
|
||||||
|
|
||||||
|
import '../../../models/course.dart';
|
||||||
import '../../common/app_colors.dart';
|
import '../../common/app_colors.dart';
|
||||||
import '../../common/enmus.dart';
|
import '../../common/enmus.dart';
|
||||||
import '../../common/ui_helpers.dart';
|
import '../../common/ui_helpers.dart';
|
||||||
|
import '../../widgets/custom_circular_progress_indicator.dart';
|
||||||
|
import '../../widgets/small_app_bar.dart';
|
||||||
import 'learn_viewmodel.dart';
|
import 'learn_viewmodel.dart';
|
||||||
|
|
||||||
class LearnView extends StackedView<LearnViewModel> {
|
class LearnView extends StackedView<LearnViewModel> {
|
||||||
const LearnView({Key? key}) : super(key: key);
|
final int id;
|
||||||
|
|
||||||
|
const LearnView({Key? key, required this.id}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onViewModelReady(LearnViewModel viewModel) async {
|
||||||
|
await viewModel.getCourses(id);
|
||||||
|
super.onViewModelReady(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
LearnViewModel viewModelBuilder(BuildContext context) => LearnViewModel();
|
LearnViewModel viewModelBuilder(BuildContext context) => LearnViewModel();
|
||||||
|
|
@ -40,49 +50,50 @@ class LearnView extends StackedView<LearnViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildAppBar(viewModel),
|
_buildAppBar(viewModel),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildLevelsColumnWrapper(viewModel)
|
_buildLearnColumnWrapper(viewModel)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildAppBar(LearnViewModel viewModel) => ProfileAppBar(
|
Widget _buildAppBar(LearnViewModel viewModel) => SmallAppBar(
|
||||||
name: viewModel.user?.firstName,
|
onTap: viewModel.pop,
|
||||||
profileImage: viewModel.user?.profilePicture,
|
showBackButton: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLevelsColumnWrapper(LearnViewModel viewModel) =>
|
Widget _buildLearnColumnWrapper(LearnViewModel viewModel) =>
|
||||||
Expanded(child: _buildLevelsColumnScrollView(viewModel));
|
Expanded(child: _buildLearnColumnScrollView(viewModel));
|
||||||
|
|
||||||
Widget _buildLevelsColumnScrollView(LearnViewModel viewModel) =>
|
Widget _buildLearnColumnScrollView(LearnViewModel viewModel) =>
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
child: _buildLevelsColumn(viewModel),
|
child: _buildListViewBuilder(viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLevelsColumn(LearnViewModel viewModel) => Column(
|
Widget _buildListViewBuilder(LearnViewModel viewModel) =>
|
||||||
mainAxisSize: MainAxisSize.min,
|
viewModel.busy(StateObjects.learnCourses)
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
? _buildProgressIndicator()
|
||||||
children: _buildLevelsColumnChildren(viewModel),
|
: _buildListView(viewModel);
|
||||||
|
|
||||||
|
Widget _buildProgressIndicator() => const Center(
|
||||||
|
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
|
||||||
);
|
);
|
||||||
|
|
||||||
List<Widget> _buildLevelsColumnChildren(LearnViewModel viewModel) =>
|
Widget _buildListView(LearnViewModel viewModel) => ListView.separated(
|
||||||
[verticalSpaceLarge, _buildListView(viewModel)];
|
|
||||||
|
|
||||||
Widget _buildListView(LearnViewModel viewModel) => ListView.builder(
|
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: viewModel.learnLevels.length,
|
itemCount: viewModel.courses.length,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
separatorBuilder: (context, index) => verticalSpaceSmall,
|
||||||
itemBuilder: (context, index) => _buildTile(
|
itemBuilder: (context, index) => _buildTile(
|
||||||
title: viewModel.learnLevels[index]['title'],
|
course: viewModel.courses[index],
|
||||||
status: viewModel.learnLevels[index]['status'],
|
onTap: () async => await viewModel
|
||||||
subtitle: viewModel.learnLevels[index]['subtitle']),
|
.navigateToLearnLevel(viewModel.courses[index].id ?? 0),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTile(
|
Widget _buildTile({
|
||||||
{required String title,
|
required Course course,
|
||||||
required String subtitle,
|
required GestureTapCallback onTap,
|
||||||
required ProgressStatuses status}) =>
|
}) =>
|
||||||
LearnLevelTile(
|
LearnTile(
|
||||||
title: title,
|
onTap: onTap,
|
||||||
status: status,
|
course: course,
|
||||||
subtitle: subtitle,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,43 @@
|
||||||
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/app/app.router.dart';
|
import 'package:yimaru_app/app/app.router.dart';
|
||||||
import 'package:yimaru_app/models/user_model.dart';
|
import 'package:yimaru_app/models/course.dart';
|
||||||
import 'package:yimaru_app/services/authentication_service.dart';
|
|
||||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||||
|
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
|
import '../../../services/api_service.dart';
|
||||||
|
import '../../../services/status_checker_service.dart';
|
||||||
|
|
||||||
|
class LearnViewModel extends BaseViewModel {
|
||||||
|
// Dependency injection
|
||||||
|
final _apiService = locator<ApiService>();
|
||||||
|
|
||||||
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
|
|
||||||
class LearnViewModel extends ReactiveViewModel {
|
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
final _authenticationService = locator<AuthenticationService>();
|
|
||||||
|
|
||||||
@override
|
// Learn courses
|
||||||
List<ListenableServiceMixin> get listenableServices =>
|
List<Course> _courses = [];
|
||||||
[_authenticationService];
|
|
||||||
|
|
||||||
// Current user
|
List<Course> get courses => _courses;
|
||||||
UserModel? get _user => _authenticationService.user;
|
|
||||||
|
|
||||||
UserModel? get user => _user;
|
// Navigation
|
||||||
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
final List<Map<String, dynamic>> _learnLevels = [
|
Future<void> navigateToLearnLevel(int id) async =>
|
||||||
{
|
_navigationService.navigateToLearnLevelView(id: id);
|
||||||
'title': 'Beginner',
|
|
||||||
'status': ProgressStatuses.started,
|
|
||||||
'subtitle': 'Start your journey with the basics of English.',
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// 'title': 'Intermediate',
|
|
||||||
// 'status': ProgressStatuses.started,
|
|
||||||
// 'subtitle': 'Practice real conversations and expand vocabulary.',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// 'title': 'Advanced',
|
|
||||||
// 'status': ProgressStatuses.pending,
|
|
||||||
// 'subtitle': 'Achieve fluency and master complex topics.',
|
|
||||||
// },
|
|
||||||
];
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> get learnLevels => _learnLevels;
|
// Remote api call
|
||||||
|
|
||||||
Future<void> navigateToLearnLevel() async =>
|
// Learn courses
|
||||||
_navigationService.navigateToLearnLevelView();
|
Future<void> getCourses(int id) async => await runBusyFuture(_getCourses(id),
|
||||||
|
busyObject: StateObjects.learnCourses);
|
||||||
|
|
||||||
|
Future<void> _getCourses(int id) async {
|
||||||
|
if (_courses.isEmpty) {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
_courses = await _apiService.getCourses(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,15 @@ class LearnLessonDetailViewModel extends BaseViewModel {
|
||||||
VideoPlayerController? get videoPlayerController => _videoPlayerController;
|
VideoPlayerController? get videoPlayerController => _videoPlayerController;
|
||||||
|
|
||||||
// Video player
|
// Video player
|
||||||
|
void close() {
|
||||||
|
_videoPlayerController?.dispose();
|
||||||
|
_chewieController?.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> pause() async {
|
||||||
|
await _chewieController?.pause();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> initializePlayer() async =>
|
Future<void> initializePlayer() async =>
|
||||||
await runBusyFuture(_initializePlayer(),
|
await runBusyFuture(_initializePlayer(),
|
||||||
busyObject: StateObjects.loadLessonVideo);
|
busyObject: StateObjects.loadLessonVideo);
|
||||||
|
|
@ -51,15 +60,6 @@ class LearnLessonDetailViewModel extends BaseViewModel {
|
||||||
// rebuildUi();
|
// rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pause() async {
|
|
||||||
await _chewieController?.pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
void close() {
|
|
||||||
_videoPlayerController?.dispose();
|
|
||||||
_chewieController?.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,24 @@
|
||||||
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/widgets/learn_sub_level_tile.dart';
|
import 'package:yimaru_app/ui/widgets/learn_level_tile.dart';
|
||||||
import 'package:yimaru_app/ui/widgets/small_app_bar.dart';
|
import 'package:yimaru_app/ui/widgets/small_app_bar.dart';
|
||||||
|
|
||||||
|
import '../../../models/level.dart';
|
||||||
import '../../common/app_colors.dart';
|
import '../../common/app_colors.dart';
|
||||||
|
import '../../common/enmus.dart';
|
||||||
import '../../common/ui_helpers.dart';
|
import '../../common/ui_helpers.dart';
|
||||||
|
import '../../widgets/custom_circular_progress_indicator.dart';
|
||||||
import 'learn_level_viewmodel.dart';
|
import 'learn_level_viewmodel.dart';
|
||||||
|
|
||||||
class LearnLevelView extends StackedView<LearnLevelViewModel> {
|
class LearnLevelView extends StackedView<LearnLevelViewModel> {
|
||||||
const LearnLevelView({Key? key}) : super(key: key);
|
final int id;
|
||||||
|
const LearnLevelView({Key? key, required this.id}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onViewModelReady(LearnLevelViewModel viewModel) async {
|
||||||
|
await viewModel.getLevels(id);
|
||||||
|
super.onViewModelReady(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
LearnLevelViewModel viewModelBuilder(BuildContext context) =>
|
LearnLevelViewModel viewModelBuilder(BuildContext context) =>
|
||||||
|
|
@ -54,36 +64,35 @@ class LearnLevelView extends StackedView<LearnLevelViewModel> {
|
||||||
|
|
||||||
Widget _buildLevelsColumnScrollView(LearnLevelViewModel viewModel) =>
|
Widget _buildLevelsColumnScrollView(LearnLevelViewModel viewModel) =>
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
child: _buildLevelsColumn(viewModel),
|
child: _buildListViewBuilder(viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLevelsColumn(LearnLevelViewModel viewModel) => Column(
|
Widget _buildListViewBuilder(LearnLevelViewModel viewModel) =>
|
||||||
mainAxisSize: MainAxisSize.min,
|
viewModel.busy(StateObjects.learnLevels)
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
? _buildProgressIndicator()
|
||||||
children: _buildLevelsColumnChildren(viewModel),
|
: _buildListView(viewModel);
|
||||||
|
|
||||||
|
Widget _buildProgressIndicator() => const Center(
|
||||||
|
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
|
||||||
);
|
);
|
||||||
|
|
||||||
List<Widget> _buildLevelsColumnChildren(LearnLevelViewModel viewModel) =>
|
Widget _buildListView(LearnLevelViewModel viewModel) => ListView.separated(
|
||||||
[verticalSpaceLarge, _buildListView(viewModel)];
|
|
||||||
|
|
||||||
Widget _buildListView(LearnLevelViewModel viewModel) => ListView.builder(
|
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: viewModel.learnSubLevels.length,
|
itemCount: viewModel.levels.length,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemBuilder: (context, index) => _buildTile(
|
itemBuilder: (context, index) => _buildTile(
|
||||||
title: viewModel.learnSubLevels[index]['title'],
|
level: viewModel.levels[index],
|
||||||
current: viewModel.learnSubLevels[index]['current'],
|
onTap: () async => await viewModel.navigateToModule( viewModel.levels[index]),
|
||||||
subtitle: viewModel.learnSubLevels[index]['subtitle']),
|
),
|
||||||
|
separatorBuilder: (context, index) => verticalSpaceSmall,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTile({
|
Widget _buildTile({
|
||||||
required String title,
|
required Level level,
|
||||||
required bool current,
|
required GestureTapCallback onTap,
|
||||||
required String subtitle,
|
|
||||||
}) =>
|
}) =>
|
||||||
LearnSubLevelTile(
|
LearnLevelTile(
|
||||||
title: title,
|
onTap: onTap,
|
||||||
current: current,
|
level: level,
|
||||||
subtitle: subtitle,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,28 +3,43 @@ import 'package:stacked_services/stacked_services.dart';
|
||||||
import 'package:yimaru_app/app/app.router.dart';
|
import 'package:yimaru_app/app/app.router.dart';
|
||||||
|
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
|
import '../../../models/level.dart';
|
||||||
|
import '../../../services/api_service.dart';
|
||||||
|
import '../../../services/status_checker_service.dart';
|
||||||
|
import '../../common/enmus.dart';
|
||||||
|
|
||||||
class LearnLevelViewModel extends BaseViewModel {
|
class LearnLevelViewModel extends BaseViewModel {
|
||||||
|
// Dependency injection
|
||||||
|
final _apiService = locator<ApiService>();
|
||||||
|
|
||||||
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
|
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
final List<Map<String, dynamic>> _learnSubLevels = [
|
// Learn levels
|
||||||
{
|
List<Level> _levels = [];
|
||||||
'title': 'A1',
|
|
||||||
'current': true,
|
|
||||||
'subtitle': 'Start your journey with the basics of English.',
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// 'title': 'A2',
|
|
||||||
// 'current': false,
|
|
||||||
// 'subtitle': 'Build upon your foundational knowledge.',
|
|
||||||
// },
|
|
||||||
];
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> get learnSubLevels => _learnSubLevels;
|
List<Level> get levels => _levels;
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
Future<void> navigateToLearnModule() async =>
|
Future<void> navigateToModule(Level level) async =>
|
||||||
_navigationService.navigateToLearnModuleView();
|
_navigationService.navigateToLearnModuleView(level: level);
|
||||||
|
|
||||||
|
// Remote api call
|
||||||
|
|
||||||
|
// Learn levels
|
||||||
|
Future<void> getLevels(int id) async =>
|
||||||
|
await runBusyFuture(_getLevels(id), busyObject: StateObjects.learnLevels);
|
||||||
|
|
||||||
|
Future<void> _getLevels(int id) async {
|
||||||
|
if (_levels.isEmpty) {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
_levels = await _apiService.getLevels(id);
|
||||||
|
_levels.sort(
|
||||||
|
(a, b) => (a.displayOrder ?? 0).compareTo(b.displayOrder ?? 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,26 @@
|
||||||
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/level.dart';
|
||||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
import 'package:yimaru_app/ui/common/enmus.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_learn_progress.dart';
|
||||||
|
|
||||||
|
import '../../../models/module.dart';
|
||||||
import '../../common/app_colors.dart';
|
import '../../common/app_colors.dart';
|
||||||
import '../../common/ui_helpers.dart';
|
import '../../common/ui_helpers.dart';
|
||||||
import '../../widgets/small_app_bar.dart';
|
import '../../widgets/small_app_bar.dart';
|
||||||
import 'learn_module_viewmodel.dart';
|
import 'learn_module_viewmodel.dart';
|
||||||
|
|
||||||
class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
||||||
const LearnModuleView({Key? key}) : super(key: key);
|
final Level level;
|
||||||
|
|
||||||
|
const LearnModuleView({Key? key, required this.level}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onViewModelReady(LearnModuleViewModel viewModel) async {
|
||||||
|
await viewModel.getModules(level.id ?? 0);
|
||||||
|
super.onViewModelReady(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
LearnModuleViewModel viewModelBuilder(BuildContext context) =>
|
LearnModuleViewModel viewModelBuilder(BuildContext context) =>
|
||||||
|
|
@ -42,7 +52,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildAppBar(viewModel),
|
_buildAppBar(viewModel),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildLevelsColumnWrapper(viewModel),
|
_buildModulesColumnWrapper(viewModel),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -51,7 +61,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLevelsColumnWrapper(LearnModuleViewModel viewModel) =>
|
Widget _buildModulesColumnWrapper(LearnModuleViewModel viewModel) =>
|
||||||
Expanded(child: _buildLevelsColumnScrollView(viewModel));
|
Expanded(child: _buildLevelsColumnScrollView(viewModel));
|
||||||
|
|
||||||
Widget _buildLevelsColumnScrollView(LearnModuleViewModel viewModel) =>
|
Widget _buildLevelsColumnScrollView(LearnModuleViewModel viewModel) =>
|
||||||
|
|
@ -61,7 +71,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
||||||
|
|
||||||
Widget _buildLevelsColumn(LearnModuleViewModel viewModel) => Column(
|
Widget _buildLevelsColumn(LearnModuleViewModel viewModel) => Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: _buildLevelsColumnChildren(viewModel),
|
children: _buildLevelsColumnChildren(viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -76,7 +86,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
'A1 - Beginner',
|
level.title ?? '',
|
||||||
style: style18P600,
|
style: style18P600,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -92,27 +102,19 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
||||||
itemCount: viewModel.modules.length,
|
itemCount: viewModel.modules.length,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemBuilder: (context, index) => _buildTile(
|
itemBuilder: (context, index) => _buildTile(
|
||||||
title: viewModel.modules[index]['title'],
|
module: viewModel.modules[index],
|
||||||
status: viewModel.modules[index]['status'],
|
onLessonTap: () {},
|
||||||
topics: viewModel.modules[index]['topics'],
|
onPracticeTap: () {}),
|
||||||
subtitle: viewModel.modules[index]['subtitle'],
|
|
||||||
practices: viewModel.modules[index]['practices'],
|
|
||||||
description: viewModel.modules[index]['description']),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTile(
|
Widget _buildTile({
|
||||||
{required String title,
|
required Module module,
|
||||||
required String topics,
|
required GestureTapCallback onLessonTap,
|
||||||
required String subtitle,
|
required GestureTapCallback onPracticeTap,
|
||||||
required String description,
|
}) =>
|
||||||
required ProgressStatuses status,
|
|
||||||
required List<Map<String, dynamic>> practices}) =>
|
|
||||||
LearnModuleTile(
|
LearnModuleTile(
|
||||||
title: title,
|
module: module,
|
||||||
status: status,
|
onLessonTap: onLessonTap,
|
||||||
topics: topics,
|
onPracticeTap: onPracticeTap,
|
||||||
subtitle: subtitle,
|
|
||||||
practices: practices,
|
|
||||||
description: description,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,351 +1,25 @@
|
||||||
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/app/app.router.dart';
|
import 'package:yimaru_app/app/app.router.dart';
|
||||||
|
import 'package:yimaru_app/models/module.dart';
|
||||||
|
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
|
import '../../../services/api_service.dart';
|
||||||
|
import '../../../services/status_checker_service.dart';
|
||||||
import '../../common/enmus.dart';
|
import '../../common/enmus.dart';
|
||||||
|
|
||||||
class LearnModuleViewModel extends BaseViewModel {
|
class LearnModuleViewModel extends BaseViewModel {
|
||||||
|
// Dependency injection
|
||||||
|
final _apiService = locator<ApiService>();
|
||||||
|
|
||||||
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
|
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
// Modules
|
// Learn module
|
||||||
final List<Map<String, dynamic>> _modules = [
|
List<Module> _modules = [];
|
||||||
{
|
|
||||||
'status': ProgressStatuses.started,
|
|
||||||
'title': 'Lesson 1.1',
|
|
||||||
'subtitle': 'Start Speaking English Today! Greetings & Introductions',
|
|
||||||
'topics':
|
|
||||||
"""👉 How to use "Good Morning," "Afternoon," and "Evening" at the right time.
|
|
||||||
👉 Why saying "I'm" is often better than "I am" in spoken English.
|
|
||||||
👉 Master "My," "Your," and the verb "To Be" without the headache.
|
|
||||||
👉 How to perfectly say the "Long I" sound in "Hi" and "My."
|
|
||||||
""",
|
|
||||||
'practices': [
|
|
||||||
{
|
|
||||||
'question_text': 'Good morning! How are you?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1El9hhmZvLnrTYtVreHR0EDXNrZyGphT9/view?usp=sharing',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1dUSahuj_VdunV293gEZr0XL9d4WV7_8G/view?usp=sharing'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'What\'s your name?',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/14oAqcMRltXeQhQ-RTGizO2DZ4CkHmKdu/view?usp=drive_link',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1iAsOIXD4NcsctKuubnvWlnwXodTMlou5/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'Nice to meet you!',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/10bOaNCcpNFzxpJ4d2-5KYahMCCcWt668/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1h8V6lFuOiOf0zRRN-NvYjtLrmMdx0SeX/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text':
|
|
||||||
'You are doing great! Tell me, are you happy to be here?',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1sjNFofDRr9KB8fQptdM12LDJ5DIaHujs/view?usp=drive_link',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1kcNIL5NCt_lfTzAbmCkM3yojD_UzjSx7/view?usp=drive_link',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'description':
|
|
||||||
"""Stop feeling nervous when you meet someone new! In this lesson, we break down the most common English greetings and show you exactly how to introduce yourself with confidence.
|
|
||||||
Whether you are at the market, in a classroom, or meeting someone online, the first 10 seconds of a conversation are the most important. Our teacher will guide you through the exact phrases you need to sound natural and friendly from the very first moment.
|
|
||||||
""",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'status': ProgressStatuses.started,
|
|
||||||
'title': 'Lesson 1.2',
|
|
||||||
'subtitle': 'Talk About Your Home! "Where Are You From?" & Locations',
|
|
||||||
'topics':
|
|
||||||
"""👉 The difference between "Where are you from?" and "Where do you live?"
|
|
||||||
👉 How to use "Am," "Is," and "Are" to talk about your home.
|
|
||||||
👉 Learn to say "Where-are-you" as one smooth sound (word linking).
|
|
||||||
👉 Practice a full conversation with Miss Alem.
|
|
||||||
👉 A fast-paced game to test your speed and memory.
|
|
||||||
""",
|
|
||||||
'practices': [
|
|
||||||
{
|
|
||||||
'question_text':
|
|
||||||
'Hey, It is a pleasure to meet you. Where are you from?',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1F98PdPqeDhkF5KMzFjgmUoy4HanIDHVs/view?usp=drive_link',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1vipFTk-DYgOCYhyDyWVaORec7g0JeidD/view?usp=drive_link',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'Ethiopia is beautiful! What city are you from?',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1nZJLV9lOgFGqYr-W3vlpt8rbay_bwARJ/view?usp=drive_link',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1T8JIYQ6T9Mq_7TazGr85ag4PCyQgHhzi/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'I see. And where does your family live now?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/19XAbHL3HqTpPcolvOQUVPSGef-Ythusu/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1O8o11DNKYZBcm1-f8pjMFJjNIvGmawvg/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'description':
|
|
||||||
"""Are you ready to talk about your city, town, or country with pride? In Lesson 1.2, we move past basic greetings and learn how to share where you come from and where you live now.
|
|
||||||
Whether you are meeting a tourist, a new classmate, or a colleague, being able to say "I’m from Ethiopia" or "I live in Addis Ababa" is the best way to build a connection. Miss Alem will show you how to link your words together so you sound like a natural English speaker!
|
|
||||||
""",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'title': 'Lesson 1.3',
|
|
||||||
'status': ProgressStatuses.started,
|
|
||||||
'subtitle': 'Talk About Your Family! Master "Have" vs. "Has"',
|
|
||||||
'topics':
|
|
||||||
"""👉 Master the names for parents, grandparents, and the secret word for brothers and sisters (Siblings).
|
|
||||||
👉 Never mix up "I have" and "She has" ever again.
|
|
||||||
👉 Why the letter "S" is the most important sound when talking about your brothers.
|
|
||||||
👉 A close-up look at how to pronounce "Mother" and "Father" like a native speaker.
|
|
||||||
👉 Test your eyes and ears by finding the hidden mistakes in our family game.
|
|
||||||
""",
|
|
||||||
'practices': [
|
|
||||||
{
|
|
||||||
'question_text':
|
|
||||||
'Hello! I want to know about your family. Do you have a brother or sister?',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1fred7Y5ocdD4codZuK7Pr349O38UkB0Z/view?usp=drive_link',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1tMoTA4D6joz0bkWS5Nfn-neUvbej6XDI/view?usp=drive_link',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'That is nice! How many people are in your family?',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1dhYhiycWwdtW0ndJUMdMXxxVyrXLXPCM/view?usp=drive_link',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1FwRxWvrxzKbhEIYImy2ONzd410pabkJa/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'Do you have grandparents?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1UBaQYiJINgOmZWDLFKeXZQ8Zfb7cOKNZ/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/14cnEJTO7GtGZcO8ag50JdupV2EkvDHes/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'description':
|
|
||||||
"""Do you have brothers and sisters? In Lesson 1.3, we dive into the most personal and important topic for building friendships: Your Family. Many beginners make mistakes with the words "have" and "has," but today, we will fix that! Our teacher will teach you the exact formulas to describe your parents, siblings, and grandparents with perfect grammar. By the end of this video, you will be able to introduce your entire family in English with confidence.""",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'status': ProgressStatuses.started,
|
|
||||||
'title': 'Lesson 1.4',
|
|
||||||
'subtitle': '"What Do You Do?" Talk About Your Job & Studies',
|
|
||||||
'topics':
|
|
||||||
"""👉 The clear difference between talking about work and academic life.
|
|
||||||
👉 Simple rules to decide between "I’m a teacher" and "I’m an accountant."
|
|
||||||
👉 How to use the "-ing" form if you are still training for your dream job.
|
|
||||||
👉 Perfecting the final "T" in "Student" and the "SH" sound in "Cashier."
|
|
||||||
👉 A high-energy game to test your grammar reflexes!""",
|
|
||||||
'practices': [
|
|
||||||
{
|
|
||||||
'question_text': 'It is good to see you again! What do you do?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/171fh_iN0aXS0t95_5RzcxFiVmLCB7caP/view?usp=sharing',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1wDkbp23F2PsLZM9mWlRd-JjnwZa97T-8/view?usp=drive_link',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text':
|
|
||||||
'That is great. Are you studying to be a professional? What are you studying?',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1egHUMIqGt9VAw5HMVMdjWR3zmamdSB6F/view?usp=drive_link',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1T_qrH_-KHgQFw5kX4Ti-UwXvnBFyJsWT/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text':
|
|
||||||
'Your sister works in a restaurant. Is she a waitress?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1Okd-W2zRZCukQfnbLEp9A4r_5rt99U_6/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1t-_JHXel9dtSHRwYU_CO1Mc8-Pa1pOa0/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'My friend works in a bank. Is he an accountant?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1K4ahmIzuBdrsUoO-gVRp525ZFyP7b1Cq/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1ecpWSQTvU9pu8BYq3qRkYUjblX5wG4RZ/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'description':
|
|
||||||
"""You’ve shared your name and your home—now it’s time to talk about your daily life! In Lesson 1.4, we master the most common question in English: "What do you do?" Whether you are working a full-time job, searching for a new career, or currently studying, this lesson gives you the exact phrases to describe your profession with perfect grammar.
|
|
||||||
Miss [Name] breaks down the tricky "A vs. An" rule so you never have to second-guess yourself again. Plus, learn how to sound more natural by "linking" your words like a native speaker!
|
|
||||||
"""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'title': 'Lesson 1.5',
|
|
||||||
'status': ProgressStatuses.started,
|
|
||||||
'subtitle': 'Talk About Your Day! Daily Routines & Time Words',
|
|
||||||
'topics':
|
|
||||||
"""👉 Master the 6 most common actions, from "Wake up" to "Go to bed."
|
|
||||||
👉 Learn why we say "I eat" but "She eats" (and how to never forget it!).
|
|
||||||
👉 Use "First," "Then," and "Finally" to tell a complete story about your day.
|
|
||||||
👉 How to perfectly say the tricky "TH" sound in the word "Teeth."
|
|
||||||
👉 A fast-paced game to test if your brain can handle the "S" rule under pressure!""",
|
|
||||||
'practices': [
|
|
||||||
{
|
|
||||||
'question_text':
|
|
||||||
'I want to hear about your day! What is the first thing you do in the morning?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1B_g45crvD0X2xfJJuCBdBhe7LNRJtKpa/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/13nV3uQ6Vftc0DznBJ8GQMi26dHxmMUwH/view?usp=drive_link',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text':
|
|
||||||
'That is a great start. What do you do for your hygiene? I brush...',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1ijbKHO9A3PEOUb712ycCw-P3ZvWX-NTp/view?usp=drive_link',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1r92ixsWSD58TrV9LIArPRaKk1bVE5m44/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text':
|
|
||||||
'Perfect. And then, what do you do before you leave the house?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1xEHuliUwyqFV35o_C2varAHcoI3QrOsS/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1RJgS3tp0ZKQ1YIt9fLRch-IqFialZR6Q/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text':
|
|
||||||
'And finally, what is the last thing you do at night?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1njWyQExmgAPWgDTrcpqitEoNjiPfCbEw/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/140O0xAkYpVo5FLZQmbc1BaY_6agYWeki/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'description':
|
|
||||||
"""What do you do from the moment you wake up until you go to bed? In Lesson 1.5, we master the art of describing your Daily Routine at Home. This is the best way to practice the "Simple Present Tense" so you can talk about your habits, chores, and schedules like a pro.
|
|
||||||
Our teacher will show you the "S" secret—the most important grammar rule for talking about other people—and teach you the "Time Order Words" that make your English flow naturally from one activity to the next.
|
|
||||||
"""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'title': 'Lesson 1.6',
|
|
||||||
'status': ProgressStatuses.started,
|
|
||||||
'subtitle': 'Express Your Feelings! Likes, Dislikes & Hobbies',
|
|
||||||
'topics':
|
|
||||||
"""👉 How to build perfect positive and negative sentences (I like vs. I don't like).
|
|
||||||
👉 Master the "Do you like...?" structure and the correct short answers.
|
|
||||||
👉 Learn why we switch from "Don't" to "Doesn't" for He and She.
|
|
||||||
👉 How to master the "L" sound and make your "Don't" sound natural and fast.
|
|
||||||
👉 A high-speed game to test if you can handle the grammar of likes and dislikes!""",
|
|
||||||
'practices': [
|
|
||||||
{
|
|
||||||
'question_text':
|
|
||||||
'I want to know about your hobbies. Do you like football?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1ombhL58UTSSMlVr4OkjEN2m1TXeIs3Jw/view?usp=sharing',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1E-NilYZkazwTyAFwwNunDQmWIxBHH_GL/view?usp=drive_link',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text':
|
|
||||||
'That’s fun! Some people prefer quiet things. What do you like to do at home',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1HJqwHQId9MUhaQvjoyhuhk-ZQNCz5-RP/view?usp=drive_link',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1HJqwHQId9MUhaQvjoyhuhk-ZQNCz5-RP/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text':
|
|
||||||
'Interesting. Many people like music. Do you like slow music?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/13edPllK_dZYmmFIsue_A-sGmDe9fh89K/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1lA18zdiFbKKXaGwUvJv4JwBMW_Vxxkip/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'Do you like running?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1MSVceVXL-mAELGvv0_SN68t1-pCM-V6R/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1XkwoWINsoXDSe63kjJXQFczjtPKbBTY4/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'description':
|
|
||||||
"""What makes you happy? What do you avoid? In Lesson 1.6, we learn how to make your English conversations personal and fun by sharing your Likes and Dislikes. This is the final topic of Module 1, and it is the key to building real friendships by finding common interests!
|
|
||||||
We will see how to use the power of "Do" and "Don't," and show you how the "S" rule changes when we talk about what our friends like. Get ready to talk about football, coffee, music, and more!
|
|
||||||
"""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'title': 'Lesson 1.7',
|
|
||||||
'status': ProgressStatuses.started,
|
|
||||||
'subtitle': 'Module 1 Final Test! Speaking Review & Graduation',
|
|
||||||
'topics': """👉 Fast-paced practice for names, origins, and jobs.
|
|
||||||
Mastering "Have," "Has," and the plural "S" under pressure.
|
|
||||||
👉 Using the Simple Present Tense to describe your day and your likes.
|
|
||||||
👉 A bonus challenge to find and correct common beginner mistakes.
|
|
||||||
👉 Two speaking scripts to help you link your words and sound like a native speaker.""",
|
|
||||||
'practices': [
|
|
||||||
{
|
|
||||||
'question_text': 'Hello! What\'s your name?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/11xsnZpqOXQREc2nku77u_qOXucUdncDq/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1OgvhumoPNdw33WfxEZfS1xtYZHshAM-k/view?usp=drive_link',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'It was a pleasure meeting you. Nice to meet you!',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1NmKeyx0mKmbFyZdmQ4sWGM6UMpwAIrHN/view?usp=drive_link',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1nu1fiK4dLCjW9jjENvThshU48vFj-T3h/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'Where are you from?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1uM2L6-u0H-LHyDQL_Y0txjoO64n5lHkp/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1VXhHRG2CJR3lx07cAuiZrfCxzxdOcCIz/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'And where do you live now?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1sGJpaRQ1wArA2iDq7BZM-56abMZwiISO/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/10Cj8r2KeYUttBJ7W2wETP8XSdK2ZItom/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'Do you have a brother or sister?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1cA4wD7zY7WRgorWladvf1f8QRu54oBBw/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1IeiGyN1S5XaREfXgmzfl-zESap8A_k3H/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'I am a teacher. Are you a student?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1n5w3skHBvQ2hE_OA2HdeUnr0YeN4zL2_/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1j86TVmeNzZvL8RueZeIueCQO_40i9aMh/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'question_text': 'What is the first thing you do in the morning?',
|
|
||||||
'question_audio_url':
|
|
||||||
'https://drive.google.com/file/d/1oCrDDtYF4-1S2UYu6shilqmQSqyRG3v_/view?usp=drive_link',
|
|
||||||
'sample_answer':
|
|
||||||
'https://drive.google.com/file/d/1shpCr2XvV12cXaxuJ-AjW-0XWvBdtT8Q/view?usp=drive_link'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'description':
|
|
||||||
"""Congratulations! You have reached the end of Module 1. You started with simple greetings, and now you are ready to hold a full conversation in English! This is our Final Speaking Review, where we test everything you’ve learned so far.
|
|
||||||
From your name and family to your daily habits and hobbies, this lesson is designed to push your speed, accuracy, and confidence. We will lead you through three high-intensity speaking drills and a bonus Grammar Check to make sure you are 100% ready for Module 2!
|
|
||||||
"""
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> get modules => _modules;
|
List<Module> get modules => _modules;
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
@ -371,4 +45,20 @@ From your name and family to your daily habits and hobbies, this lesson is desig
|
||||||
buttonLabel: 'Begin Lesson Practice',
|
buttonLabel: 'Begin Lesson Practice',
|
||||||
subtitle: 'Let’s quickly review what you’ve learned in this lesson!',
|
subtitle: 'Let’s quickly review what you’ve learned in this lesson!',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Remote api call
|
||||||
|
|
||||||
|
// Learn modules
|
||||||
|
Future<void> getModules(int id) async => await runBusyFuture(_getModules(id),
|
||||||
|
busyObject: StateObjects.learnModules);
|
||||||
|
|
||||||
|
Future<void> _getModules(int id) async {
|
||||||
|
if (_modules.isEmpty) {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
_modules = await _apiService.getModules(id);
|
||||||
|
_modules.sort(
|
||||||
|
(a, b) => (a.displayOrder ?? 0).compareTo(b.displayOrder ?? 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import 'package:audioplayers/audioplayers.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:waveform_recorder/waveform_recorder.dart';
|
import 'package:waveform_recorder/waveform_recorder.dart';
|
||||||
import 'package:yimaru_app/models/user_model.dart';
|
import 'package:yimaru_app/models/user.dart';
|
||||||
import 'package:yimaru_app/services/authentication_service.dart';
|
import 'package:yimaru_app/services/authentication_service.dart';
|
||||||
import 'package:yimaru_app/services/voice_recorder_service.dart';
|
import 'package:yimaru_app/services/voice_recorder_service.dart';
|
||||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||||
|
|
@ -32,9 +32,9 @@ class LearnPracticeViewModel extends ReactiveViewModel {
|
||||||
[_audioPlayerService, _voiceRecorderService, _authenticationService];
|
[_audioPlayerService, _voiceRecorderService, _authenticationService];
|
||||||
|
|
||||||
// User
|
// User
|
||||||
UserModel? get _user => _authenticationService.user;
|
User? get _user => _authenticationService.user;
|
||||||
|
|
||||||
UserModel? get user => _user;
|
User? get user => _user;
|
||||||
|
|
||||||
// AudioPlayer
|
// AudioPlayer
|
||||||
AudioPlayer get _player => _audioPlayerService.player;
|
AudioPlayer get _player => _audioPlayerService.player;
|
||||||
|
|
@ -55,7 +55,6 @@ class LearnPracticeViewModel extends ReactiveViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Voice recorder
|
// Voice recorder
|
||||||
|
|
||||||
WaveformRecorderController get _waveController =>
|
WaveformRecorderController get _waveController =>
|
||||||
_voiceRecorderService.waveController;
|
_voiceRecorderService.waveController;
|
||||||
|
|
||||||
|
|
@ -82,22 +81,36 @@ class LearnPracticeViewModel extends ReactiveViewModel {
|
||||||
|
|
||||||
Map<String, dynamic> get selectedPractice => _selectedPractice;
|
Map<String, dynamic> get selectedPractice => _selectedPractice;
|
||||||
|
|
||||||
// Practice
|
// Voice recorder
|
||||||
void setPractice(Map<String, dynamic> practice) {
|
Future<void> stopRecording() async =>
|
||||||
_selectedPractice = practice;
|
await _voiceRecorderService.stopRecording();
|
||||||
goTo(1);
|
|
||||||
|
Future<void> startRecording() async => await runBusyFuture(_startRecording(),
|
||||||
|
busyObject: StateObjects.recordLearnPracticeAnswer);
|
||||||
|
|
||||||
|
Future<void> _startRecording() async =>
|
||||||
|
await _voiceRecorderService.startRecording();
|
||||||
|
|
||||||
|
// Sample audio
|
||||||
|
Future<void> playSampleAudio() async =>
|
||||||
|
await runBusyFuture(_playSampleAudio(),
|
||||||
|
busyObject: StateObjects.learnPracticeSample);
|
||||||
|
|
||||||
|
Future<void> _playSampleAudio() async {
|
||||||
|
setBusyObject(StateObjects.learnPracticeSample);
|
||||||
|
await _audioPlayerService.playUrl(_selectedPractice['sample_answer']);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> pauseSampleAudio() async =>
|
||||||
|
await runBusyFuture(_pauseSampleAudio(),
|
||||||
|
busyObject: StateObjects.learnPracticeSample);
|
||||||
|
|
||||||
|
Future<void> _pauseSampleAudio() async {
|
||||||
|
setBusyObject(StateObjects.learnPracticeSample);
|
||||||
|
await _audioPlayerService.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play practice audio
|
// Play practice audio
|
||||||
Future<void> playQuestionAudio() async =>
|
|
||||||
await runBusyFuture(_playQuestionAudio(),
|
|
||||||
busyObject: StateObjects.learnPracticeQuestion);
|
|
||||||
|
|
||||||
Future<void> _playQuestionAudio() async {
|
|
||||||
goTo(3);
|
|
||||||
await _audioPlayerService.playUrl(_selectedPractice['question_audio_url']);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listenToAudio() {
|
void _listenToAudio() {
|
||||||
_audioPlayerService.durationStream.listen((dur) {
|
_audioPlayerService.durationStream.listen((dur) {
|
||||||
_duration = dur;
|
_duration = dur;
|
||||||
|
|
@ -112,29 +125,13 @@ class LearnPracticeViewModel extends ReactiveViewModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set busy object
|
Future<void> playQuestionAudio() async =>
|
||||||
|
await runBusyFuture(_playQuestionAudio(),
|
||||||
|
busyObject: StateObjects.learnPracticeQuestion);
|
||||||
|
|
||||||
void setBusyObject(StateObjects object) {
|
Future<void> _playQuestionAudio() async {
|
||||||
_busyObject = object;
|
goTo(3);
|
||||||
notifyListeners();
|
await _audioPlayerService.playUrl(_selectedPractice['question_audio_url']);
|
||||||
}
|
|
||||||
|
|
||||||
// Sample audio
|
|
||||||
Future<void> playSampleAudio() async =>
|
|
||||||
await runBusyFuture(_playSampleAudio(),
|
|
||||||
busyObject: StateObjects.learnPracticeSample);
|
|
||||||
Future<void> _playSampleAudio() async {
|
|
||||||
setBusyObject(StateObjects.learnPracticeSample);
|
|
||||||
await _audioPlayerService.playUrl(_selectedPractice['sample_answer']);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> pauseSampleAudio() async =>
|
|
||||||
await runBusyFuture(_pauseSampleAudio(),
|
|
||||||
busyObject: StateObjects.learnPracticeSample);
|
|
||||||
|
|
||||||
Future<void> _pauseSampleAudio() async {
|
|
||||||
setBusyObject(StateObjects.learnPracticeSample);
|
|
||||||
await _audioPlayerService.pause();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recorded audio
|
// Recorded audio
|
||||||
|
|
@ -157,15 +154,17 @@ class LearnPracticeViewModel extends ReactiveViewModel {
|
||||||
await _audioPlayerService.pause();
|
await _audioPlayerService.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Voice recorder
|
// Set busy object
|
||||||
Future<void> startRecording() async => await runBusyFuture(_startRecording(),
|
void setBusyObject(StateObjects object) {
|
||||||
busyObject: StateObjects.recordLearnPracticeAnswer);
|
_busyObject = object;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _startRecording() async =>
|
// Practice
|
||||||
await _voiceRecorderService.startRecording();
|
void setPractice(Map<String, dynamic> practice) {
|
||||||
|
_selectedPractice = practice;
|
||||||
Future<void> stopRecording() async =>
|
goTo(1);
|
||||||
await _voiceRecorderService.stopRecording();
|
}
|
||||||
|
|
||||||
// Dialogue
|
// Dialogue
|
||||||
Future<bool?> showAbortDialog() async {
|
Future<bool?> showAbortDialog() async {
|
||||||
|
|
|
||||||
100
lib/ui/views/learn_subcategory/learn_subcategory_view.dart
Normal file
100
lib/ui/views/learn_subcategory/learn_subcategory_view.dart
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/learn_subcategory_card.dart';
|
||||||
|
|
||||||
|
import '../../../models/subcategory.dart';
|
||||||
|
import '../../common/app_colors.dart';
|
||||||
|
import '../../common/enmus.dart';
|
||||||
|
import '../../common/ui_helpers.dart';
|
||||||
|
import '../../widgets/custom_circular_progress_indicator.dart';
|
||||||
|
import '../../widgets/profile_app_bar.dart';
|
||||||
|
import 'learn_subcategory_viewmodel.dart';
|
||||||
|
|
||||||
|
class LearnSubcategoryView extends StackedView<LearnSubcategoryViewModel> {
|
||||||
|
const LearnSubcategoryView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onViewModelReady(LearnSubcategoryViewModel viewModel) async {
|
||||||
|
await viewModel.getLearnSubcategories();
|
||||||
|
super.onViewModelReady(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
LearnSubcategoryViewModel viewModelBuilder(BuildContext context) =>
|
||||||
|
LearnSubcategoryViewModel();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget builder(
|
||||||
|
BuildContext context,
|
||||||
|
LearnSubcategoryViewModel viewModel,
|
||||||
|
Widget? child,
|
||||||
|
) =>
|
||||||
|
_buildScaffoldWrapper(viewModel);
|
||||||
|
|
||||||
|
Widget _buildScaffoldWrapper(LearnSubcategoryViewModel viewModel) => Scaffold(
|
||||||
|
backgroundColor: kcBackgroundColor,
|
||||||
|
body: _buildScaffold(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildScaffold(LearnSubcategoryViewModel viewModel) =>
|
||||||
|
SafeArea(child: _buildBody(viewModel));
|
||||||
|
|
||||||
|
Widget _buildBody(LearnSubcategoryViewModel viewModel) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
child: _buildColumn(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildColumn(LearnSubcategoryViewModel viewModel) => Column(
|
||||||
|
children: [
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildAppBar(viewModel),
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildSubcategoryColumnWrapper(viewModel)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildAppBar(LearnSubcategoryViewModel viewModel) => ProfileAppBar(
|
||||||
|
name: viewModel.user?.firstName,
|
||||||
|
profileImage: viewModel.user?.profilePicture,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSubcategoryColumnWrapper(LearnSubcategoryViewModel viewModel) =>
|
||||||
|
Expanded(child: _buildSubcategoryColumnScrollView(viewModel));
|
||||||
|
|
||||||
|
Widget _buildSubcategoryColumnScrollView(
|
||||||
|
LearnSubcategoryViewModel viewModel) =>
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: _buildListViewBuilder(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildListViewBuilder(LearnSubcategoryViewModel viewModel) =>
|
||||||
|
viewModel.busy(StateObjects.learnSubcategories)
|
||||||
|
? _buildProgressIndicator()
|
||||||
|
: _buildListView(viewModel);
|
||||||
|
|
||||||
|
Widget _buildProgressIndicator() => const Center(
|
||||||
|
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildListView(LearnSubcategoryViewModel viewModel) =>
|
||||||
|
ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: viewModel.subcategories.length,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
separatorBuilder: (context, index) => verticalSpaceSmall,
|
||||||
|
itemBuilder: (context, index) => _buildTile(
|
||||||
|
subcategory: viewModel.subcategories[index],
|
||||||
|
onTap: () async => await viewModel
|
||||||
|
.navigateToLearn(viewModel.subcategories[index].id ?? 0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildTile({
|
||||||
|
required Subcategory subcategory,
|
||||||
|
required GestureTapCallback onTap,
|
||||||
|
}) =>
|
||||||
|
LearnSubcategoryCard(
|
||||||
|
onTap: onTap,
|
||||||
|
subcategory: subcategory,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
|
||||||
|
import '../../../app/app.locator.dart';
|
||||||
|
import '../../../app/app.router.dart';
|
||||||
|
import '../../../models/subcategory.dart';
|
||||||
|
import '../../../models/user.dart';
|
||||||
|
import '../../../services/api_service.dart';
|
||||||
|
import '../../../services/authentication_service.dart';
|
||||||
|
import '../../../services/status_checker_service.dart';
|
||||||
|
import '../../common/enmus.dart';
|
||||||
|
|
||||||
|
class LearnSubcategoryViewModel extends ReactiveViewModel {
|
||||||
|
// Dependency injection
|
||||||
|
final _apiService = locator<ApiService>();
|
||||||
|
|
||||||
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
|
|
||||||
|
final _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
|
final _authenticationService = locator<AuthenticationService>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<ListenableServiceMixin> get listenableServices =>
|
||||||
|
[_authenticationService];
|
||||||
|
|
||||||
|
// Current user
|
||||||
|
User? get _user => _authenticationService.user;
|
||||||
|
|
||||||
|
User? get user => _user;
|
||||||
|
|
||||||
|
// Learn subcategories
|
||||||
|
List<Subcategory> _subcategories = [];
|
||||||
|
|
||||||
|
List<Subcategory> get subcategories => _subcategories;
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
Future<void> navigateToLearn(int id) async =>
|
||||||
|
_navigationService.navigateToLearnView(id: id);
|
||||||
|
|
||||||
|
// Remote api call
|
||||||
|
|
||||||
|
// Learn subcategories
|
||||||
|
Future<void> getLearnSubcategories() async =>
|
||||||
|
await runBusyFuture(_getLearnSubcategories(),
|
||||||
|
busyObject: StateObjects.learnSubcategories);
|
||||||
|
|
||||||
|
Future<void> _getLearnSubcategories() async {
|
||||||
|
if (_subcategories.isEmpty) {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
_subcategories = await _apiService.getLearnSubcategories();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,8 +11,8 @@ import 'login_viewmodel.dart';
|
||||||
|
|
||||||
@FormView(fields: [
|
@FormView(fields: [
|
||||||
FormTextField(name: 'otp', validator: FormValidator.validateForm),
|
FormTextField(name: 'otp', validator: FormValidator.validateForm),
|
||||||
FormTextField(name: 'email', validator: FormValidator.validateEmailForm),
|
|
||||||
FormTextField(name: 'password', validator: FormValidator.validateForm),
|
FormTextField(name: 'password', validator: FormValidator.validateForm),
|
||||||
|
FormTextField(name: 'email', validator: FormValidator.validateEmailForm),
|
||||||
FormTextField(name: 'phoneNumber', validator: FormValidator.validateForm)
|
FormTextField(name: 'phoneNumber', validator: FormValidator.validateForm)
|
||||||
])
|
])
|
||||||
class LoginView extends StackedView<LoginViewModel> with $LoginView {
|
class LoginView extends StackedView<LoginViewModel> with $LoginView {
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ import 'package:yimaru_app/ui/common/validators/form_validator.dart';
|
||||||
const bool _autoTextFieldValidation = true;
|
const bool _autoTextFieldValidation = true;
|
||||||
|
|
||||||
const String OtpValueKey = 'otp';
|
const String OtpValueKey = 'otp';
|
||||||
const String EmailValueKey = 'email';
|
|
||||||
const String PasswordValueKey = 'password';
|
const String PasswordValueKey = 'password';
|
||||||
|
const String EmailValueKey = 'email';
|
||||||
const String PhoneNumberValueKey = 'phoneNumber';
|
const String PhoneNumberValueKey = 'phoneNumber';
|
||||||
|
|
||||||
final Map<String, TextEditingController> _LoginViewTextEditingControllers = {};
|
final Map<String, TextEditingController> _LoginViewTextEditingControllers = {};
|
||||||
|
|
@ -23,24 +23,24 @@ final Map<String, FocusNode> _LoginViewFocusNodes = {};
|
||||||
|
|
||||||
final Map<String, String? Function(String?)?> _LoginViewTextValidations = {
|
final Map<String, String? Function(String?)?> _LoginViewTextValidations = {
|
||||||
OtpValueKey: FormValidator.validateForm,
|
OtpValueKey: FormValidator.validateForm,
|
||||||
EmailValueKey: FormValidator.validateEmailForm,
|
|
||||||
PasswordValueKey: FormValidator.validateForm,
|
PasswordValueKey: FormValidator.validateForm,
|
||||||
|
EmailValueKey: FormValidator.validateEmailForm,
|
||||||
PhoneNumberValueKey: FormValidator.validateForm,
|
PhoneNumberValueKey: FormValidator.validateForm,
|
||||||
};
|
};
|
||||||
|
|
||||||
mixin $LoginView {
|
mixin $LoginView {
|
||||||
TextEditingController get otpController =>
|
TextEditingController get otpController =>
|
||||||
_getFormTextEditingController(OtpValueKey);
|
_getFormTextEditingController(OtpValueKey);
|
||||||
TextEditingController get emailController =>
|
|
||||||
_getFormTextEditingController(EmailValueKey);
|
|
||||||
TextEditingController get passwordController =>
|
TextEditingController get passwordController =>
|
||||||
_getFormTextEditingController(PasswordValueKey);
|
_getFormTextEditingController(PasswordValueKey);
|
||||||
|
TextEditingController get emailController =>
|
||||||
|
_getFormTextEditingController(EmailValueKey);
|
||||||
TextEditingController get phoneNumberController =>
|
TextEditingController get phoneNumberController =>
|
||||||
_getFormTextEditingController(PhoneNumberValueKey);
|
_getFormTextEditingController(PhoneNumberValueKey);
|
||||||
|
|
||||||
FocusNode get otpFocusNode => _getFormFocusNode(OtpValueKey);
|
FocusNode get otpFocusNode => _getFormFocusNode(OtpValueKey);
|
||||||
FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey);
|
|
||||||
FocusNode get passwordFocusNode => _getFormFocusNode(PasswordValueKey);
|
FocusNode get passwordFocusNode => _getFormFocusNode(PasswordValueKey);
|
||||||
|
FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey);
|
||||||
FocusNode get phoneNumberFocusNode => _getFormFocusNode(PhoneNumberValueKey);
|
FocusNode get phoneNumberFocusNode => _getFormFocusNode(PhoneNumberValueKey);
|
||||||
|
|
||||||
TextEditingController _getFormTextEditingController(
|
TextEditingController _getFormTextEditingController(
|
||||||
|
|
@ -68,8 +68,8 @@ mixin $LoginView {
|
||||||
/// with the latest textController values
|
/// with the latest textController values
|
||||||
void syncFormWithViewModel(FormStateHelper model) {
|
void syncFormWithViewModel(FormStateHelper model) {
|
||||||
otpController.addListener(() => _updateFormData(model));
|
otpController.addListener(() => _updateFormData(model));
|
||||||
emailController.addListener(() => _updateFormData(model));
|
|
||||||
passwordController.addListener(() => _updateFormData(model));
|
passwordController.addListener(() => _updateFormData(model));
|
||||||
|
emailController.addListener(() => _updateFormData(model));
|
||||||
phoneNumberController.addListener(() => _updateFormData(model));
|
phoneNumberController.addListener(() => _updateFormData(model));
|
||||||
|
|
||||||
_updateFormData(model, forceValidate: _autoTextFieldValidation);
|
_updateFormData(model, forceValidate: _autoTextFieldValidation);
|
||||||
|
|
@ -83,8 +83,8 @@ mixin $LoginView {
|
||||||
)
|
)
|
||||||
void listenToFormUpdated(FormViewModel model) {
|
void listenToFormUpdated(FormViewModel model) {
|
||||||
otpController.addListener(() => _updateFormData(model));
|
otpController.addListener(() => _updateFormData(model));
|
||||||
emailController.addListener(() => _updateFormData(model));
|
|
||||||
passwordController.addListener(() => _updateFormData(model));
|
passwordController.addListener(() => _updateFormData(model));
|
||||||
|
emailController.addListener(() => _updateFormData(model));
|
||||||
phoneNumberController.addListener(() => _updateFormData(model));
|
phoneNumberController.addListener(() => _updateFormData(model));
|
||||||
|
|
||||||
_updateFormData(model, forceValidate: _autoTextFieldValidation);
|
_updateFormData(model, forceValidate: _autoTextFieldValidation);
|
||||||
|
|
@ -96,8 +96,8 @@ mixin $LoginView {
|
||||||
model.formValueMap
|
model.formValueMap
|
||||||
..addAll({
|
..addAll({
|
||||||
OtpValueKey: otpController.text,
|
OtpValueKey: otpController.text,
|
||||||
EmailValueKey: emailController.text,
|
|
||||||
PasswordValueKey: passwordController.text,
|
PasswordValueKey: passwordController.text,
|
||||||
|
EmailValueKey: emailController.text,
|
||||||
PhoneNumberValueKey: phoneNumberController.text,
|
PhoneNumberValueKey: phoneNumberController.text,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -141,8 +141,8 @@ extension ValueProperties on FormStateHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
String? get otpValue => this.formValueMap[OtpValueKey] as String?;
|
String? get otpValue => this.formValueMap[OtpValueKey] as String?;
|
||||||
String? get emailValue => this.formValueMap[EmailValueKey] as String?;
|
|
||||||
String? get passwordValue => this.formValueMap[PasswordValueKey] as String?;
|
String? get passwordValue => this.formValueMap[PasswordValueKey] as String?;
|
||||||
|
String? get emailValue => this.formValueMap[EmailValueKey] as String?;
|
||||||
String? get phoneNumberValue =>
|
String? get phoneNumberValue =>
|
||||||
this.formValueMap[PhoneNumberValueKey] as String?;
|
this.formValueMap[PhoneNumberValueKey] as String?;
|
||||||
|
|
||||||
|
|
@ -156,16 +156,6 @@ extension ValueProperties on FormStateHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set emailValue(String? value) {
|
|
||||||
this.setData(
|
|
||||||
this.formValueMap..addAll({EmailValueKey: value}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (_LoginViewTextEditingControllers.containsKey(EmailValueKey)) {
|
|
||||||
_LoginViewTextEditingControllers[EmailValueKey]?.text = value ?? '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set passwordValue(String? value) {
|
set passwordValue(String? value) {
|
||||||
this.setData(
|
this.setData(
|
||||||
this.formValueMap..addAll({PasswordValueKey: value}),
|
this.formValueMap..addAll({PasswordValueKey: value}),
|
||||||
|
|
@ -176,6 +166,16 @@ extension ValueProperties on FormStateHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set emailValue(String? value) {
|
||||||
|
this.setData(
|
||||||
|
this.formValueMap..addAll({EmailValueKey: value}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (_LoginViewTextEditingControllers.containsKey(EmailValueKey)) {
|
||||||
|
_LoginViewTextEditingControllers[EmailValueKey]?.text = value ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
set phoneNumberValue(String? value) {
|
set phoneNumberValue(String? value) {
|
||||||
this.setData(
|
this.setData(
|
||||||
this.formValueMap..addAll({PhoneNumberValueKey: value}),
|
this.formValueMap..addAll({PhoneNumberValueKey: value}),
|
||||||
|
|
@ -189,31 +189,31 @@ extension ValueProperties on FormStateHelper {
|
||||||
bool get hasOtp =>
|
bool get hasOtp =>
|
||||||
this.formValueMap.containsKey(OtpValueKey) &&
|
this.formValueMap.containsKey(OtpValueKey) &&
|
||||||
(otpValue?.isNotEmpty ?? false);
|
(otpValue?.isNotEmpty ?? false);
|
||||||
bool get hasEmail =>
|
|
||||||
this.formValueMap.containsKey(EmailValueKey) &&
|
|
||||||
(emailValue?.isNotEmpty ?? false);
|
|
||||||
bool get hasPassword =>
|
bool get hasPassword =>
|
||||||
this.formValueMap.containsKey(PasswordValueKey) &&
|
this.formValueMap.containsKey(PasswordValueKey) &&
|
||||||
(passwordValue?.isNotEmpty ?? false);
|
(passwordValue?.isNotEmpty ?? false);
|
||||||
|
bool get hasEmail =>
|
||||||
|
this.formValueMap.containsKey(EmailValueKey) &&
|
||||||
|
(emailValue?.isNotEmpty ?? false);
|
||||||
bool get hasPhoneNumber =>
|
bool get hasPhoneNumber =>
|
||||||
this.formValueMap.containsKey(PhoneNumberValueKey) &&
|
this.formValueMap.containsKey(PhoneNumberValueKey) &&
|
||||||
(phoneNumberValue?.isNotEmpty ?? false);
|
(phoneNumberValue?.isNotEmpty ?? false);
|
||||||
|
|
||||||
bool get hasOtpValidationMessage =>
|
bool get hasOtpValidationMessage =>
|
||||||
this.fieldsValidationMessages[OtpValueKey]?.isNotEmpty ?? false;
|
this.fieldsValidationMessages[OtpValueKey]?.isNotEmpty ?? false;
|
||||||
bool get hasEmailValidationMessage =>
|
|
||||||
this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false;
|
|
||||||
bool get hasPasswordValidationMessage =>
|
bool get hasPasswordValidationMessage =>
|
||||||
this.fieldsValidationMessages[PasswordValueKey]?.isNotEmpty ?? false;
|
this.fieldsValidationMessages[PasswordValueKey]?.isNotEmpty ?? false;
|
||||||
|
bool get hasEmailValidationMessage =>
|
||||||
|
this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false;
|
||||||
bool get hasPhoneNumberValidationMessage =>
|
bool get hasPhoneNumberValidationMessage =>
|
||||||
this.fieldsValidationMessages[PhoneNumberValueKey]?.isNotEmpty ?? false;
|
this.fieldsValidationMessages[PhoneNumberValueKey]?.isNotEmpty ?? false;
|
||||||
|
|
||||||
String? get otpValidationMessage =>
|
String? get otpValidationMessage =>
|
||||||
this.fieldsValidationMessages[OtpValueKey];
|
this.fieldsValidationMessages[OtpValueKey];
|
||||||
String? get emailValidationMessage =>
|
|
||||||
this.fieldsValidationMessages[EmailValueKey];
|
|
||||||
String? get passwordValidationMessage =>
|
String? get passwordValidationMessage =>
|
||||||
this.fieldsValidationMessages[PasswordValueKey];
|
this.fieldsValidationMessages[PasswordValueKey];
|
||||||
|
String? get emailValidationMessage =>
|
||||||
|
this.fieldsValidationMessages[EmailValueKey];
|
||||||
String? get phoneNumberValidationMessage =>
|
String? get phoneNumberValidationMessage =>
|
||||||
this.fieldsValidationMessages[PhoneNumberValueKey];
|
this.fieldsValidationMessages[PhoneNumberValueKey];
|
||||||
}
|
}
|
||||||
|
|
@ -221,18 +221,18 @@ extension ValueProperties on FormStateHelper {
|
||||||
extension Methods on FormStateHelper {
|
extension Methods on FormStateHelper {
|
||||||
setOtpValidationMessage(String? validationMessage) =>
|
setOtpValidationMessage(String? validationMessage) =>
|
||||||
this.fieldsValidationMessages[OtpValueKey] = validationMessage;
|
this.fieldsValidationMessages[OtpValueKey] = validationMessage;
|
||||||
setEmailValidationMessage(String? validationMessage) =>
|
|
||||||
this.fieldsValidationMessages[EmailValueKey] = validationMessage;
|
|
||||||
setPasswordValidationMessage(String? validationMessage) =>
|
setPasswordValidationMessage(String? validationMessage) =>
|
||||||
this.fieldsValidationMessages[PasswordValueKey] = validationMessage;
|
this.fieldsValidationMessages[PasswordValueKey] = validationMessage;
|
||||||
|
setEmailValidationMessage(String? validationMessage) =>
|
||||||
|
this.fieldsValidationMessages[EmailValueKey] = validationMessage;
|
||||||
setPhoneNumberValidationMessage(String? validationMessage) =>
|
setPhoneNumberValidationMessage(String? validationMessage) =>
|
||||||
this.fieldsValidationMessages[PhoneNumberValueKey] = validationMessage;
|
this.fieldsValidationMessages[PhoneNumberValueKey] = validationMessage;
|
||||||
|
|
||||||
/// Clears text input fields on the Form
|
/// Clears text input fields on the Form
|
||||||
void clearForm() {
|
void clearForm() {
|
||||||
otpValue = '';
|
otpValue = '';
|
||||||
emailValue = '';
|
|
||||||
passwordValue = '';
|
passwordValue = '';
|
||||||
|
emailValue = '';
|
||||||
phoneNumberValue = '';
|
phoneNumberValue = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,8 +240,8 @@ extension Methods on FormStateHelper {
|
||||||
void validateForm() {
|
void validateForm() {
|
||||||
this.setValidationMessages({
|
this.setValidationMessages({
|
||||||
OtpValueKey: getValidationMessage(OtpValueKey),
|
OtpValueKey: getValidationMessage(OtpValueKey),
|
||||||
EmailValueKey: getValidationMessage(EmailValueKey),
|
|
||||||
PasswordValueKey: getValidationMessage(PasswordValueKey),
|
PasswordValueKey: getValidationMessage(PasswordValueKey),
|
||||||
|
EmailValueKey: getValidationMessage(EmailValueKey),
|
||||||
PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey),
|
PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -263,7 +263,7 @@ String? getValidationMessage(String key) {
|
||||||
void updateValidationData(FormStateHelper model) =>
|
void updateValidationData(FormStateHelper model) =>
|
||||||
model.setValidationMessages({
|
model.setValidationMessages({
|
||||||
OtpValueKey: getValidationMessage(OtpValueKey),
|
OtpValueKey: getValidationMessage(OtpValueKey),
|
||||||
EmailValueKey: getValidationMessage(EmailValueKey),
|
|
||||||
PasswordValueKey: getValidationMessage(PasswordValueKey),
|
PasswordValueKey: getValidationMessage(PasswordValueKey),
|
||||||
|
EmailValueKey: getValidationMessage(EmailValueKey),
|
||||||
PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey),
|
PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
import 'package:yimaru_app/app/app.locator.dart';
|
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:yimaru_app/models/user_model.dart';
|
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';
|
||||||
|
|
@ -29,8 +29,6 @@ class LoginViewModel extends ReactiveViewModel
|
||||||
@override
|
@override
|
||||||
List<ListenableServiceMixin> get listenableServices => [_googleAuthService];
|
List<ListenableServiceMixin> get listenableServices => [_googleAuthService];
|
||||||
|
|
||||||
// Sms
|
|
||||||
|
|
||||||
// Google user
|
// Google user
|
||||||
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
|
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
|
||||||
|
|
||||||
|
|
@ -128,21 +126,16 @@ class LoginViewModel extends ReactiveViewModel
|
||||||
_resendTime = DateTime.now().add(const Duration(minutes: 3, seconds: 0));
|
_resendTime = DateTime.now().add(const Duration(minutes: 3, seconds: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add user data
|
// User data
|
||||||
void addUserData(Map<String, dynamic> data) {
|
|
||||||
_userData.addAll(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearUserData() {
|
void clearUserData() {
|
||||||
_userData.clear();
|
_userData.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// In app navigation
|
void addUserData(Map<String, dynamic> data) {
|
||||||
void goTo(int page) {
|
_userData.addAll(data);
|
||||||
_currentPage = page;
|
|
||||||
rebuildUi();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In app navigation
|
||||||
void goBack() {
|
void goBack() {
|
||||||
if (_currentPage == 1) {
|
if (_currentPage == 1) {
|
||||||
_currentPage = 0;
|
_currentPage = 0;
|
||||||
|
|
@ -155,16 +148,21 @@ class LoginViewModel extends ReactiveViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void goTo(int page) {
|
||||||
|
_currentPage = page;
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
|
Future<void> replaceWithHome() async =>
|
||||||
|
await _navigationService.clearStackAndShow(Routes.homeView);
|
||||||
|
|
||||||
Future<void> navigateToRegister() async =>
|
Future<void> navigateToRegister() async =>
|
||||||
await _navigationService.navigateToRegisterView();
|
await _navigationService.navigateToRegisterView();
|
||||||
|
|
||||||
Future<void> navigateToForgetPassword() async =>
|
Future<void> navigateToForgetPassword() async =>
|
||||||
await _navigationService.navigateToForgetPasswordView();
|
await _navigationService.navigateToForgetPasswordView();
|
||||||
|
|
||||||
Future<void> replaceWithHome() async =>
|
|
||||||
await _navigationService.clearStackAndShow(Routes.homeView);
|
|
||||||
|
|
||||||
// Remote api calls
|
// Remote api calls
|
||||||
|
|
||||||
// Login with email
|
// Login with email
|
||||||
|
|
@ -175,7 +173,7 @@ class LoginViewModel extends ReactiveViewModel
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
Map<String, dynamic> response = await _apiService.login(_userData);
|
Map<String, dynamic> response = await _apiService.login(_userData);
|
||||||
if (response['status'] == ResponseStatus.success) {
|
if (response['status'] == ResponseStatus.success) {
|
||||||
UserModel user = response['data'] as UserModel;
|
User user = response['data'] as User;
|
||||||
Map<String, dynamic> data = {
|
Map<String, dynamic> data = {
|
||||||
'userId': user.userId,
|
'userId': user.userId,
|
||||||
'accessToken': user.accessToken,
|
'accessToken': user.accessToken,
|
||||||
|
|
@ -192,6 +190,37 @@ class LoginViewModel extends ReactiveViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sign-in with google
|
||||||
|
Future<void> _googleAuth() async {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
await _googleAuthService.googleAuth();
|
||||||
|
|
||||||
|
Map<String, dynamic> data = {
|
||||||
|
'id_token': _googleUser?.authentication.idToken ?? '',
|
||||||
|
};
|
||||||
|
|
||||||
|
Map<String, dynamic> response = await _apiService.googleAuth(data);
|
||||||
|
|
||||||
|
if (response['status'] == ResponseStatus.success) {
|
||||||
|
User user = response['data'] as User;
|
||||||
|
Map<String, dynamic> data = {
|
||||||
|
'userId': user.userId,
|
||||||
|
'accessToken': user.accessToken,
|
||||||
|
'refreshToken': user.refreshToken
|
||||||
|
};
|
||||||
|
await _authenticationService.saveUserCredential(data);
|
||||||
|
clearUserData();
|
||||||
|
await replaceWithHome();
|
||||||
|
showSuccessToast(response['message']);
|
||||||
|
} else {
|
||||||
|
showErrorToast(response['message']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> signInWithGoogle() async => await runBusyFuture(_googleAuth(),
|
||||||
|
busyObject: StateObjects.loginWithGoogle);
|
||||||
|
|
||||||
// Login with phone
|
// Login with phone
|
||||||
Future<void> loginWithPhoneNumber() async =>
|
Future<void> loginWithPhoneNumber() async =>
|
||||||
await runBusyFuture(_loginWithPhoneNumber(),
|
await runBusyFuture(_loginWithPhoneNumber(),
|
||||||
|
|
@ -209,46 +238,12 @@ class LoginViewModel extends ReactiveViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign-in with google
|
|
||||||
Future<void> signInWithGoogle() async => await runBusyFuture(_googleAuth(),
|
|
||||||
busyObject: StateObjects.loginWithGoogle);
|
|
||||||
|
|
||||||
Future<void> _googleAuth() async {
|
|
||||||
if (await _statusChecker.checkConnection()) {
|
|
||||||
await _googleAuthService.googleAuth();
|
|
||||||
|
|
||||||
Map<String, dynamic> data = {
|
|
||||||
'id_token': _googleUser?.authentication.idToken ?? '',
|
|
||||||
};
|
|
||||||
|
|
||||||
Map<String, dynamic> response = await _apiService.googleAuth(data);
|
|
||||||
|
|
||||||
if (response['status'] == ResponseStatus.success) {
|
|
||||||
UserModel user = response['data'] as UserModel;
|
|
||||||
Map<String, dynamic> data = {
|
|
||||||
'userId': user.userId,
|
|
||||||
'accessToken': user.accessToken,
|
|
||||||
'refreshToken': user.refreshToken
|
|
||||||
};
|
|
||||||
await _authenticationService.saveUserCredential(data);
|
|
||||||
clearUserData();
|
|
||||||
await replaceWithHome();
|
|
||||||
showSuccessToast(response['message']);
|
|
||||||
} else {
|
|
||||||
showErrorToast(response['message']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify otp
|
// Verify otp
|
||||||
Future<void> verifyOtp() async =>
|
|
||||||
await runBusyFuture(_verifyOtp(), busyObject: StateObjects.verifyOtp);
|
|
||||||
|
|
||||||
Future<void> _verifyOtp() async {
|
Future<void> _verifyOtp() async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
Map<String, dynamic> response = await _apiService.verifyOtp(_userData);
|
Map<String, dynamic> response = await _apiService.verifyOtp(_userData);
|
||||||
if (response['status'] == ResponseStatus.success) {
|
if (response['status'] == ResponseStatus.success) {
|
||||||
UserModel user = response['data'] as UserModel;
|
User user = response['data'] as User;
|
||||||
Map<String, dynamic> data = {
|
Map<String, dynamic> data = {
|
||||||
'userId': user.userId,
|
'userId': user.userId,
|
||||||
'accessToken': user.accessToken,
|
'accessToken': user.accessToken,
|
||||||
|
|
@ -264,10 +259,10 @@ class LoginViewModel extends ReactiveViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resend otp
|
Future<void> verifyOtp() async =>
|
||||||
Future<void> resendOtp() async =>
|
await runBusyFuture(_verifyOtp(), busyObject: StateObjects.verifyOtp);
|
||||||
await runBusyFuture(_resendOtp(), busyObject: StateObjects.resendOtp);
|
|
||||||
|
|
||||||
|
// Resend otp
|
||||||
Future<void> _resendOtp() async {
|
Future<void> _resendOtp() async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
resetButton();
|
resetButton();
|
||||||
|
|
@ -281,4 +276,7 @@ class LoginViewModel extends ReactiveViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> resendOtp() async =>
|
||||||
|
await runBusyFuture(_resendOtp(), busyObject: StateObjects.resendOtp);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import 'package:yimaru_app/services/image_picker_service.dart';
|
||||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||||
|
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
import '../../../models/user_model.dart';
|
import '../../../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';
|
||||||
|
|
@ -32,9 +32,9 @@ class ProfileViewModel extends ReactiveViewModel {
|
||||||
[_authenticationService];
|
[_authenticationService];
|
||||||
|
|
||||||
// Current user
|
// Current user
|
||||||
UserModel? get _user => _authenticationService.user;
|
User? get _user => _authenticationService.user;
|
||||||
|
|
||||||
UserModel? get user => _user;
|
User? get user => _user;
|
||||||
|
|
||||||
// Image picker
|
// Image picker
|
||||||
Future<void> openCamera() async =>
|
Future<void> openCamera() async =>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,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 '../../../models/user_model.dart';
|
import '../../../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/image_picker_service.dart';
|
import '../../../services/image_picker_service.dart';
|
||||||
|
|
@ -28,9 +28,9 @@ class ProfileDetailViewModel extends ReactiveViewModel
|
||||||
[_authenticationService];
|
[_authenticationService];
|
||||||
|
|
||||||
// Current user
|
// Current user
|
||||||
UserModel? get _user => _authenticationService.user;
|
User? get _user => _authenticationService.user;
|
||||||
|
|
||||||
UserModel? get user => _user;
|
User? get user => _user;
|
||||||
|
|
||||||
// First name
|
// First name
|
||||||
bool _focusFirstName = false;
|
bool _focusFirstName = false;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ 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';
|
||||||
|
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
import '../../../models/user_model.dart';
|
import '../../../models/user.dart';
|
||||||
import '../../../services/google_auth_service.dart';
|
import '../../../services/google_auth_service.dart';
|
||||||
import '../../../services/smart_auth_service.dart';
|
import '../../../services/smart_auth_service.dart';
|
||||||
import '../../../services/status_checker_service.dart';
|
import '../../../services/status_checker_service.dart';
|
||||||
|
|
@ -336,7 +336,7 @@ class RegisterViewModel extends ReactiveViewModel
|
||||||
Map<String, dynamic> response = await _apiService.googleAuth(data);
|
Map<String, dynamic> response = await _apiService.googleAuth(data);
|
||||||
|
|
||||||
if (response['status'] == ResponseStatus.success) {
|
if (response['status'] == ResponseStatus.success) {
|
||||||
UserModel user = response['data'] as UserModel;
|
User user = response['data'] as User;
|
||||||
Map<String, dynamic> data = {
|
Map<String, dynamic> data = {
|
||||||
'userId': user.userId,
|
'userId': user.userId,
|
||||||
'accessToken': user.accessToken,
|
'accessToken': user.accessToken,
|
||||||
|
|
@ -359,7 +359,7 @@ class RegisterViewModel extends ReactiveViewModel
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
Map<String, dynamic> response = await _apiService.verifyOtp(_userData);
|
Map<String, dynamic> response = await _apiService.verifyOtp(_userData);
|
||||||
if (response['status'] == ResponseStatus.success) {
|
if (response['status'] == ResponseStatus.success) {
|
||||||
UserModel user = response['data'] as UserModel;
|
User user = response['data'] as User;
|
||||||
Map<String, dynamic> data = {
|
Map<String, dynamic> data = {
|
||||||
'userId': user.userId,
|
'userId': user.userId,
|
||||||
'accessToken': user.accessToken,
|
'accessToken': user.accessToken,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:yimaru_app/models/course_category.dart';
|
import 'package:yimaru_app/models/category.dart';
|
||||||
import 'package:yimaru_app/ui/common/helper_functions.dart';
|
import 'package:yimaru_app/ui/common/helper_functions.dart';
|
||||||
|
|
||||||
import '../common/app_colors.dart';
|
import '../common/app_colors.dart';
|
||||||
|
|
@ -8,7 +8,7 @@ import '../common/ui_helpers.dart';
|
||||||
import 'custom_elevated_button.dart';
|
import 'custom_elevated_button.dart';
|
||||||
|
|
||||||
class CourseCategoryCard extends StatelessWidget {
|
class CourseCategoryCard extends StatelessWidget {
|
||||||
final CourseCategory category;
|
final Category category;
|
||||||
final GestureTapCallback? onTap;
|
final GestureTapCallback? onTap;
|
||||||
|
|
||||||
const CourseCategoryCard({super.key, this.onTap, required this.category});
|
const CourseCategoryCard({super.key, this.onTap, required this.category});
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
|
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
|
||||||
|
|
||||||
import '../../models/course_subcategory.dart';
|
import '../../models/subcategory.dart';
|
||||||
import '../common/app_colors.dart';
|
import '../common/app_colors.dart';
|
||||||
import '../common/ui_helpers.dart';
|
import '../common/ui_helpers.dart';
|
||||||
import 'custom_elevated_button.dart';
|
import 'custom_elevated_button.dart';
|
||||||
|
|
||||||
class CourseSubcategoryTile extends StatelessWidget {
|
class CourseSubcategoryTile extends StatelessWidget {
|
||||||
final CourseSubcategory subcategory;
|
final Subcategory subcategory;
|
||||||
final GestureTapCallback? onCourseTap;
|
final GestureTapCallback? onCourseTap;
|
||||||
final GestureTapCallback? onPracticeTap;
|
final GestureTapCallback? onPracticeTap;
|
||||||
|
|
||||||
|
|
@ -57,9 +57,9 @@ class CourseSubcategoryTile extends StatelessWidget {
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
(subcategory.title == null || subcategory.title!.isEmpty)
|
(subcategory.name == null || subcategory.name!.isEmpty)
|
||||||
? 'Course ${subcategory.id}'
|
? 'Course ${subcategory.id}'
|
||||||
: subcategory.title!,
|
: subcategory.name!,
|
||||||
style: style16P600,
|
style: style16P600,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,67 +1,69 @@
|
||||||
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/enmus.dart';
|
import 'package:yimaru_app/ui/views/learn_level/learn_level_viewmodel.dart';
|
||||||
import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
|
||||||
import 'package:yimaru_app/ui/views/learn/learn_viewmodel.dart';
|
|
||||||
import 'package:yimaru_app/ui/widgets/progress_status.dart';
|
import 'package:yimaru_app/ui/widgets/progress_status.dart';
|
||||||
|
|
||||||
|
import '../../models/level.dart';
|
||||||
import '../common/app_colors.dart';
|
import '../common/app_colors.dart';
|
||||||
|
import '../common/ui_helpers.dart';
|
||||||
import 'custom_elevated_button.dart';
|
import 'custom_elevated_button.dart';
|
||||||
|
|
||||||
class LearnLevelTile extends ViewModelWidget<LearnViewModel> {
|
class LearnLevelTile extends ViewModelWidget<LearnLevelViewModel> {
|
||||||
final String title;
|
final Level level;
|
||||||
final String subtitle;
|
final GestureTapCallback? onTap;
|
||||||
final ProgressStatuses status;
|
|
||||||
|
|
||||||
const LearnLevelTile({
|
const LearnLevelTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.title,
|
this.onTap,
|
||||||
required this.status,
|
required this.level,
|
||||||
required this.subtitle,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, LearnViewModel viewModel) =>
|
Widget build(BuildContext context, LearnLevelViewModel viewModel) =>
|
||||||
_buildExpansionTileCard(viewModel);
|
_buildExpansionTileCard(viewModel);
|
||||||
|
|
||||||
Widget _buildExpansionTileCard(LearnViewModel viewModel) => Container(
|
Widget _buildExpansionTileCard(LearnLevelViewModel viewModel) => Container(
|
||||||
margin: const EdgeInsets.only(bottom: 15),
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: status == ProgressStatuses.started
|
color: kcPrimaryColor.withOpacity(0.2),
|
||||||
? kcPrimaryColor.withOpacity(0.2)
|
// color:
|
||||||
: kcVeryLightGrey),
|
// current ? kcPrimaryColor.withOpacity(0.2) : kcVeryLightGrey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: _buildExpansionTile(viewModel),
|
child: _buildExpansionTile(viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildExpansionTile(LearnViewModel viewModel) => ExpansionTile(
|
Widget _buildExpansionTile(LearnLevelViewModel viewModel) => ExpansionTile(
|
||||||
|
enabled: true,
|
||||||
textColor: kcDarkGrey,
|
textColor: kcDarkGrey,
|
||||||
|
showTrailingIcon: true,
|
||||||
title: _buildTitleRow(),
|
title: _buildTitleRow(),
|
||||||
subtitle: _buildContent(),
|
initiallyExpanded: false,
|
||||||
collapsedIconColor: kcDarkGrey,
|
collapsedIconColor: kcDarkGrey,
|
||||||
collapsedTextColor: kcDarkGrey,
|
collapsedTextColor: kcDarkGrey,
|
||||||
shape: Border.all(color: kcTransparent),
|
shape: Border.all(color: kcTransparent),
|
||||||
expandedAlignment: Alignment.centerLeft,
|
expandedAlignment: Alignment.centerLeft,
|
||||||
childrenPadding: const EdgeInsets.all(15),
|
backgroundColor: kcPrimaryColor.withOpacity(0.1),
|
||||||
controlAffinity: ListTileControlAffinity.trailing,
|
controlAffinity: ListTileControlAffinity.trailing,
|
||||||
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||||
backgroundColor: status != ProgressStatuses.pending
|
|
||||||
? kcPrimaryColor.withOpacity(0.1)
|
|
||||||
: kcBackgroundColor,
|
|
||||||
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
|
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
enabled: status != ProgressStatuses.pending ? true : false,
|
collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1),
|
||||||
collapsedBackgroundColor: status != ProgressStatuses.pending
|
childrenPadding: const EdgeInsets.only(left: 15, right: 15, bottom: 15),
|
||||||
? kcPrimaryColor.withOpacity(0.1)
|
|
||||||
: kcBackgroundColor,
|
//enabled: current,
|
||||||
showTrailingIcon: status != ProgressStatuses.pending ? true : false,
|
// showTrailingIcon: current,
|
||||||
initiallyExpanded: status == ProgressStatuses.started ? true : false,
|
//initiallyExpanded: current,
|
||||||
|
// collapsedBackgroundColor:
|
||||||
|
// current ? kcPrimaryColor.withOpacity(0.1) : kcBackgroundColor,
|
||||||
|
// backgroundColor:
|
||||||
|
// current ? kcPrimaryColor.withOpacity(0.1) : kcBackgroundColor,
|
||||||
children: _buildExpansionTileChildren(viewModel),
|
children: _buildExpansionTileChildren(viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
List<Widget> _buildExpansionTileChildren(LearnViewModel viewModel) =>
|
List<Widget> _buildExpansionTileChildren(LearnLevelViewModel viewModel) =>
|
||||||
[_buildActionButton(viewModel)];
|
[_buildViewButton(viewModel)];
|
||||||
|
|
||||||
Widget _buildTitleRow() => Row(
|
Widget _buildTitleRow() => Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
@ -70,42 +72,27 @@ class LearnLevelTile extends ViewModelWidget<LearnViewModel> {
|
||||||
|
|
||||||
List<Widget> _buildTitleChildren() => [
|
List<Widget> _buildTitleChildren() => [
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
if (status != ProgressStatuses.pending) horizontalSpaceSmall,
|
// if (current) horizontalSpaceSmall,
|
||||||
if (status != ProgressStatuses.pending) _buildProgressStatus()
|
// if (current) _buildProgressStatus()
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
title,
|
level.title ?? '',
|
||||||
style: const TextStyle(
|
style: style16P600,
|
||||||
fontSize: 16,
|
);
|
||||||
|
|
||||||
|
Widget _buildProgressStatus() => const ProgressStatus(
|
||||||
color: kcPrimaryColor,
|
color: kcPrimaryColor,
|
||||||
fontWeight: FontWeight.w600,
|
status: 'Current Level',
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildProgressStatus() => ProgressStatus(
|
Widget _buildViewButton(LearnLevelViewModel viewModel) =>
|
||||||
status: status.name.substring(0, 1).toUpperCase() +
|
CustomElevatedButton(
|
||||||
status.name.substring(1, status.name.length),
|
|
||||||
color: kcPrimaryColor,
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildContent() => Text(
|
|
||||||
subtitle,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: kcDarkGrey,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildActionButton(LearnViewModel viewModel) => CustomElevatedButton(
|
|
||||||
height: 15,
|
height: 15,
|
||||||
|
onTap: onTap,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
|
text: 'View Level',
|
||||||
foregroundColor: kcWhite,
|
foregroundColor: kcWhite,
|
||||||
backgroundColor: kcPrimaryColor,
|
backgroundColor: kcPrimaryColor,
|
||||||
text: status == ProgressStatuses.completed
|
|
||||||
? 'Review Course'
|
|
||||||
: status == ProgressStatuses.pending
|
|
||||||
? 'Start Learning'
|
|
||||||
: 'Continue Learning',
|
|
||||||
onTap: () async => await viewModel.navigateToLearnLevel(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,27 +4,19 @@ import 'package:yimaru_app/ui/views/learn_module/learn_module_viewmodel.dart';
|
||||||
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
|
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
|
||||||
import 'package:yimaru_app/ui/widgets/finish_practice_sheet.dart';
|
import 'package:yimaru_app/ui/widgets/finish_practice_sheet.dart';
|
||||||
|
|
||||||
|
import '../../models/module.dart';
|
||||||
import '../common/app_colors.dart';
|
import '../common/app_colors.dart';
|
||||||
import '../common/enmus.dart';
|
import '../common/enmus.dart';
|
||||||
import '../common/ui_helpers.dart';
|
import '../common/ui_helpers.dart';
|
||||||
import 'custom_elevated_button.dart';
|
import 'custom_elevated_button.dart';
|
||||||
|
|
||||||
class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
|
class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
|
||||||
final String title;
|
final Module module;
|
||||||
final String topics;
|
final GestureTapCallback? onLessonTap;
|
||||||
final String subtitle;
|
final GestureTapCallback? onPracticeTap;
|
||||||
final String description;
|
|
||||||
final ProgressStatuses status;
|
|
||||||
final List<Map<String, dynamic>> practices;
|
|
||||||
|
|
||||||
const LearnModuleTile(
|
const LearnModuleTile(
|
||||||
{super.key,
|
{super.key, this.onLessonTap, this.onPracticeTap, required this.module});
|
||||||
required this.title,
|
|
||||||
required this.topics,
|
|
||||||
required this.status,
|
|
||||||
required this.subtitle,
|
|
||||||
required this.practices,
|
|
||||||
required this.description});
|
|
||||||
|
|
||||||
Future<void> _showSheet(
|
Future<void> _showSheet(
|
||||||
{required BuildContext context,
|
{required BuildContext context,
|
||||||
|
|
@ -57,7 +49,7 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
_buildExpansionTile(context: context, viewModel: viewModel),
|
_buildExpansionTile(context: context, viewModel: viewModel),
|
||||||
_buildContainerShaderState()
|
// _buildContainerShaderState()
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -65,8 +57,11 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
|
||||||
{required BuildContext context,
|
{required BuildContext context,
|
||||||
required LearnModuleViewModel viewModel}) =>
|
required LearnModuleViewModel viewModel}) =>
|
||||||
ExpansionTile(
|
ExpansionTile(
|
||||||
textColor: kcDarkGrey,
|
enabled: true,
|
||||||
title: _buildTitle(),
|
title: _buildTitle(),
|
||||||
|
textColor: kcDarkGrey,
|
||||||
|
showTrailingIcon: true,
|
||||||
|
initiallyExpanded: true,
|
||||||
subtitle: _buildContent(),
|
subtitle: _buildContent(),
|
||||||
leading: _buildIconWrapper(),
|
leading: _buildIconWrapper(),
|
||||||
collapsedIconColor: kcDarkGrey,
|
collapsedIconColor: kcDarkGrey,
|
||||||
|
|
@ -75,13 +70,13 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
|
||||||
shape: Border.all(color: kcTransparent),
|
shape: Border.all(color: kcTransparent),
|
||||||
expandedAlignment: Alignment.centerLeft,
|
expandedAlignment: Alignment.centerLeft,
|
||||||
collapsedBackgroundColor: kcBackgroundColor,
|
collapsedBackgroundColor: kcBackgroundColor,
|
||||||
enabled: status != ProgressStatuses.pending,
|
|
||||||
controlAffinity: ListTileControlAffinity.trailing,
|
controlAffinity: ListTileControlAffinity.trailing,
|
||||||
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||||
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
|
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
childrenPadding: const EdgeInsets.fromLTRB(70, 15, 15, 15),
|
childrenPadding: const EdgeInsets.fromLTRB(70, 15, 15, 15),
|
||||||
showTrailingIcon: status != ProgressStatuses.pending ? true : false,
|
// enabled: status != ProgressStatuses.pending,
|
||||||
initiallyExpanded: status == ProgressStatuses.started ? true : false,
|
// showTrailingIcon: status != ProgressStatuses.pending ? true : false,
|
||||||
|
//initiallyExpanded: status == ProgressStatuses.started ? true : false,
|
||||||
children:
|
children:
|
||||||
_buildExpansionTileChildren(context: context, viewModel: viewModel),
|
_buildExpansionTileChildren(context: context, viewModel: viewModel),
|
||||||
);
|
);
|
||||||
|
|
@ -97,13 +92,19 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
title,
|
module.title ?? '',
|
||||||
|
maxLines: 1,
|
||||||
|
softWrap: false,
|
||||||
style: style16P600,
|
style: style16P600,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildContent() => Text(
|
Widget _buildContent() => Text(
|
||||||
subtitle,
|
module.description ?? '',
|
||||||
|
maxLines: 1,
|
||||||
|
softWrap: false,
|
||||||
style: style14DG400,
|
style: style14DG400,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
);
|
);
|
||||||
|
|
||||||
List<Widget> _buildExpansionTileChildren(
|
List<Widget> _buildExpansionTileChildren(
|
||||||
|
|
@ -179,15 +180,16 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
|
||||||
CustomElevatedButton(
|
CustomElevatedButton(
|
||||||
height: 15,
|
height: 15,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
text: 'View Lessons',
|
onTap: onLessonTap,
|
||||||
|
text: 'View Module',
|
||||||
foregroundColor: kcWhite,
|
foregroundColor: kcWhite,
|
||||||
backgroundColor: kcPrimaryColor,
|
backgroundColor: kcPrimaryColor,
|
||||||
onTap: () async => await viewModel.navigateToLearnLesson(
|
// onTap: () async => await viewModel.navigateToLearnLesson(
|
||||||
title: title,
|
// title: title,
|
||||||
topics: topics,
|
// topics: topics,
|
||||||
subtitle: subtitle,
|
// subtitle: subtitle,
|
||||||
practices: practices,
|
// practices: practices,
|
||||||
description: description),
|
// description: description),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildPracticeButtonWrapper(
|
Widget _buildPracticeButtonWrapper(
|
||||||
|
|
@ -203,20 +205,21 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
|
||||||
CustomElevatedButton(
|
CustomElevatedButton(
|
||||||
height: 15,
|
height: 15,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
|
onTap: onPracticeTap,
|
||||||
text: 'View Practices',
|
text: 'View Practices',
|
||||||
backgroundColor: kcWhite,
|
backgroundColor: kcWhite,
|
||||||
borderColor: kcPrimaryColor,
|
borderColor: kcPrimaryColor,
|
||||||
foregroundColor: kcPrimaryColor,
|
foregroundColor: kcPrimaryColor,
|
||||||
onTap: () async => await viewModel.navigateToLearnPractice(practices),
|
// onTap: () async => await viewModel.navigateToLearnPractice(practices),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSheet(LearnModuleViewModel viewModel) => FinishPracticeSheet(
|
Widget _buildSheet(LearnModuleViewModel viewModel) => FinishPracticeSheet(
|
||||||
onTap: viewModel.pop,
|
onTap: viewModel.pop,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildContainerShaderState() => status == ProgressStatuses.pending
|
// Widget _buildContainerShaderState() => status == ProgressStatuses.pending
|
||||||
? _buildContainerShaderWrapper()
|
// ? _buildContainerShaderWrapper()
|
||||||
: Container();
|
// : Container();
|
||||||
|
|
||||||
Widget _buildContainerShaderWrapper() => Positioned.fill(
|
Widget _buildContainerShaderWrapper() => Positioned.fill(
|
||||||
child: _buildContainerShader(),
|
child: _buildContainerShader(),
|
||||||
|
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
|
||||||
import 'package:yimaru_app/ui/views/learn_level/learn_level_viewmodel.dart';
|
|
||||||
import 'package:yimaru_app/ui/widgets/progress_status.dart';
|
|
||||||
|
|
||||||
import '../common/app_colors.dart';
|
|
||||||
import '../common/ui_helpers.dart';
|
|
||||||
import 'custom_elevated_button.dart';
|
|
||||||
|
|
||||||
class LearnSubLevelTile extends ViewModelWidget<LearnLevelViewModel> {
|
|
||||||
final bool current;
|
|
||||||
final String title;
|
|
||||||
final String subtitle;
|
|
||||||
|
|
||||||
const LearnSubLevelTile({
|
|
||||||
super.key,
|
|
||||||
required this.title,
|
|
||||||
required this.current,
|
|
||||||
required this.subtitle,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, LearnLevelViewModel viewModel) =>
|
|
||||||
_buildExpansionTileCard(viewModel);
|
|
||||||
|
|
||||||
Widget _buildExpansionTileCard(LearnLevelViewModel viewModel) => Container(
|
|
||||||
margin: const EdgeInsets.only(bottom: 15),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(5),
|
|
||||||
border: Border.all(
|
|
||||||
color:
|
|
||||||
current ? kcPrimaryColor.withOpacity(0.2) : kcVeryLightGrey),
|
|
||||||
),
|
|
||||||
child: _buildExpansionTile(viewModel),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildExpansionTile(LearnLevelViewModel viewModel) => ExpansionTile(
|
|
||||||
enabled: current,
|
|
||||||
textColor: kcDarkGrey,
|
|
||||||
title: _buildTitleRow(),
|
|
||||||
showTrailingIcon: current,
|
|
||||||
subtitle: _buildContent(),
|
|
||||||
initiallyExpanded: current,
|
|
||||||
collapsedIconColor: kcDarkGrey,
|
|
||||||
collapsedTextColor: kcDarkGrey,
|
|
||||||
shape: Border.all(color: kcTransparent),
|
|
||||||
expandedAlignment: Alignment.centerLeft,
|
|
||||||
childrenPadding: const EdgeInsets.all(15),
|
|
||||||
controlAffinity: ListTileControlAffinity.trailing,
|
|
||||||
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
backgroundColor:
|
|
||||||
current ? kcPrimaryColor.withOpacity(0.1) : kcBackgroundColor,
|
|
||||||
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
|
|
||||||
collapsedBackgroundColor:
|
|
||||||
current ? kcPrimaryColor.withOpacity(0.1) : kcBackgroundColor,
|
|
||||||
children: _buildExpansionTileChildren(viewModel),
|
|
||||||
);
|
|
||||||
|
|
||||||
List<Widget> _buildExpansionTileChildren(LearnLevelViewModel viewModel) =>
|
|
||||||
[_buildActionButtonWrapper(viewModel)];
|
|
||||||
|
|
||||||
Widget _buildTitleRow() => Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: _buildTitleChildren(),
|
|
||||||
);
|
|
||||||
|
|
||||||
List<Widget> _buildTitleChildren() => [
|
|
||||||
_buildTitle(),
|
|
||||||
if (current) horizontalSpaceSmall,
|
|
||||||
if (current) _buildProgressStatus()
|
|
||||||
];
|
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
|
||||||
title,
|
|
||||||
style: style16P600,
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildProgressStatus() => const ProgressStatus(
|
|
||||||
color: kcPrimaryColor,
|
|
||||||
status: 'Current Level',
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildContent() => Text(
|
|
||||||
subtitle,
|
|
||||||
style: style14DG400,
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildActionButtonWrapper(LearnLevelViewModel viewModel) => SizedBox(
|
|
||||||
height: 40,
|
|
||||||
child: _buildActionButtons(viewModel),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildActionButtons(LearnLevelViewModel viewModel) => Row(
|
|
||||||
children: [
|
|
||||||
_buildViewButtonWrapper(viewModel),
|
|
||||||
horizontalSpaceSmall,
|
|
||||||
_buildPracticeButtonWrapper(viewModel)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildViewButtonWrapper(LearnLevelViewModel viewModel) => Expanded(
|
|
||||||
child: _buildViewButton(viewModel),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildViewButton(LearnLevelViewModel viewModel) =>
|
|
||||||
CustomElevatedButton(
|
|
||||||
height: 15,
|
|
||||||
borderRadius: 12,
|
|
||||||
text: 'View Course',
|
|
||||||
foregroundColor: kcWhite,
|
|
||||||
backgroundColor: kcPrimaryColor,
|
|
||||||
onTap: () async => await viewModel.navigateToLearnModule(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildPracticeButtonWrapper(LearnLevelViewModel viewModel) => Expanded(
|
|
||||||
child: Container(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget _buildPracticeButton(LearnLevelViewModel viewModel) =>
|
|
||||||
const CustomElevatedButton(
|
|
||||||
height: 15,
|
|
||||||
text: 'Practice',
|
|
||||||
borderRadius: 12,
|
|
||||||
backgroundColor: kcWhite,
|
|
||||||
borderColor: kcPrimaryColor,
|
|
||||||
foregroundColor: kcPrimaryColor
|
|
||||||
// onTap: () async => await viewModel.navigateToLearnPractice()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
70
lib/ui/widgets/learn_subcategory_card.dart
Normal file
70
lib/ui/widgets/learn_subcategory_card.dart
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:yimaru_app/models/subcategory.dart';
|
||||||
|
|
||||||
|
import '../common/app_colors.dart';
|
||||||
|
import '../common/app_strings.dart';
|
||||||
|
import '../common/helper_functions.dart';
|
||||||
|
import '../common/ui_helpers.dart';
|
||||||
|
import 'custom_elevated_button.dart';
|
||||||
|
|
||||||
|
class LearnSubcategoryCard extends StatelessWidget {
|
||||||
|
final Subcategory subcategory;
|
||||||
|
final GestureTapCallback? onTap;
|
||||||
|
|
||||||
|
const LearnSubcategoryCard(
|
||||||
|
{super.key, this.onTap, required this.subcategory});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => _buildContainer();
|
||||||
|
|
||||||
|
Widget _buildContainer() => Container(
|
||||||
|
height: 200,
|
||||||
|
padding: const EdgeInsets.all(15),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: getColor(),
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
child: _buildColumn(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildColumn() => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: _buildColumnChildren(),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildColumnChildren() => [
|
||||||
|
_buildTitle(),
|
||||||
|
verticalSpaceTiny,
|
||||||
|
_buildSubtitle(),
|
||||||
|
verticalSpaceMedium,
|
||||||
|
__buildStartButtonWrapper(),
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildTitle() => Text(
|
||||||
|
subcategory.name ?? '',
|
||||||
|
style: style18DG700,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSubtitle() => Text(
|
||||||
|
subcategory.description ?? ksCategorySubtitle,
|
||||||
|
maxLines: 3,
|
||||||
|
style: style16DG400,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget __buildStartButtonWrapper() => SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: _buildStartButton(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildStartButton() => CustomElevatedButton(
|
||||||
|
height: 50,
|
||||||
|
width: 200,
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: 12,
|
||||||
|
text: 'Select Course',
|
||||||
|
foregroundColor: kcWhite,
|
||||||
|
backgroundColor: kcPrimaryColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
123
lib/ui/widgets/learn_tile.dart
Normal file
123
lib/ui/widgets/learn_tile.dart
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:yimaru_app/models/course.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/learn/learn_viewmodel.dart';
|
||||||
|
|
||||||
|
import '../common/app_colors.dart';
|
||||||
|
import 'custom_elevated_button.dart';
|
||||||
|
|
||||||
|
class LearnTile extends ViewModelWidget<LearnViewModel> {
|
||||||
|
final Course course;
|
||||||
|
final GestureTapCallback? onTap;
|
||||||
|
|
||||||
|
const LearnTile({
|
||||||
|
super.key,
|
||||||
|
this.onTap,
|
||||||
|
required this.course,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, LearnViewModel viewModel) =>
|
||||||
|
_buildExpansionTileCard(viewModel);
|
||||||
|
|
||||||
|
Widget _buildExpansionTileCard(LearnViewModel viewModel) => Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
border: Border.all(color: kcPrimaryColor.withOpacity(0.2)
|
||||||
|
// color: status == ProgressStatuses.started
|
||||||
|
// ? kcPrimaryColor.withOpacity(0.2)
|
||||||
|
// : kcVeryLightGrey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _buildExpansionTile(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildExpansionTile(LearnViewModel viewModel) => ExpansionTile(
|
||||||
|
enabled: true,
|
||||||
|
textColor: kcDarkGrey,
|
||||||
|
showTrailingIcon: true,
|
||||||
|
title: _buildTitleRow(),
|
||||||
|
initiallyExpanded: false,
|
||||||
|
subtitle: _buildContent(),
|
||||||
|
collapsedIconColor: kcDarkGrey,
|
||||||
|
collapsedTextColor: kcDarkGrey,
|
||||||
|
shape: Border.all(color: kcTransparent),
|
||||||
|
expandedAlignment: Alignment.centerLeft,
|
||||||
|
backgroundColor: kcPrimaryColor.withOpacity(0.1),
|
||||||
|
controlAffinity: ListTileControlAffinity.trailing,
|
||||||
|
childrenPadding: const EdgeInsets.only(bottom: 15),
|
||||||
|
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1),
|
||||||
|
// enabled: status != ProgressStatuses.pending ? true : false,
|
||||||
|
// status != ProgressStatuses.pending
|
||||||
|
// ? kcPrimaryColor.withOpacity(0.1)
|
||||||
|
// : kcBackgroundColor,
|
||||||
|
// collapsedBackgroundColor: status != ProgressStatuses.pending
|
||||||
|
// ? kcPrimaryColor.withOpacity(0.1)
|
||||||
|
// : kcBackgroundColor,
|
||||||
|
// showTrailingIcon: status != ProgressStatuses.pending ? true : false,
|
||||||
|
// initiallyExpanded: status == ProgressStatuses.started ? true : false,
|
||||||
|
children: _buildExpansionTileChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildExpansionTileChildren(LearnViewModel viewModel) => [
|
||||||
|
_buildDivider(),
|
||||||
|
verticalSpaceTiny,
|
||||||
|
_buildActionButtonWrapper(viewModel)
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildTitleRow() => Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: _buildTitleChildren(),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildTitleChildren() => [
|
||||||
|
_buildTitle(),
|
||||||
|
// if (status != ProgressStatuses.pending) horizontalSpaceSmall,
|
||||||
|
// if (status != ProgressStatuses.pending) _buildProgressStatus()
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildTitle() => Text(
|
||||||
|
course.title ?? '',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: kcPrimaryColor,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Widget _buildProgressStatus() => ProgressStatus(
|
||||||
|
// status: status.name.substring(0, 1).toUpperCase() +
|
||||||
|
// status.name.substring(1, status.name.length),
|
||||||
|
// color: kcPrimaryColor,
|
||||||
|
// );
|
||||||
|
|
||||||
|
Widget _buildContent() => Text(
|
||||||
|
course.description ?? '',
|
||||||
|
style: style14DG400,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildDivider() => const Divider(color: kcVeryLightGrey);
|
||||||
|
|
||||||
|
Widget _buildActionButtonWrapper(LearnViewModel viewModel) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
child: _buildActionButton(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildActionButton(LearnViewModel viewModel) => CustomElevatedButton(
|
||||||
|
height: 15,
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: 12,
|
||||||
|
text: 'Start Course',
|
||||||
|
foregroundColor: kcWhite,
|
||||||
|
backgroundColor: kcPrimaryColor,
|
||||||
|
// text: status == ProgressStatuses.completed
|
||||||
|
// ? 'Review Course'
|
||||||
|
// : status == ProgressStatuses.pending
|
||||||
|
// ? 'Start Learning'
|
||||||
|
// : 'Continue Learning',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -84,7 +84,8 @@ class SelectableCoursePracticeQuestion
|
||||||
? _buildProgressIndicator()
|
? _buildProgressIndicator()
|
||||||
: _buildContinueButton(viewModel);
|
: _buildContinueButton(viewModel);
|
||||||
|
|
||||||
Widget _buildProgressIndicator()=>const CustomCircularProgressIndicator(color: kcPrimaryColor);
|
Widget _buildProgressIndicator() =>
|
||||||
|
const CustomCircularProgressIndicator(color: kcPrimaryColor);
|
||||||
|
|
||||||
Widget _buildContinueButton(CoursePracticeQuestionViewModel viewModel) =>
|
Widget _buildContinueButton(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
CustomElevatedButton(
|
CustomElevatedButton(
|
||||||
|
|
@ -108,6 +109,5 @@ class SelectableCoursePracticeQuestion
|
||||||
.questionId ??
|
.questionId ??
|
||||||
0)
|
0)
|
||||||
: null,
|
: null,
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,8 @@ class WritingCoursePracticeQuestion
|
||||||
? _buildProgressIndicator()
|
? _buildProgressIndicator()
|
||||||
: _buildContinueButton(viewModel);
|
: _buildContinueButton(viewModel);
|
||||||
|
|
||||||
Widget _buildProgressIndicator()=>const CustomCircularProgressIndicator(color: kcPrimaryColor);
|
Widget _buildProgressIndicator() =>
|
||||||
|
const CustomCircularProgressIndicator(color: kcPrimaryColor);
|
||||||
|
|
||||||
Widget _buildContinueButton(CoursePracticeQuestionViewModel viewModel) =>
|
Widget _buildContinueButton(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
CustomElevatedButton(
|
CustomElevatedButton(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
name: yimaru_app
|
name: yimaru_app
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.1.2+3
|
version: 0.1.2+4
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.3 <4.0.0'
|
sdk: '>=3.0.3 <4.0.0'
|
||||||
|
|
|
||||||
|
|
@ -8,38 +8,40 @@ import 'dart:ui' as _i10;
|
||||||
|
|
||||||
import 'package:audioplayers/audioplayers.dart' as _i4;
|
import 'package:audioplayers/audioplayers.dart' as _i4;
|
||||||
import 'package:dio/dio.dart' as _i2;
|
import 'package:dio/dio.dart' as _i2;
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart' as _i30;
|
import 'package:firebase_messaging/firebase_messaging.dart' as _i32;
|
||||||
import 'package:flutter/material.dart' as _i8;
|
import 'package:flutter/material.dart' as _i8;
|
||||||
import 'package:mockito/mockito.dart' as _i1;
|
import 'package:mockito/mockito.dart' as _i1;
|
||||||
import 'package:mockito/src/dummies.dart' as _i7;
|
import 'package:mockito/src/dummies.dart' as _i7;
|
||||||
import 'package:permission_handler/permission_handler.dart' as _i25;
|
import 'package:permission_handler/permission_handler.dart' as _i27;
|
||||||
import 'package:stacked_services/stacked_services.dart' as _i6;
|
import 'package:stacked_services/stacked_services.dart' as _i6;
|
||||||
import 'package:waveform_recorder/waveform_recorder.dart' as _i5;
|
import 'package:waveform_recorder/waveform_recorder.dart' as _i5;
|
||||||
import 'package:yimaru_app/models/course.dart' as _i17;
|
import 'package:yimaru_app/models/category.dart' as _i15;
|
||||||
import 'package:yimaru_app/models/course_category.dart' as _i15;
|
import 'package:yimaru_app/models/course.dart' as _i21;
|
||||||
import 'package:yimaru_app/models/course_detail.dart' as _i33;
|
import 'package:yimaru_app/models/course_detail.dart' as _i35;
|
||||||
import 'package:yimaru_app/models/course_lesson.dart' as _i19;
|
import 'package:yimaru_app/models/course_lesson.dart' as _i18;
|
||||||
import 'package:yimaru_app/models/course_progress.dart' as _i18;
|
import 'package:yimaru_app/models/course_progress.dart' as _i17;
|
||||||
import 'package:yimaru_app/models/course_subcategory.dart' as _i16;
|
import 'package:yimaru_app/models/level.dart' as _i22;
|
||||||
import 'package:yimaru_app/models/practice.dart' as _i20;
|
import 'package:yimaru_app/models/module.dart' as _i23;
|
||||||
import 'package:yimaru_app/models/practice_question.dart' as _i21;
|
import 'package:yimaru_app/models/practice.dart' as _i19;
|
||||||
|
import 'package:yimaru_app/models/practice_question.dart' as _i20;
|
||||||
import 'package:yimaru_app/models/question.dart' as _i14;
|
import 'package:yimaru_app/models/question.dart' as _i14;
|
||||||
import 'package:yimaru_app/models/user_model.dart' as _i12;
|
import 'package:yimaru_app/models/subcategory.dart' as _i16;
|
||||||
|
import 'package:yimaru_app/models/user.dart' as _i12;
|
||||||
import 'package:yimaru_app/services/api_service.dart' as _i13;
|
import 'package:yimaru_app/services/api_service.dart' as _i13;
|
||||||
import 'package:yimaru_app/services/audio_player_service.dart' as _i34;
|
import 'package:yimaru_app/services/audio_player_service.dart' as _i36;
|
||||||
import 'package:yimaru_app/services/authentication_service.dart' as _i11;
|
import 'package:yimaru_app/services/authentication_service.dart' as _i11;
|
||||||
import 'package:yimaru_app/services/course_service.dart' as _i32;
|
import 'package:yimaru_app/services/course_service.dart' as _i34;
|
||||||
import 'package:yimaru_app/services/dio_service.dart' as _i22;
|
import 'package:yimaru_app/services/dio_service.dart' as _i24;
|
||||||
import 'package:yimaru_app/services/google_auth_service.dart' as _i27;
|
import 'package:yimaru_app/services/google_auth_service.dart' as _i29;
|
||||||
import 'package:yimaru_app/services/image_downloader_service.dart' as _i28;
|
import 'package:yimaru_app/services/image_downloader_service.dart' as _i30;
|
||||||
import 'package:yimaru_app/services/image_picker_service.dart' as _i26;
|
import 'package:yimaru_app/services/image_picker_service.dart' as _i28;
|
||||||
import 'package:yimaru_app/services/notification_service.dart' as _i29;
|
import 'package:yimaru_app/services/notification_service.dart' as _i31;
|
||||||
import 'package:yimaru_app/services/permission_handler_service.dart' as _i24;
|
import 'package:yimaru_app/services/permission_handler_service.dart' as _i26;
|
||||||
import 'package:yimaru_app/services/secure_storage_service.dart' as _i3;
|
import 'package:yimaru_app/services/secure_storage_service.dart' as _i3;
|
||||||
import 'package:yimaru_app/services/smart_auth_service.dart' as _i31;
|
import 'package:yimaru_app/services/smart_auth_service.dart' as _i33;
|
||||||
import 'package:yimaru_app/services/status_checker_service.dart' as _i23;
|
import 'package:yimaru_app/services/status_checker_service.dart' as _i25;
|
||||||
import 'package:yimaru_app/services/voice_recorder_service.dart' as _i35;
|
import 'package:yimaru_app/services/voice_recorder_service.dart' as _i37;
|
||||||
import 'package:yimaru_app/ui/common/enmus.dart' as _i36;
|
import 'package:yimaru_app/ui/common/enmus.dart' as _i38;
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: avoid_redundant_argument_values
|
// ignore_for_file: avoid_redundant_argument_values
|
||||||
|
|
@ -857,7 +859,7 @@ class MockAuthenticationService extends _i1.Mock
|
||||||
) as _i9.Future<void>);
|
) as _i9.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<void> saveUserData(_i12.UserModel? data) => (super.noSuchMethod(
|
_i9.Future<void> saveUserData(_i12.User? data) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#saveUserData,
|
#saveUserData,
|
||||||
[data],
|
[data],
|
||||||
|
|
@ -898,14 +900,14 @@ class MockAuthenticationService extends _i1.Mock
|
||||||
) as _i9.Future<void>);
|
) as _i9.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<_i12.UserModel?> getUser() => (super.noSuchMethod(
|
_i9.Future<_i12.User?> getUser() => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getUser,
|
#getUser,
|
||||||
[],
|
[],
|
||||||
),
|
),
|
||||||
returnValue: _i9.Future<_i12.UserModel?>.value(),
|
returnValue: _i9.Future<_i12.User?>.value(),
|
||||||
returnValueForMissingStub: _i9.Future<_i12.UserModel?>.value(),
|
returnValueForMissingStub: _i9.Future<_i12.User?>.value(),
|
||||||
) as _i9.Future<_i12.UserModel?>);
|
) as _i9.Future<_i12.User?>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<void> logout() => (super.noSuchMethod(
|
_i9.Future<void> logout() => (super.noSuchMethod(
|
||||||
|
|
@ -1053,7 +1055,7 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
|
||||||
) as _i9.Future<Map<String, dynamic>>);
|
) as _i9.Future<Map<String, dynamic>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<Map<String, dynamic>> getProfileStatus(_i12.UserModel? user) =>
|
_i9.Future<Map<String, dynamic>> getProfileStatus(_i12.User? user) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getProfileStatus,
|
#getProfileStatus,
|
||||||
|
|
@ -1124,68 +1126,54 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
|
||||||
) as _i9.Future<List<_i14.Question>>);
|
) as _i9.Future<List<_i14.Question>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<List<_i15.CourseCategory>> getCourseCategories() =>
|
_i9.Future<List<_i15.Category>> getCategories() => (super.noSuchMethod(
|
||||||
(super.noSuchMethod(
|
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getCourseCategories,
|
#getCourseCategories,
|
||||||
[],
|
[],
|
||||||
),
|
),
|
||||||
returnValue: _i9.Future<List<_i15.CourseCategory>>.value(
|
returnValue: _i9.Future<List<_i15.Category>>.value(<_i15.Category>[]),
|
||||||
<_i15.CourseCategory>[]),
|
returnValueForMissingStub:
|
||||||
returnValueForMissingStub: _i9.Future<List<_i15.CourseCategory>>.value(
|
_i9.Future<List<_i15.Category>>.value(<_i15.Category>[]),
|
||||||
<_i15.CourseCategory>[]),
|
) as _i9.Future<List<_i15.Category>>);
|
||||||
) as _i9.Future<List<_i15.CourseCategory>>);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<List<_i16.CourseSubcategory>> getCourseSubcategories(int? id) =>
|
_i9.Future<List<_i16.Subcategory>> getSubcategories(int? id) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getCourseSubcategories,
|
#getCourseSubcategories,
|
||||||
[id],
|
[id],
|
||||||
),
|
),
|
||||||
returnValue: _i9.Future<List<_i16.CourseSubcategory>>.value(
|
returnValue:
|
||||||
<_i16.CourseSubcategory>[]),
|
_i9.Future<List<_i16.Subcategory>>.value(<_i16.Subcategory>[]),
|
||||||
returnValueForMissingStub:
|
returnValueForMissingStub:
|
||||||
_i9.Future<List<_i16.CourseSubcategory>>.value(
|
_i9.Future<List<_i16.Subcategory>>.value(<_i16.Subcategory>[]),
|
||||||
<_i16.CourseSubcategory>[]),
|
) as _i9.Future<List<_i16.Subcategory>>);
|
||||||
) as _i9.Future<List<_i16.CourseSubcategory>>);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<List<_i17.Course>> getCourses(int? id) => (super.noSuchMethod(
|
_i9.Future<List<_i17.CourseProgress>> getCourseProgress(int? id) =>
|
||||||
Invocation.method(
|
|
||||||
#getCourses,
|
|
||||||
[id],
|
|
||||||
),
|
|
||||||
returnValue: _i9.Future<List<_i17.Course>>.value(<_i17.Course>[]),
|
|
||||||
returnValueForMissingStub:
|
|
||||||
_i9.Future<List<_i17.Course>>.value(<_i17.Course>[]),
|
|
||||||
) as _i9.Future<List<_i17.Course>>);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_i9.Future<List<_i18.CourseProgress>> getCourseProgress(int? id) =>
|
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getCourseProgress,
|
#getCourseProgress,
|
||||||
[id],
|
[id],
|
||||||
),
|
),
|
||||||
returnValue: _i9.Future<List<_i18.CourseProgress>>.value(
|
returnValue: _i9.Future<List<_i17.CourseProgress>>.value(
|
||||||
<_i18.CourseProgress>[]),
|
<_i17.CourseProgress>[]),
|
||||||
returnValueForMissingStub: _i9.Future<List<_i18.CourseProgress>>.value(
|
returnValueForMissingStub: _i9.Future<List<_i17.CourseProgress>>.value(
|
||||||
<_i18.CourseProgress>[]),
|
<_i17.CourseProgress>[]),
|
||||||
) as _i9.Future<List<_i18.CourseProgress>>);
|
) as _i9.Future<List<_i17.CourseProgress>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<List<_i19.CourseLesson>> getCourseLessons(int? id) =>
|
_i9.Future<List<_i18.CourseLesson>> getCourseLessons(int? id) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getCourseLessons,
|
#getCourseLessons,
|
||||||
[id],
|
[id],
|
||||||
),
|
),
|
||||||
returnValue:
|
returnValue:
|
||||||
_i9.Future<List<_i19.CourseLesson>>.value(<_i19.CourseLesson>[]),
|
_i9.Future<List<_i18.CourseLesson>>.value(<_i18.CourseLesson>[]),
|
||||||
returnValueForMissingStub:
|
returnValueForMissingStub:
|
||||||
_i9.Future<List<_i19.CourseLesson>>.value(<_i19.CourseLesson>[]),
|
_i9.Future<List<_i18.CourseLesson>>.value(<_i18.CourseLesson>[]),
|
||||||
) as _i9.Future<List<_i19.CourseLesson>>);
|
) as _i9.Future<List<_i18.CourseLesson>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<Map<String, dynamic>> completeLesson(int? id) =>
|
_i9.Future<Map<String, dynamic>> completeLesson(int? id) =>
|
||||||
|
|
@ -1201,30 +1189,87 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
|
||||||
) as _i9.Future<Map<String, dynamic>>);
|
) as _i9.Future<Map<String, dynamic>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<List<_i20.Practice>> getCoursePractices(int? id) =>
|
_i9.Future<List<_i19.Practice>> getCoursePractices(int? id) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getCoursePractices,
|
#getCoursePractices,
|
||||||
[id],
|
[id],
|
||||||
),
|
),
|
||||||
returnValue: _i9.Future<List<_i20.Practice>>.value(<_i20.Practice>[]),
|
returnValue: _i9.Future<List<_i19.Practice>>.value(<_i19.Practice>[]),
|
||||||
returnValueForMissingStub:
|
returnValueForMissingStub:
|
||||||
_i9.Future<List<_i20.Practice>>.value(<_i20.Practice>[]),
|
_i9.Future<List<_i19.Practice>>.value(<_i19.Practice>[]),
|
||||||
) as _i9.Future<List<_i20.Practice>>);
|
) as _i9.Future<List<_i19.Practice>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<List<_i21.PracticeQuestion>> getCoursePracticeQuestions(int? id) =>
|
_i9.Future<List<_i20.PracticeQuestion>> getCoursePracticeQuestions(int? id) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getCoursePracticeQuestions,
|
#getCoursePracticeQuestions,
|
||||||
[id],
|
[id],
|
||||||
),
|
),
|
||||||
returnValue: _i9.Future<List<_i21.PracticeQuestion>>.value(
|
returnValue: _i9.Future<List<_i20.PracticeQuestion>>.value(
|
||||||
<_i21.PracticeQuestion>[]),
|
<_i20.PracticeQuestion>[]),
|
||||||
returnValueForMissingStub:
|
returnValueForMissingStub:
|
||||||
_i9.Future<List<_i21.PracticeQuestion>>.value(
|
_i9.Future<List<_i20.PracticeQuestion>>.value(
|
||||||
<_i21.PracticeQuestion>[]),
|
<_i20.PracticeQuestion>[]),
|
||||||
) as _i9.Future<List<_i21.PracticeQuestion>>);
|
) as _i9.Future<List<_i20.PracticeQuestion>>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_i9.Future<_i14.Question?> getCoursePracticeQuestion(int? id) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#getCoursePracticeQuestion,
|
||||||
|
[id],
|
||||||
|
),
|
||||||
|
returnValue: _i9.Future<_i14.Question?>.value(),
|
||||||
|
returnValueForMissingStub: _i9.Future<_i14.Question?>.value(),
|
||||||
|
) as _i9.Future<_i14.Question?>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_i9.Future<List<_i16.Subcategory>> getLearnSubcategories() =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#getLearnSubcategories,
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
returnValue:
|
||||||
|
_i9.Future<List<_i16.Subcategory>>.value(<_i16.Subcategory>[]),
|
||||||
|
returnValueForMissingStub:
|
||||||
|
_i9.Future<List<_i16.Subcategory>>.value(<_i16.Subcategory>[]),
|
||||||
|
) as _i9.Future<List<_i16.Subcategory>>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_i9.Future<List<_i21.Course>> getCourses(int? id) => (super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#getCourses,
|
||||||
|
[id],
|
||||||
|
),
|
||||||
|
returnValue: _i9.Future<List<_i21.Course>>.value(<_i21.Course>[]),
|
||||||
|
returnValueForMissingStub:
|
||||||
|
_i9.Future<List<_i21.Course>>.value(<_i21.Course>[]),
|
||||||
|
) as _i9.Future<List<_i21.Course>>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_i9.Future<List<_i22.Level>> getLevels(int? id) => (super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#getLevels,
|
||||||
|
[id],
|
||||||
|
),
|
||||||
|
returnValue: _i9.Future<List<_i22.Level>>.value(<_i22.Level>[]),
|
||||||
|
returnValueForMissingStub:
|
||||||
|
_i9.Future<List<_i22.Level>>.value(<_i22.Level>[]),
|
||||||
|
) as _i9.Future<List<_i22.Level>>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_i9.Future<List<_i23.Module>> getModules(int? id) => (super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#getModules,
|
||||||
|
[id],
|
||||||
|
),
|
||||||
|
returnValue: _i9.Future<List<_i23.Module>>.value(<_i23.Module>[]),
|
||||||
|
returnValueForMissingStub:
|
||||||
|
_i9.Future<List<_i23.Module>>.value(<_i23.Module>[]),
|
||||||
|
) as _i9.Future<List<_i23.Module>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A class which mocks [SecureStorageService].
|
/// A class which mocks [SecureStorageService].
|
||||||
|
|
@ -1327,7 +1372,7 @@ class MockSecureStorageService extends _i1.Mock
|
||||||
/// A class which mocks [DioService].
|
/// A class which mocks [DioService].
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockDioService extends _i1.Mock implements _i22.DioService {
|
class MockDioService extends _i1.Mock implements _i24.DioService {
|
||||||
@override
|
@override
|
||||||
_i2.Dio get dio => (super.noSuchMethod(
|
_i2.Dio get dio => (super.noSuchMethod(
|
||||||
Invocation.getter(#dio),
|
Invocation.getter(#dio),
|
||||||
|
|
@ -1346,7 +1391,7 @@ class MockDioService extends _i1.Mock implements _i22.DioService {
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockStatusCheckerService extends _i1.Mock
|
class MockStatusCheckerService extends _i1.Mock
|
||||||
implements _i23.StatusCheckerService {
|
implements _i25.StatusCheckerService {
|
||||||
@override
|
@override
|
||||||
_i3.SecureStorageService get storage => (super.noSuchMethod(
|
_i3.SecureStorageService get storage => (super.noSuchMethod(
|
||||||
Invocation.getter(#storage),
|
Invocation.getter(#storage),
|
||||||
|
|
@ -1412,40 +1457,40 @@ class MockStatusCheckerService extends _i1.Mock
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockPermissionHandlerService extends _i1.Mock
|
class MockPermissionHandlerService extends _i1.Mock
|
||||||
implements _i24.PermissionHandlerService {
|
implements _i26.PermissionHandlerService {
|
||||||
@override
|
@override
|
||||||
_i9.Future<_i25.PermissionStatus> requestPermission(
|
_i9.Future<_i27.PermissionStatus> requestPermission(
|
||||||
_i25.Permission? requestedPermission) =>
|
_i27.Permission? requestedPermission) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#requestPermission,
|
#requestPermission,
|
||||||
[requestedPermission],
|
[requestedPermission],
|
||||||
),
|
),
|
||||||
returnValue: _i9.Future<_i25.PermissionStatus>.value(
|
returnValue: _i9.Future<_i27.PermissionStatus>.value(
|
||||||
_i25.PermissionStatus.denied),
|
_i27.PermissionStatus.denied),
|
||||||
returnValueForMissingStub: _i9.Future<_i25.PermissionStatus>.value(
|
returnValueForMissingStub: _i9.Future<_i27.PermissionStatus>.value(
|
||||||
_i25.PermissionStatus.denied),
|
_i27.PermissionStatus.denied),
|
||||||
) as _i9.Future<_i25.PermissionStatus>);
|
) as _i9.Future<_i27.PermissionStatus>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<_i25.PermissionStatus> request(_i25.Permission? permission) =>
|
_i9.Future<_i27.PermissionStatus> request(_i27.Permission? permission) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#request,
|
#request,
|
||||||
[permission],
|
[permission],
|
||||||
),
|
),
|
||||||
returnValue: _i9.Future<_i25.PermissionStatus>.value(
|
returnValue: _i9.Future<_i27.PermissionStatus>.value(
|
||||||
_i25.PermissionStatus.denied),
|
_i27.PermissionStatus.denied),
|
||||||
returnValueForMissingStub: _i9.Future<_i25.PermissionStatus>.value(
|
returnValueForMissingStub: _i9.Future<_i27.PermissionStatus>.value(
|
||||||
_i25.PermissionStatus.denied),
|
_i27.PermissionStatus.denied),
|
||||||
) as _i9.Future<_i25.PermissionStatus>);
|
) as _i9.Future<_i27.PermissionStatus>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A class which mocks [ImagePickerService].
|
/// A class which mocks [ImagePickerService].
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockImagePickerService extends _i1.Mock
|
class MockImagePickerService extends _i1.Mock
|
||||||
implements _i26.ImagePickerService {
|
implements _i28.ImagePickerService {
|
||||||
@override
|
@override
|
||||||
_i9.Future<String?> gallery() => (super.noSuchMethod(
|
_i9.Future<String?> gallery() => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
|
|
@ -1470,7 +1515,7 @@ class MockImagePickerService extends _i1.Mock
|
||||||
/// A class which mocks [GoogleAuthService].
|
/// A class which mocks [GoogleAuthService].
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockGoogleAuthService extends _i1.Mock implements _i27.GoogleAuthService {
|
class MockGoogleAuthService extends _i1.Mock implements _i29.GoogleAuthService {
|
||||||
@override
|
@override
|
||||||
int get listenersCount => (super.noSuchMethod(
|
int get listenersCount => (super.noSuchMethod(
|
||||||
Invocation.getter(#listenersCount),
|
Invocation.getter(#listenersCount),
|
||||||
|
|
@ -1540,7 +1585,7 @@ class MockGoogleAuthService extends _i1.Mock implements _i27.GoogleAuthService {
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockImageDownloaderService extends _i1.Mock
|
class MockImageDownloaderService extends _i1.Mock
|
||||||
implements _i28.ImageDownloaderService {
|
implements _i30.ImageDownloaderService {
|
||||||
@override
|
@override
|
||||||
_i9.Future<String> downloader(String? networkImage) => (super.noSuchMethod(
|
_i9.Future<String> downloader(String? networkImage) => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
|
|
@ -1569,7 +1614,7 @@ class MockImageDownloaderService extends _i1.Mock
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockNotificationService extends _i1.Mock
|
class MockNotificationService extends _i1.Mock
|
||||||
implements _i29.NotificationService {
|
implements _i31.NotificationService {
|
||||||
@override
|
@override
|
||||||
_i9.Future<void> initialize() => (super.noSuchMethod(
|
_i9.Future<void> initialize() => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
|
|
@ -1591,7 +1636,7 @@ class MockNotificationService extends _i1.Mock
|
||||||
) as _i9.Future<void>);
|
) as _i9.Future<void>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i9.Future<void> showNotification(_i30.RemoteMessage? message) =>
|
_i9.Future<void> showNotification(_i32.RemoteMessage? message) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#showNotification,
|
#showNotification,
|
||||||
|
|
@ -1625,7 +1670,7 @@ class MockNotificationService extends _i1.Mock
|
||||||
/// A class which mocks [SmartAuthService].
|
/// A class which mocks [SmartAuthService].
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockSmartAuthService extends _i1.Mock implements _i31.SmartAuthService {
|
class MockSmartAuthService extends _i1.Mock implements _i33.SmartAuthService {
|
||||||
@override
|
@override
|
||||||
bool get listenForMultipleSms => (super.noSuchMethod(
|
bool get listenForMultipleSms => (super.noSuchMethod(
|
||||||
Invocation.getter(#listenForMultipleSms),
|
Invocation.getter(#listenForMultipleSms),
|
||||||
|
|
@ -1657,26 +1702,26 @@ class MockSmartAuthService extends _i1.Mock implements _i31.SmartAuthService {
|
||||||
/// A class which mocks [CourseService].
|
/// A class which mocks [CourseService].
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockCourseService extends _i1.Mock implements _i32.CourseService {
|
class MockCourseService extends _i1.Mock implements _i34.CourseService {
|
||||||
@override
|
@override
|
||||||
_i9.Future<List<_i33.CourseDetail>> getCoursesDetail(int? id) =>
|
_i9.Future<List<_i35.CourseDetail>> getCoursesDetail(int? id) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getCoursesDetail,
|
#getCoursesDetail,
|
||||||
[id],
|
[id],
|
||||||
),
|
),
|
||||||
returnValue:
|
returnValue:
|
||||||
_i9.Future<List<_i33.CourseDetail>>.value(<_i33.CourseDetail>[]),
|
_i9.Future<List<_i35.CourseDetail>>.value(<_i35.CourseDetail>[]),
|
||||||
returnValueForMissingStub:
|
returnValueForMissingStub:
|
||||||
_i9.Future<List<_i33.CourseDetail>>.value(<_i33.CourseDetail>[]),
|
_i9.Future<List<_i35.CourseDetail>>.value(<_i35.CourseDetail>[]),
|
||||||
) as _i9.Future<List<_i33.CourseDetail>>);
|
) as _i9.Future<List<_i35.CourseDetail>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A class which mocks [AudioPlayerService].
|
/// A class which mocks [AudioPlayerService].
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockAudioPlayerService extends _i1.Mock
|
class MockAudioPlayerService extends _i1.Mock
|
||||||
implements _i34.AudioPlayerService {
|
implements _i36.AudioPlayerService {
|
||||||
@override
|
@override
|
||||||
_i4.AudioPlayer get player => (super.noSuchMethod(
|
_i4.AudioPlayer get player => (super.noSuchMethod(
|
||||||
Invocation.getter(#player),
|
Invocation.getter(#player),
|
||||||
|
|
@ -1800,13 +1845,13 @@ class MockAudioPlayerService extends _i1.Mock
|
||||||
///
|
///
|
||||||
/// See the documentation for Mockito's code generation for more information.
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
class MockVoiceRecorderService extends _i1.Mock
|
class MockVoiceRecorderService extends _i1.Mock
|
||||||
implements _i35.VoiceRecorderService {
|
implements _i37.VoiceRecorderService {
|
||||||
@override
|
@override
|
||||||
_i36.VoiceRecordingState get recordingState => (super.noSuchMethod(
|
_i38.VoiceRecordingState get recordingState => (super.noSuchMethod(
|
||||||
Invocation.getter(#recordingState),
|
Invocation.getter(#recordingState),
|
||||||
returnValue: _i36.VoiceRecordingState.pending,
|
returnValue: _i38.VoiceRecordingState.pending,
|
||||||
returnValueForMissingStub: _i36.VoiceRecordingState.pending,
|
returnValueForMissingStub: _i38.VoiceRecordingState.pending,
|
||||||
) as _i36.VoiceRecordingState);
|
) as _i38.VoiceRecordingState);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i5.WaveformRecorderController get waveController => (super.noSuchMethod(
|
_i5.WaveformRecorderController get waveController => (super.noSuchMethod(
|
||||||
|
|
|
||||||
11
test/viewmodels/learn_subcategory_viewmodel_test.dart
Normal file
11
test/viewmodels/learn_subcategory_viewmodel_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('LearnSubcategoryViewModel Tests -', () {
|
||||||
|
setUp(() => registerServices());
|
||||||
|
tearDown(() => locator.reset());
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user