Merge branch 'release/0.1.1'
- feat(course): Finalize course practice integration.
This commit is contained in:
commit
a00c39d4e5
|
|
@ -50,6 +50,7 @@ import 'package:yimaru_app/ui/views/course_subcategory/course_subcategory_view.d
|
||||||
import 'package:yimaru_app/ui/views/course/course_view.dart';
|
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';
|
||||||
// @stacked-import
|
// @stacked-import
|
||||||
|
|
||||||
@StackedApp(
|
@StackedApp(
|
||||||
|
|
@ -88,6 +89,7 @@ import 'package:yimaru_app/services/voice_recorder_service.dart';
|
||||||
MaterialRoute(page: DuolingoView),
|
MaterialRoute(page: DuolingoView),
|
||||||
MaterialRoute(page: CourseSubcategoryView),
|
MaterialRoute(page: CourseSubcategoryView),
|
||||||
MaterialRoute(page: CourseView),
|
MaterialRoute(page: CourseView),
|
||||||
|
MaterialRoute(page: CoursePracticeQuestionView),
|
||||||
// @stacked-route
|
// @stacked-route
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||||
import 'package:flutter/material.dart' as _i36;
|
import 'package:flutter/material.dart' as _i37;
|
||||||
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 _i41;
|
import 'package:stacked_services/stacked_services.dart' as _i42;
|
||||||
import 'package:yimaru_app/models/course.dart' as _i37;
|
import 'package:yimaru_app/models/course.dart' as _i38;
|
||||||
import 'package:yimaru_app/models/course_category.dart' as _i39;
|
import 'package:yimaru_app/models/course_category.dart' as _i40;
|
||||||
import 'package:yimaru_app/models/course_lesson.dart' as _i38;
|
import 'package:yimaru_app/models/course_lesson.dart' as _i39;
|
||||||
import 'package:yimaru_app/models/course_subcategory.dart' as _i40;
|
import 'package:yimaru_app/models/course_subcategory.dart' as _i41;
|
||||||
import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart'
|
import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart'
|
||||||
as _i9;
|
as _i9;
|
||||||
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i22;
|
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i22;
|
||||||
|
|
@ -29,6 +29,8 @@ import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart'
|
||||||
as _i28;
|
as _i28;
|
||||||
import 'package:yimaru_app/ui/views/course_practice/course_practice_view.dart'
|
import 'package:yimaru_app/ui/views/course_practice/course_practice_view.dart'
|
||||||
as _i27;
|
as _i27;
|
||||||
|
import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_view.dart'
|
||||||
|
as _i36;
|
||||||
import 'package:yimaru_app/ui/views/course_subcategory/course_subcategory_view.dart'
|
import 'package:yimaru_app/ui/views/course_subcategory/course_subcategory_view.dart'
|
||||||
as _i34;
|
as _i34;
|
||||||
import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7;
|
import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7;
|
||||||
|
|
@ -134,6 +136,8 @@ class Routes {
|
||||||
|
|
||||||
static const courseView = '/course-view';
|
static const courseView = '/course-view';
|
||||||
|
|
||||||
|
static const coursePracticeQuestionView = '/course-practice-question-view';
|
||||||
|
|
||||||
static const all = <String>{
|
static const all = <String>{
|
||||||
homeView,
|
homeView,
|
||||||
onboardingView,
|
onboardingView,
|
||||||
|
|
@ -169,6 +173,7 @@ class Routes {
|
||||||
duolingoView,
|
duolingoView,
|
||||||
courseSubcategoryView,
|
courseSubcategoryView,
|
||||||
courseView,
|
courseView,
|
||||||
|
coursePracticeQuestionView,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,17 +315,21 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
Routes.courseView,
|
Routes.courseView,
|
||||||
page: _i35.CourseView,
|
page: _i35.CourseView,
|
||||||
),
|
),
|
||||||
|
_i1.RouteDef(
|
||||||
|
Routes.coursePracticeQuestionView,
|
||||||
|
page: _i36.CoursePracticeQuestionView,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
final _pagesMap = <Type, _i1.StackedRouteFactory>{
|
final _pagesMap = <Type, _i1.StackedRouteFactory>{
|
||||||
_i2.HomeView: (data) {
|
_i2.HomeView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i2.HomeView(),
|
builder: (context) => const _i2.HomeView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i3.OnboardingView: (data) {
|
_i3.OnboardingView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i3.OnboardingView(),
|
builder: (context) => const _i3.OnboardingView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
|
|
@ -329,116 +338,116 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
final args = data.getArgs<StartupViewArguments>(
|
final args = data.getArgs<StartupViewArguments>(
|
||||||
orElse: () => const StartupViewArguments(),
|
orElse: () => const StartupViewArguments(),
|
||||||
);
|
);
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i5.ProfileView(),
|
builder: (context) => const _i5.ProfileView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i6.ProfileDetailView: (data) {
|
_i6.ProfileDetailView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i6.ProfileDetailView(),
|
builder: (context) => const _i6.ProfileDetailView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i7.DownloadsView: (data) {
|
_i7.DownloadsView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i7.DownloadsView(),
|
builder: (context) => const _i7.DownloadsView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i8.ProgressView: (data) {
|
_i8.ProgressView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i8.ProgressView(),
|
builder: (context) => const _i8.ProgressView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i9.AccountPrivacyView: (data) {
|
_i9.AccountPrivacyView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i9.AccountPrivacyView(),
|
builder: (context) => const _i9.AccountPrivacyView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i10.SupportView: (data) {
|
_i10.SupportView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i10.SupportView(),
|
builder: (context) => const _i10.SupportView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i11.TelegramSupportView: (data) {
|
_i11.TelegramSupportView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i11.TelegramSupportView(),
|
builder: (context) => const _i11.TelegramSupportView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i12.CallSupportView: (data) {
|
_i12.CallSupportView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i12.CallSupportView(),
|
builder: (context) => const _i12.CallSupportView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i13.LanguageView: (data) {
|
_i13.LanguageView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i13.LanguageView(),
|
builder: (context) => const _i13.LanguageView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i14.PrivacyPolicyView: (data) {
|
_i14.PrivacyPolicyView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i14.PrivacyPolicyView(),
|
builder: (context) => const _i14.PrivacyPolicyView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i15.TermsAndConditionsView: (data) {
|
_i15.TermsAndConditionsView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i15.TermsAndConditionsView(),
|
builder: (context) => const _i15.TermsAndConditionsView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i16.RegisterView: (data) {
|
_i16.RegisterView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i16.RegisterView(),
|
builder: (context) => const _i16.RegisterView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i17.LoginView: (data) {
|
_i17.LoginView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i17.LoginView(),
|
builder: (context) => const _i17.LoginView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i18.LearnView: (data) {
|
_i18.LearnView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i18.LearnView(),
|
builder: (context) => const _i18.LearnView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i19.LearnLevelView: (data) {
|
_i19.LearnLevelView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i19.LearnLevelView(),
|
builder: (context) => const _i19.LearnLevelView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i20.LearnModuleView: (data) {
|
_i20.LearnModuleView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i20.LearnModuleView(),
|
builder: (context) => const _i20.LearnModuleView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i21.WelcomeView: (data) {
|
_i21.WelcomeView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.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,
|
||||||
|
|
@ -446,7 +455,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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => _i23.LearnLessonView(
|
builder: (context) => _i23.LearnLessonView(
|
||||||
key: args.key,
|
key: args.key,
|
||||||
title: args.title,
|
title: args.title,
|
||||||
|
|
@ -458,14 +467,14 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i24.ForgetPasswordView: (data) {
|
_i24.ForgetPasswordView: (data) {
|
||||||
return _i36.MaterialPageRoute<dynamic>(
|
return _i37.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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => _i25.LearnLessonDetailView(
|
builder: (context) => _i25.LearnLessonDetailView(
|
||||||
key: args.key,
|
key: args.key,
|
||||||
title: args.title,
|
title: args.title,
|
||||||
|
|
@ -476,7 +485,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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => _i26.LearnPracticeView(
|
builder: (context) => _i26.LearnPracticeView(
|
||||||
key: args.key,
|
key: args.key,
|
||||||
title: args.title,
|
title: args.title,
|
||||||
|
|
@ -488,7 +497,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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.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,
|
||||||
|
|
@ -496,21 +505,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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.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,
|
||||||
|
|
@ -518,7 +527,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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.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,
|
||||||
|
|
@ -526,21 +535,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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.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,
|
||||||
|
|
@ -548,12 +557,21 @@ 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 _i36.MaterialPageRoute<dynamic>(
|
return _i37.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,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
_i36.CoursePracticeQuestionView: (data) {
|
||||||
|
final args =
|
||||||
|
data.getArgs<CoursePracticeQuestionViewArguments>(nullOk: false);
|
||||||
|
return _i37.MaterialPageRoute<dynamic>(
|
||||||
|
builder: (context) =>
|
||||||
|
_i36.CoursePracticeQuestionView(key: args.key, id: args.id),
|
||||||
|
settings: data,
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -569,7 +587,7 @@ class StartupViewArguments {
|
||||||
this.label = 'Loading',
|
this.label = 'Loading',
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i36.Key? key;
|
final _i37.Key? key;
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
|
|
||||||
|
|
@ -596,7 +614,7 @@ class AssessmentViewArguments {
|
||||||
required this.data,
|
required this.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i36.Key? key;
|
final _i37.Key? key;
|
||||||
|
|
||||||
final Map<String, dynamic> data;
|
final Map<String, dynamic> data;
|
||||||
|
|
||||||
|
|
@ -627,7 +645,7 @@ class LearnLessonViewArguments {
|
||||||
required this.description,
|
required this.description,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i36.Key? key;
|
final _i37.Key? key;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
|
|
@ -674,7 +692,7 @@ class LearnLessonDetailViewArguments {
|
||||||
required this.description,
|
required this.description,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i36.Key? key;
|
final _i37.Key? key;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
|
|
@ -714,7 +732,7 @@ class LearnPracticeViewArguments {
|
||||||
required this.buttonLabel,
|
required this.buttonLabel,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i36.Key? key;
|
final _i37.Key? key;
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
|
|
@ -755,7 +773,7 @@ class CoursePracticeViewArguments {
|
||||||
required this.id,
|
required this.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i36.Key? key;
|
final _i37.Key? key;
|
||||||
|
|
||||||
final int id;
|
final int id;
|
||||||
|
|
||||||
|
|
@ -782,9 +800,9 @@ class CoursePaymentViewArguments {
|
||||||
required this.course,
|
required this.course,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i36.Key? key;
|
final _i37.Key? key;
|
||||||
|
|
||||||
final _i37.Course course;
|
final _i38.Course course;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -809,7 +827,7 @@ class FailureViewArguments {
|
||||||
required this.label,
|
required this.label,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i36.Key? key;
|
final _i37.Key? key;
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
|
|
||||||
|
|
@ -836,9 +854,9 @@ class CourseLessonViewArguments {
|
||||||
required this.course,
|
required this.course,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i36.Key? key;
|
final _i37.Key? key;
|
||||||
|
|
||||||
final _i37.Course course;
|
final _i38.Course course;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -863,9 +881,9 @@ class CourseLessonDetailViewArguments {
|
||||||
required this.lesson,
|
required this.lesson,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i36.Key? key;
|
final _i37.Key? key;
|
||||||
|
|
||||||
final _i38.CourseLesson lesson;
|
final _i39.CourseLesson lesson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -890,9 +908,9 @@ class CourseSubcategoryViewArguments {
|
||||||
required this.category,
|
required this.category,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i36.Key? key;
|
final _i37.Key? key;
|
||||||
|
|
||||||
final _i39.CourseCategory category;
|
final _i40.CourseCategory category;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -917,9 +935,9 @@ class CourseViewArguments {
|
||||||
required this.subcategory,
|
required this.subcategory,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i36.Key? key;
|
final _i37.Key? key;
|
||||||
|
|
||||||
final _i40.CourseSubcategory subcategory;
|
final _i41.CourseSubcategory subcategory;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -938,7 +956,34 @@ class CourseViewArguments {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NavigatorStateExtension on _i41.NavigationService {
|
class CoursePracticeQuestionViewArguments {
|
||||||
|
const CoursePracticeQuestionViewArguments({
|
||||||
|
this.key,
|
||||||
|
required this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
final _i37.Key? key;
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '{"key": "$key", "id": "$id"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant CoursePracticeQuestionViewArguments other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
return other.key == key && other.id == id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return key.hashCode ^ id.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NavigatorStateExtension on _i42.NavigationService {
|
||||||
Future<dynamic> navigateToHomeView([
|
Future<dynamic> navigateToHomeView([
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -968,7 +1013,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToStartupView({
|
Future<dynamic> navigateToStartupView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
String label = 'Loading',
|
String label = 'Loading',
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1223,7 +1268,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToAssessmentView({
|
Future<dynamic> navigateToAssessmentView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required Map<String, dynamic> data,
|
required Map<String, dynamic> data,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1240,7 +1285,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToLearnLessonView({
|
Future<dynamic> navigateToLearnLessonView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required String title,
|
required String title,
|
||||||
required String topics,
|
required String topics,
|
||||||
required String subtitle,
|
required String subtitle,
|
||||||
|
|
@ -1281,7 +1326,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToLearnLessonDetailView({
|
Future<dynamic> navigateToLearnLessonDetailView({
|
||||||
_i36.Key? key,
|
_i37.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,
|
||||||
|
|
@ -1304,7 +1349,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToLearnPracticeView({
|
Future<dynamic> navigateToLearnPracticeView({
|
||||||
_i36.Key? key,
|
_i37.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,
|
||||||
|
|
@ -1329,7 +1374,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCoursePracticeView({
|
Future<dynamic> navigateToCoursePracticeView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required int id,
|
required int id,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1346,8 +1391,8 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCoursePaymentView({
|
Future<dynamic> navigateToCoursePaymentView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required _i37.Course course,
|
required _i38.Course course,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1377,7 +1422,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToFailureView({
|
Future<dynamic> navigateToFailureView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required String label,
|
required String label,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1394,8 +1439,8 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCourseLessonView({
|
Future<dynamic> navigateToCourseLessonView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required _i37.Course course,
|
required _i38.Course course,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1411,8 +1456,8 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCourseLessonDetailView({
|
Future<dynamic> navigateToCourseLessonDetailView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required _i38.CourseLesson lesson,
|
required _i39.CourseLesson lesson,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1442,8 +1487,8 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCourseSubcategoryView({
|
Future<dynamic> navigateToCourseSubcategoryView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required _i39.CourseCategory category,
|
required _i40.CourseCategory category,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1459,8 +1504,8 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToCourseView({
|
Future<dynamic> navigateToCourseView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required _i40.CourseSubcategory subcategory,
|
required _i41.CourseSubcategory subcategory,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1475,6 +1520,23 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
transition: transition);
|
transition: transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<dynamic> navigateToCoursePracticeQuestionView({
|
||||||
|
_i37.Key? key,
|
||||||
|
required int id,
|
||||||
|
int? routerId,
|
||||||
|
bool preventDuplicates = true,
|
||||||
|
Map<String, String>? parameters,
|
||||||
|
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||||
|
transition,
|
||||||
|
}) async {
|
||||||
|
return navigateTo<dynamic>(Routes.coursePracticeQuestionView,
|
||||||
|
arguments: CoursePracticeQuestionViewArguments(key: key, id: id),
|
||||||
|
id: routerId,
|
||||||
|
preventDuplicates: preventDuplicates,
|
||||||
|
parameters: parameters,
|
||||||
|
transition: transition);
|
||||||
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithHomeView([
|
Future<dynamic> replaceWithHomeView([
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1504,7 +1566,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithStartupView({
|
Future<dynamic> replaceWithStartupView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
String label = 'Loading',
|
String label = 'Loading',
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1759,7 +1821,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithAssessmentView({
|
Future<dynamic> replaceWithAssessmentView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required Map<String, dynamic> data,
|
required Map<String, dynamic> data,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1776,7 +1838,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithLearnLessonView({
|
Future<dynamic> replaceWithLearnLessonView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required String title,
|
required String title,
|
||||||
required String topics,
|
required String topics,
|
||||||
required String subtitle,
|
required String subtitle,
|
||||||
|
|
@ -1817,7 +1879,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithLearnLessonDetailView({
|
Future<dynamic> replaceWithLearnLessonDetailView({
|
||||||
_i36.Key? key,
|
_i37.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,
|
||||||
|
|
@ -1840,7 +1902,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithLearnPracticeView({
|
Future<dynamic> replaceWithLearnPracticeView({
|
||||||
_i36.Key? key,
|
_i37.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,
|
||||||
|
|
@ -1865,7 +1927,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCoursePracticeView({
|
Future<dynamic> replaceWithCoursePracticeView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required int id,
|
required int id,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1882,8 +1944,8 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCoursePaymentView({
|
Future<dynamic> replaceWithCoursePaymentView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required _i37.Course course,
|
required _i38.Course course,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1913,7 +1975,7 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithFailureView({
|
Future<dynamic> replaceWithFailureView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required String label,
|
required String label,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1930,8 +1992,8 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCourseLessonView({
|
Future<dynamic> replaceWithCourseLessonView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required _i37.Course course,
|
required _i38.Course course,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1947,8 +2009,8 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCourseLessonDetailView({
|
Future<dynamic> replaceWithCourseLessonDetailView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required _i38.CourseLesson lesson,
|
required _i39.CourseLesson lesson,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1978,8 +2040,8 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCourseSubcategoryView({
|
Future<dynamic> replaceWithCourseSubcategoryView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required _i39.CourseCategory category,
|
required _i40.CourseCategory category,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -1995,8 +2057,8 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithCourseView({
|
Future<dynamic> replaceWithCourseView({
|
||||||
_i36.Key? key,
|
_i37.Key? key,
|
||||||
required _i40.CourseSubcategory subcategory,
|
required _i41.CourseSubcategory subcategory,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
Map<String, String>? parameters,
|
Map<String, String>? parameters,
|
||||||
|
|
@ -2010,4 +2072,21 @@ extension NavigatorStateExtension on _i41.NavigationService {
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
transition: transition);
|
transition: transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<dynamic> replaceWithCoursePracticeQuestionView({
|
||||||
|
_i37.Key? key,
|
||||||
|
required int id,
|
||||||
|
int? routerId,
|
||||||
|
bool preventDuplicates = true,
|
||||||
|
Map<String, String>? parameters,
|
||||||
|
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||||
|
transition,
|
||||||
|
}) async {
|
||||||
|
return replaceWith<dynamic>(Routes.coursePracticeQuestionView,
|
||||||
|
arguments: CoursePracticeQuestionViewArguments(key: key, id: id),
|
||||||
|
id: routerId,
|
||||||
|
preventDuplicates: preventDuplicates,
|
||||||
|
parameters: parameters,
|
||||||
|
transition: transition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,15 @@ class CourseLesson {
|
||||||
@JsonKey(name: 'video_url')
|
@JsonKey(name: 'video_url')
|
||||||
String? videoUrl;
|
String? videoUrl;
|
||||||
|
|
||||||
|
@JsonKey(name: 'vimeo_status')
|
||||||
|
String? vimeoStatus;
|
||||||
|
|
||||||
@JsonKey(name: 'instructor_id')
|
@JsonKey(name: 'instructor_id')
|
||||||
int? instructorId;
|
int? instructorId;
|
||||||
|
|
||||||
@JsonKey(name: 'sub_course_id')
|
@JsonKey(name: 'sub_course_id')
|
||||||
int? courseId;
|
int? courseId;
|
||||||
|
|
||||||
@JsonKey(name: 'vimeo_status')
|
|
||||||
String? vimeoStatus;
|
|
||||||
|
|
||||||
@JsonKey(name: 'display_order')
|
@JsonKey(name: 'display_order')
|
||||||
int? displayOrder;
|
int? displayOrder;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ part 'option.g.dart';
|
||||||
class Option {
|
class Option {
|
||||||
final int? id;
|
final int? id;
|
||||||
|
|
||||||
@JsonKey(name: 'option_text')
|
|
||||||
final String? optionText;
|
|
||||||
|
|
||||||
@JsonKey(name: 'is_correct')
|
@JsonKey(name: 'is_correct')
|
||||||
final bool? isCorrect;
|
final bool? isCorrect;
|
||||||
|
|
||||||
|
@JsonKey(name: 'option_text')
|
||||||
|
final String? optionText;
|
||||||
|
|
||||||
const Option({this.id, this.optionText, this.isCorrect});
|
const Option({this.id, this.optionText, this.isCorrect});
|
||||||
|
|
||||||
factory Option.fromJson(Map<String, dynamic> json) => _$OptionFromJson(json);
|
factory Option.fromJson(Map<String, dynamic> json) => _$OptionFromJson(json);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:yimaru_app/models/option.dart';
|
import 'package:yimaru_app/models/option.dart';
|
||||||
part 'assessment.g.dart';
|
part 'question.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class Assessment {
|
class Question {
|
||||||
final int? id;
|
final int? id;
|
||||||
|
|
||||||
final int? points;
|
final int? points;
|
||||||
|
|
@ -21,7 +21,7 @@ class Assessment {
|
||||||
@JsonKey(name: 'difficulty_level')
|
@JsonKey(name: 'difficulty_level')
|
||||||
final String? difficultyLevel;
|
final String? difficultyLevel;
|
||||||
|
|
||||||
const Assessment({
|
const Question({
|
||||||
this.id,
|
this.id,
|
||||||
this.points,
|
this.points,
|
||||||
this.status,
|
this.status,
|
||||||
|
|
@ -31,8 +31,8 @@ class Assessment {
|
||||||
this.difficultyLevel,
|
this.difficultyLevel,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Assessment.fromJson(Map<String, dynamic> json) =>
|
factory Question.fromJson(Map<String, dynamic> json) =>
|
||||||
_$AssessmentFromJson(json);
|
_$QuestionFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$AssessmentToJson(this);
|
Map<String, dynamic> toJson() => _$QuestionToJson(this);
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'assessment.dart';
|
part of 'question.dart';
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
Assessment _$AssessmentFromJson(Map<String, dynamic> json) => Assessment(
|
Question _$QuestionFromJson(Map<String, dynamic> json) => Question(
|
||||||
id: (json['id'] as num?)?.toInt(),
|
id: (json['id'] as num?)?.toInt(),
|
||||||
points: (json['points'] as num?)?.toInt(),
|
points: (json['points'] as num?)?.toInt(),
|
||||||
status: json['status'] as String?,
|
status: json['status'] as String?,
|
||||||
|
|
@ -18,8 +18,7 @@ Assessment _$AssessmentFromJson(Map<String, dynamic> json) => Assessment(
|
||||||
difficultyLevel: json['difficulty_level'] as String?,
|
difficultyLevel: json['difficulty_level'] as String?,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$AssessmentToJson(Assessment instance) =>
|
Map<String, dynamic> _$QuestionToJson(Question instance) => <String, dynamic>{
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'points': instance.points,
|
'points': instance.points,
|
||||||
'status': instance.status,
|
'status': instance.status,
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:yimaru_app/models/assessment.dart';
|
import 'package:yimaru_app/models/question.dart';
|
||||||
import 'package:yimaru_app/models/course_subcategory.dart';
|
import 'package:yimaru_app/models/course_subcategory.dart';
|
||||||
import 'package:yimaru_app/models/course_category.dart';
|
import 'package:yimaru_app/models/course_category.dart';
|
||||||
import 'package:yimaru_app/models/course_lesson.dart';
|
import 'package:yimaru_app/models/course_lesson.dart';
|
||||||
|
|
@ -18,7 +18,7 @@ class ApiService {
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
final _service = locator<DioService>();
|
final _service = locator<DioService>();
|
||||||
|
|
||||||
// Register
|
// Register with email
|
||||||
Future<Map<String, dynamic>> registerWithEmail(
|
Future<Map<String, dynamic>> registerWithEmail(
|
||||||
Map<String, dynamic> data) async {
|
Map<String, dynamic> data) async {
|
||||||
try {
|
try {
|
||||||
|
|
@ -46,7 +46,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Email login
|
// Login
|
||||||
Future<Map<String, dynamic>> login(Map<String, dynamic> data) async {
|
Future<Map<String, dynamic>> login(Map<String, dynamic> data) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.post(
|
Response response = await _service.dio.post(
|
||||||
|
|
@ -74,7 +74,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Google login
|
// Google auth
|
||||||
Future<Map<String, dynamic>> googleAuth(Map<String, dynamic> data) async {
|
Future<Map<String, dynamic>> googleAuth(Map<String, dynamic> data) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.post(
|
Response response = await _service.dio.post(
|
||||||
|
|
@ -211,7 +211,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile completed
|
// GEt profile completion status
|
||||||
Future<Map<String, dynamic>> getProfileStatus(UserModel? user) async {
|
Future<Map<String, dynamic>> getProfileStatus(UserModel? user) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.get(
|
Response response = await _service.dio.get(
|
||||||
|
|
@ -238,7 +238,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get profile
|
// Get profile data
|
||||||
Future<Map<String, dynamic>> getProfileData(int? userId) async {
|
Future<Map<String, dynamic>> getProfileData(int? userId) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.get(
|
Response response = await _service.dio.get(
|
||||||
|
|
@ -345,10 +345,10 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assessments
|
// Get assessments
|
||||||
Future<List<Assessment>> getAssessments() async {
|
Future<List<Question>> getAssessments() async {
|
||||||
try {
|
try {
|
||||||
List<Assessment> assessments = [];
|
List<Question> assessments = [];
|
||||||
|
|
||||||
final Response response =
|
final Response response =
|
||||||
await _service.dio.get('$kBaseUrl/$kAssessmentsUrl');
|
await _service.dio.get('$kBaseUrl/$kAssessmentsUrl');
|
||||||
|
|
@ -358,7 +358,7 @@ class ApiService {
|
||||||
var decodedData = data['data'] as List;
|
var decodedData = data['data'] as List;
|
||||||
assessments = decodedData.map(
|
assessments = decodedData.map(
|
||||||
(e) {
|
(e) {
|
||||||
return Assessment.fromJson(e);
|
return Question.fromJson(e);
|
||||||
},
|
},
|
||||||
).toList();
|
).toList();
|
||||||
return assessments;
|
return assessments;
|
||||||
|
|
@ -369,7 +369,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Course categories
|
// Get course categories
|
||||||
Future<List<CourseCategory>> getCourseCategories() async {
|
Future<List<CourseCategory>> getCourseCategories() async {
|
||||||
try {
|
try {
|
||||||
List<CourseCategory> categories = [];
|
List<CourseCategory> categories = [];
|
||||||
|
|
@ -393,7 +393,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Course subcategory
|
// Get course subcategory
|
||||||
Future<List<CourseSubcategory>> getCourseSubcategories(int id) async {
|
Future<List<CourseSubcategory>> getCourseSubcategories(int id) async {
|
||||||
try {
|
try {
|
||||||
List<CourseSubcategory> subcategories = [];
|
List<CourseSubcategory> subcategories = [];
|
||||||
|
|
@ -417,7 +417,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sub-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 = [];
|
||||||
|
|
@ -441,7 +441,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Course progress
|
// Get course progress
|
||||||
Future<List<CourseProgress>> getCourseProgress(int id) async {
|
Future<List<CourseProgress>> getCourseProgress(int id) async {
|
||||||
try {
|
try {
|
||||||
List<CourseProgress> courseProgress = [];
|
List<CourseProgress> courseProgress = [];
|
||||||
|
|
@ -465,7 +465,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Course videos
|
// Get course lessons
|
||||||
Future<List<CourseLesson>> getCourseLessons(int id) async {
|
Future<List<CourseLesson>> getCourseLessons(int id) async {
|
||||||
try {
|
try {
|
||||||
List<CourseLesson> courseLessons = [];
|
List<CourseLesson> courseLessons = [];
|
||||||
|
|
@ -513,12 +513,12 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Course practices
|
// Course practices
|
||||||
Future<List<Practice>> getCoursePractices(Map<String, dynamic> data) async {
|
Future<List<Practice>> getCoursePractices(int id) async {
|
||||||
try {
|
try {
|
||||||
List<Practice> coursePractices = [];
|
List<Practice> coursePractices = [];
|
||||||
|
|
||||||
final Response response = await _service.dio
|
final Response response = await _service.dio.get(
|
||||||
.get('$kBaseUrl/$kPracticeBaseUrl/$kCoursePractice', data: data);
|
'$kBaseUrl/$kPracticeBaseUrl/$kCoursePractice?owner_type=SUB_COURSE&owner_id=$id');
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var data = response.data;
|
var data = response.data;
|
||||||
|
|
@ -536,7 +536,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Course practic questions
|
// Get course practic questions
|
||||||
Future<List<PracticeQuestion>> getCoursePracticeQuestions(int id) async {
|
Future<List<PracticeQuestion>> getCoursePracticeQuestions(int id) async {
|
||||||
try {
|
try {
|
||||||
List<PracticeQuestion> coursePracticeQuestions = [];
|
List<PracticeQuestion> coursePracticeQuestions = [];
|
||||||
|
|
@ -559,4 +559,21 @@ class ApiService {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get course practice question
|
||||||
|
Future<Question?> getCoursePracticeQuestion(int id) async {
|
||||||
|
try {
|
||||||
|
final Response response =
|
||||||
|
await _service.dio.get('$kBaseUrl/$kCoursePracticeQuestion/$id');
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
Question question = Question.fromJson(response.data['data']);
|
||||||
|
|
||||||
|
return question;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:stacked/stacked.dart';
|
||||||
import '../ui/common/helper_functions.dart';
|
import '../ui/common/helper_functions.dart';
|
||||||
|
|
||||||
class AudioPlayerService with ListenableServiceMixin {
|
class AudioPlayerService with ListenableServiceMixin {
|
||||||
|
// Player initialization
|
||||||
final AudioPlayer _player = AudioPlayer();
|
final AudioPlayer _player = AudioPlayer();
|
||||||
|
|
||||||
AudioPlayer get player => _player;
|
AudioPlayer get player => _player;
|
||||||
|
|
@ -30,8 +31,6 @@ class AudioPlayerService with ListenableServiceMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> playLocal(String url) async {
|
Future<void> playLocal(String url) async {
|
||||||
|
|
||||||
|
|
||||||
await _player.play(UrlSource(url));
|
await _player.play(UrlSource(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,25 +67,11 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
profileCompleted: await _secureService.getBool('profileCompleted'),
|
profileCompleted: await _secureService.getBool('profileCompleted'),
|
||||||
);
|
);
|
||||||
|
|
||||||
/* UserModel(
|
|
||||||
email: _user?.email,
|
|
||||||
gender: _user?.gender,
|
|
||||||
region: _user?.region,
|
|
||||||
userId: _user?.userId,
|
|
||||||
country: _user?.country,
|
|
||||||
lastName: _user?.lastName,
|
|
||||||
birthday: _user?.birthday,
|
|
||||||
firstName: _user?.firstName,
|
|
||||||
occupation: _user?.occupation,
|
|
||||||
accessToken: _user?.accessToken,
|
|
||||||
refreshToken: _user?.refreshToken,
|
|
||||||
profilePicture: _user?.profilePicture,
|
|
||||||
userInfoLoaded: _user?.userInfoLoaded ?? false,
|
|
||||||
profileCompleted: await _secureService.getBool('profileCompleted'));
|
|
||||||
*/
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save profile picture
|
||||||
Future<void> saveProfilePicture(String image) async {
|
Future<void> saveProfilePicture(String image) async {
|
||||||
await _secureService.setString('profilePicture', image);
|
await _secureService.setString('profilePicture', image);
|
||||||
_user = _user?.copyWith(
|
_user = _user?.copyWith(
|
||||||
|
|
@ -93,26 +79,10 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
profilePicture: await _secureService.getString('profilePicture'),
|
profilePicture: await _secureService.getString('profilePicture'),
|
||||||
);
|
);
|
||||||
|
|
||||||
/*UserModel(
|
|
||||||
email: _user?.email,
|
|
||||||
gender: _user?.gender,
|
|
||||||
region: _user?.region,
|
|
||||||
userId: _user?.userId,
|
|
||||||
country: _user?.country,
|
|
||||||
lastName: _user?.lastName,
|
|
||||||
birthday: _user?.birthday,
|
|
||||||
firstName: _user?.firstName,
|
|
||||||
occupation: _user?.occupation,
|
|
||||||
accessToken: _user?.accessToken,
|
|
||||||
refreshToken: _user?.refreshToken,
|
|
||||||
profileCompleted: _user?.profileCompleted,
|
|
||||||
userInfoLoaded: _user?.userInfoLoaded ?? false,
|
|
||||||
profilePicture: await _secureService.getString('profilePicture'),
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save user data
|
||||||
Future<void> saveUserData(UserModel data) async {
|
Future<void> saveUserData(UserModel data) async {
|
||||||
await _secureService.setBool('userInfoLoaded', true);
|
await _secureService.setBool('userInfoLoaded', true);
|
||||||
await _secureService.setBool(
|
await _secureService.setBool(
|
||||||
|
|
@ -145,6 +115,7 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update user data
|
||||||
Future<void> updateUserData(Map<String, dynamic> data) async {
|
Future<void> updateUserData(Map<String, dynamic> data) async {
|
||||||
await _secureService.setString('region', data['region']);
|
await _secureService.setString('region', data['region']);
|
||||||
await _secureService.setString('gender', data['gender']);
|
await _secureService.setString('gender', data['gender']);
|
||||||
|
|
@ -164,31 +135,19 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
occupation: await _secureService.getString('occupation'),
|
occupation: await _secureService.getString('occupation'),
|
||||||
);
|
);
|
||||||
|
|
||||||
/*UserModel(
|
|
||||||
email: _user?.email,
|
|
||||||
userId: _user?.userId,
|
|
||||||
accessToken: _user?.accessToken,
|
|
||||||
refreshToken: _user?.refreshToken,
|
|
||||||
profilePicture: _user?.profilePicture,
|
|
||||||
profileCompleted: _user?.profileCompleted,
|
|
||||||
region: await _secureService.getString('region'),
|
|
||||||
gender: await _secureService.getString('gender'),
|
|
||||||
country: await _secureService.getString('country'),
|
|
||||||
lastName: await _secureService.getString('lastName'),
|
|
||||||
birthday: await _secureService.getString('birthday'),
|
|
||||||
firstName: await _secureService.getString('firstName'),
|
|
||||||
occupation: await _secureService.getString('occupation'),
|
|
||||||
);*/
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check first time install
|
||||||
Future<bool> isFirstTimeInstall() async =>
|
Future<bool> isFirstTimeInstall() async =>
|
||||||
await _secureService.getBool('firstTimeInstall') ?? true;
|
await _secureService.getBool('firstTimeInstall') ?? true;
|
||||||
|
|
||||||
|
// Set first time install
|
||||||
Future<void> setFirstTimeInstall(bool value) async {
|
Future<void> setFirstTimeInstall(bool value) async {
|
||||||
await _secureService.setBool('firstTimeInstall', value);
|
await _secureService.setBool('firstTimeInstall', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get user data
|
||||||
Future<UserModel?> getUser() async {
|
Future<UserModel?> getUser() async {
|
||||||
_user = UserModel(
|
_user = UserModel(
|
||||||
userId: await _secureService.getInt('userId'),
|
userId: await _secureService.getInt('userId'),
|
||||||
|
|
@ -209,6 +168,7 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
return _user;
|
return _user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logout
|
||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
bool firstTimeInstall = await isFirstTimeInstall();
|
bool firstTimeInstall = await isFirstTimeInstall();
|
||||||
_user = null;
|
_user = null;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,10 @@ import 'package:yimaru_app/services/api_service.dart';
|
||||||
import '../models/course_detail.dart';
|
import '../models/course_detail.dart';
|
||||||
|
|
||||||
class CourseService {
|
class CourseService {
|
||||||
|
// Dependency injection
|
||||||
final _apiService = locator<ApiService>();
|
final _apiService = locator<ApiService>();
|
||||||
|
|
||||||
|
// Get course detail
|
||||||
Future<List<CourseDetail>> getCoursesDetail(int id) async {
|
Future<List<CourseDetail>> getCoursesDetail(int id) async {
|
||||||
final courses = await _apiService.getCourses(id);
|
final courses = await _apiService.getCourses(id);
|
||||||
final progress = await _apiService.getCourseProgress(id);
|
final progress = await _apiService.getCourseProgress(id);
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,14 @@ import 'package:pinput/pinput.dart';
|
||||||
import 'package:smart_auth/smart_auth.dart';
|
import 'package:smart_auth/smart_auth.dart';
|
||||||
|
|
||||||
class SmartAuthService implements SmsRetriever {
|
class SmartAuthService implements SmsRetriever {
|
||||||
|
// Instance initialization
|
||||||
final SmartAuth _smartAuth = SmartAuth.instance;
|
final SmartAuth _smartAuth = SmartAuth.instance;
|
||||||
|
|
||||||
|
// Dispose listener
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() => _smartAuth.removeUserConsentApiListener();
|
Future<void> dispose() => _smartAuth.removeUserConsentApiListener();
|
||||||
|
|
||||||
|
// Get sms code
|
||||||
@override
|
@override
|
||||||
Future<String?> getSmsCode() async {
|
Future<String?> getSmsCode() async {
|
||||||
final res = await _smartAuth.getSmsWithUserConsentApi();
|
final res = await _smartAuth.getSmsWithUserConsentApi();
|
||||||
|
|
@ -21,6 +24,7 @@ class SmartAuthService implements SmsRetriever {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen multiple sms
|
||||||
@override
|
@override
|
||||||
bool get listenForMultipleSms => true;
|
bool get listenForMultipleSms => true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,29 +3,32 @@ import 'package:waveform_recorder/waveform_recorder.dart';
|
||||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||||
|
|
||||||
class VoiceRecorderService with ListenableServiceMixin {
|
class VoiceRecorderService with ListenableServiceMixin {
|
||||||
|
// Recording states
|
||||||
VoiceRecordingState _recordingState = VoiceRecordingState.pending;
|
VoiceRecordingState _recordingState = VoiceRecordingState.pending;
|
||||||
|
|
||||||
VoiceRecordingState get recordingState => _recordingState;
|
VoiceRecordingState get recordingState => _recordingState;
|
||||||
|
|
||||||
|
// Voice recorder controller
|
||||||
final WaveformRecorderController _waveController =
|
final WaveformRecorderController _waveController =
|
||||||
WaveformRecorderController();
|
WaveformRecorderController();
|
||||||
|
|
||||||
WaveformRecorderController get waveController => _waveController;
|
WaveformRecorderController get waveController => _waveController;
|
||||||
|
|
||||||
|
// Start voice recording
|
||||||
Future<void> startRecording() async {
|
Future<void> startRecording() async {
|
||||||
|
|
||||||
await _waveController.startRecording();
|
await _waveController.startRecording();
|
||||||
_recordingState = VoiceRecordingState.recording;
|
_recordingState = VoiceRecordingState.recording;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop voice recording
|
||||||
Future<void> stopRecording() async {
|
Future<void> stopRecording() async {
|
||||||
await _waveController.stopRecording();
|
await _waveController.stopRecording();
|
||||||
_recordingState = VoiceRecordingState.pending;
|
_recordingState = VoiceRecordingState.pending;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get recorded audio
|
||||||
Future<String?> getRecordedAudio() async {
|
Future<String?> getRecordedAudio() async {
|
||||||
final file = _waveController.file;
|
final file = _waveController.file;
|
||||||
print('RECORDED $file');
|
print('RECORDED $file');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
String kBaseUrl = 'https://api.yimaruacademy.com';
|
String kBaseUrl = 'https://api.yimaruacademy.com';
|
||||||
//String baseUrl = 'https://api.yimaru.yaltopia.com';
|
|
||||||
|
|
||||||
String kCoursesUrl = 'courses';
|
String kCoursesUrl = 'courses';
|
||||||
|
|
||||||
|
|
@ -41,6 +40,8 @@ String kProfileStatusUrl = 'is-profile-completed';
|
||||||
|
|
||||||
String kCourseBaseUrl = 'api/v1/course-management';
|
String kCourseBaseUrl = 'api/v1/course-management';
|
||||||
|
|
||||||
|
String kCoursePracticeQuestion = 'api/v1/questions';
|
||||||
|
|
||||||
String kLessonProgressUrl = 'api/v1/progress/videos';
|
String kLessonProgressUrl = 'api/v1/progress/videos';
|
||||||
|
|
||||||
String kGoogleAuthUrl = 'api/v1/auth/google/android';
|
String kGoogleAuthUrl = 'api/v1/auth/google/android';
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,16 @@ const String ksHomeBottomSheetTitle = 'Build Great Apps!';
|
||||||
const String ksSuggestion =
|
const String ksSuggestion =
|
||||||
"15 minutes a day can make you 3x more fluent in 3 month";
|
"15 minutes a day can make you 3x more fluent in 3 month";
|
||||||
|
|
||||||
|
const String ksCategorySubtitle =
|
||||||
|
'Watch expert-led videos and reinforce your knowledge through guided practice activities.';
|
||||||
|
|
||||||
const String ksHomeBottomSheetDescription =
|
const String ksHomeBottomSheetDescription =
|
||||||
'Stacked is built to help you build better apps. Give us a chance and we\'ll prove it to you. Check out stacked.filledstacks.com to learn more';
|
'Stacked is built to help you build better apps. Give us a chance and we\'ll prove it to you. Check out stacked.filledstacks.com to learn more';
|
||||||
|
|
||||||
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 ksCategorySubtitle =
|
|
||||||
'Watch expert-led videos and reinforce your knowledge through guided practice activities.';
|
|
||||||
|
|
||||||
const String ksTerms = """
|
const String ksTerms = """
|
||||||
<p style="color:#9C2C91;font-size:13px;">
|
<p style="color:#9C2C91;font-size:13px;">
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// Login method
|
|
||||||
enum LoginMethod { phone, email, google }
|
|
||||||
|
|
||||||
// Response status
|
// Response status
|
||||||
enum ResponseStatus { success, failure }
|
enum ResponseStatus { success, failure }
|
||||||
|
|
||||||
|
// Login method
|
||||||
|
enum LoginMethod { phone, email, google }
|
||||||
|
|
||||||
// Sign-up method
|
// Sign-up method
|
||||||
enum SignUpMethod { phone, email, google }
|
enum SignUpMethod { phone, email, google }
|
||||||
|
|
||||||
|
|
@ -45,6 +45,7 @@ enum StateObjects {
|
||||||
learnPracticeAnswer,
|
learnPracticeAnswer,
|
||||||
loginWithPhoneNumber,
|
loginWithPhoneNumber,
|
||||||
learnPracticeQuestion,
|
learnPracticeQuestion,
|
||||||
|
coursePracticeQuestion,
|
||||||
|
coursePracticeQuestions,
|
||||||
recordLearnPracticeAnswer,
|
recordLearnPracticeAnswer,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
// Split full name
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'app_colors.dart';
|
import 'app_colors.dart';
|
||||||
|
// 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+'));
|
||||||
|
|
||||||
|
|
@ -22,6 +21,7 @@ Map<String, String> splitFullName(String fullName) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get random color
|
||||||
Color getColor() {
|
Color getColor() {
|
||||||
final generator = Random();
|
final generator = Random();
|
||||||
int random = generator.nextInt(8);
|
int random = generator.nextInt(8);
|
||||||
|
|
@ -44,6 +44,7 @@ Color getColor() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get playable url
|
||||||
String? getPlayableUrl(String url) {
|
String? getPlayableUrl(String url) {
|
||||||
try {
|
try {
|
||||||
// Case 1: /file/d/FILE_ID/view
|
// Case 1: /file/d/FILE_ID/view
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class FormValidator {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form validator
|
// Full name validator
|
||||||
static String? validateFullNameForm(String? value) {
|
static String? validateFullNameForm(String? value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,6 @@ class AssessmentView extends StackedView<AssessmentViewModel> {
|
||||||
List<Widget> _buildScreens() => [
|
List<Widget> _buildScreens() => [
|
||||||
_buildAssessmentIntro(),
|
_buildAssessmentIntro(),
|
||||||
_buildAssessment(),
|
_buildAssessment(),
|
||||||
/*
|
|
||||||
_buildAssessmentFailure(),
|
|
||||||
_buildRetakeAssessment(),
|
|
||||||
_buildResultAnalysis(),
|
|
||||||
_buildAssessmentCompletion(),
|
|
||||||
*/
|
|
||||||
_buildAssessmentResult(),
|
_buildAssessmentResult(),
|
||||||
_buildStartLesson(),
|
_buildStartLesson(),
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import 'package:yimaru_app/ui/common/enmus.dart';
|
||||||
|
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
import '../../../app/app.router.dart';
|
import '../../../app/app.router.dart';
|
||||||
import '../../../models/assessment.dart';
|
import '../../../models/question.dart';
|
||||||
import '../../../services/api_service.dart';
|
import '../../../services/api_service.dart';
|
||||||
import '../../common/app_colors.dart';
|
import '../../common/app_colors.dart';
|
||||||
import '../../common/ui_helpers.dart';
|
import '../../common/ui_helpers.dart';
|
||||||
|
|
@ -24,14 +24,14 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
|
|
||||||
int get currentPage => _currentPage;
|
int get currentPage => _currentPage;
|
||||||
|
|
||||||
final PageController _pageController = PageController();
|
|
||||||
|
|
||||||
PageController get pageController => _pageController;
|
|
||||||
|
|
||||||
int _previousPage = 0;
|
int _previousPage = 0;
|
||||||
|
|
||||||
int get previousPage => _previousPage;
|
int get previousPage => _previousPage;
|
||||||
|
|
||||||
|
final PageController _pageController = PageController();
|
||||||
|
|
||||||
|
PageController get pageController => _pageController;
|
||||||
|
|
||||||
// Assessment
|
// Assessment
|
||||||
int _currentQuestion = 0;
|
int _currentQuestion = 0;
|
||||||
|
|
||||||
|
|
@ -41,9 +41,9 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
|
|
||||||
ProficiencyLevels get proficiencyLevel => _proficiencyLevel;
|
ProficiencyLevels get proficiencyLevel => _proficiencyLevel;
|
||||||
|
|
||||||
List<Assessment> _assessments = [];
|
List<Question> _assessments = [];
|
||||||
|
|
||||||
List<Assessment> get assessments => _assessments;
|
List<Question> get assessments => _assessments;
|
||||||
|
|
||||||
final Map<String, dynamic> _selectedAnswers = {};
|
final Map<String, dynamic> _selectedAnswers = {};
|
||||||
|
|
||||||
|
|
@ -251,13 +251,6 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
Future<void> _getAssessments() async {
|
Future<void> _getAssessments() async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
_assessments = await _apiService.getAssessments();
|
_assessments = await _apiService.getAssessments();
|
||||||
/*
|
|
||||||
for (int i = 0; i < 6; i++) {
|
|
||||||
final generator = Random();
|
|
||||||
int random = generator.nextInt(15);
|
|
||||||
response.add(response[random]);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ class CourseViewModel extends BaseViewModel {
|
||||||
_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));
|
||||||
rebuildUi();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ class CourseCategoryViewModel extends ReactiveViewModel {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
_categories = await _apiService.getCourseCategories();
|
_categories = await _apiService.getCourseCategories();
|
||||||
|
|
||||||
rebuildUi();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,6 @@ class CourseLessonViewModel extends BaseViewModel {
|
||||||
Future<void> _getCourseLessons(int courseId) async {
|
Future<void> _getCourseLessons(int courseId) async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
_courseLessons = await _apiService.getCourseLessons(1);
|
_courseLessons = await _apiService.getCourseLessons(1);
|
||||||
|
|
||||||
if (_courseLessons.isNotEmpty) {
|
|
||||||
rebuildUi();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,19 +92,25 @@ class CoursePracticeView extends StackedView<CoursePracticeViewModel> {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildListView(CoursePracticeViewModel viewModel) => GridView.builder(
|
Widget _buildListView(CoursePracticeViewModel viewModel) => GridView.builder(
|
||||||
itemCount: 6,
|
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
|
itemCount: viewModel.coursePractices.length,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemBuilder: (context, index) =>
|
itemBuilder: (context, index) => _buildCard(
|
||||||
_buildCard(title: viewModel.coursePractices[index].title ?? ''),
|
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.45,
|
childAspectRatio: 1.2,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildCard({required String title}) =>
|
Widget _buildCard({
|
||||||
CoursePracticeCard(title: title);
|
required String title,
|
||||||
|
GestureTapCallback? onTap,
|
||||||
|
}) =>
|
||||||
|
CoursePracticeCard(onTap: onTap, title: title);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
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/models/practice.dart';
|
import 'package:yimaru_app/models/practice.dart';
|
||||||
|
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
|
|
@ -23,19 +24,19 @@ class CoursePracticeViewModel extends BaseViewModel {
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
|
Future<void> navigateToCoursePracticeQuestion(int id) async =>
|
||||||
|
await _navigationService.navigateToCoursePracticeQuestionView(id: id);
|
||||||
|
|
||||||
// Remote api call
|
// Remote api call
|
||||||
|
|
||||||
// Courses
|
// Course practices
|
||||||
Future<void> getCoursePractice(int id) async =>
|
Future<void> getCoursePractice(int id) async =>
|
||||||
await runBusyFuture(_getCoursePractice(id),
|
await runBusyFuture(_getCoursePractice(id),
|
||||||
busyObject: StateObjects.coursePractice);
|
busyObject: StateObjects.coursePractice);
|
||||||
|
|
||||||
Future<void> _getCoursePractice(int id) async {
|
Future<void> _getCoursePractice(int id) async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
Map<String, dynamic> data = {'owner_id': id, 'owner_type': 'SUB_COURSE'};
|
_coursePractices = await _apiService.getCoursePractices(id);
|
||||||
_coursePractices = await _apiService.getCoursePractices(data);
|
|
||||||
|
|
||||||
rebuildUi();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:stacked/stacked_annotations.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_view.form.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/course_practice_question/screens/practice_questions_screen.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/course_practice_question/screens/practice_result_screen.dart';
|
||||||
|
|
||||||
|
import '../../common/validators/form_validator.dart';
|
||||||
|
import 'course_practice_question_viewmodel.dart';
|
||||||
|
|
||||||
|
@FormView(fields: [
|
||||||
|
FormTextField(name: 'answer', validator: FormValidator.validateForm),
|
||||||
|
])
|
||||||
|
class CoursePracticeQuestionView
|
||||||
|
extends StackedView<CoursePracticeQuestionViewModel>
|
||||||
|
with $CoursePracticeQuestionView {
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
const CoursePracticeQuestionView({Key? key, required this.id})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onViewModelReady(CoursePracticeQuestionViewModel viewModel) async {
|
||||||
|
await viewModel.getCoursePracticeQuestions(id);
|
||||||
|
syncFormWithViewModel(viewModel);
|
||||||
|
super.onViewModelReady(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
CoursePracticeQuestionViewModel viewModelBuilder(BuildContext context) =>
|
||||||
|
CoursePracticeQuestionViewModel();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget builder(
|
||||||
|
BuildContext context,
|
||||||
|
CoursePracticeQuestionViewModel viewModel,
|
||||||
|
Widget? child,
|
||||||
|
) =>
|
||||||
|
_buildAssessmentScreensWrapper(viewModel);
|
||||||
|
|
||||||
|
Widget _buildAssessmentScreensWrapper(
|
||||||
|
CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
PopScope(
|
||||||
|
canPop: false,
|
||||||
|
onPopInvokedWithResult: (value, data) => viewModel.previousQuestion(),
|
||||||
|
child: _buildAssessmentScreens(viewModel));
|
||||||
|
|
||||||
|
Widget _buildAssessmentScreens(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
IndexedStack(
|
||||||
|
index: viewModel.currentPage,
|
||||||
|
children: _buildScreens(),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildScreens() => [
|
||||||
|
_buildPracticeQuestionScreen(),
|
||||||
|
_buildPracticeResultScreen(),
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildPracticeQuestionScreen() =>
|
||||||
|
PracticeQuestionsScreen(id: id, answerController: answerController);
|
||||||
|
|
||||||
|
Widget _buildPracticeResultScreen() => const PracticeResultScreen();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// StackedFormGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// ignore_for_file: public_member_api_docs, constant_identifier_names, non_constant_identifier_names,unnecessary_this
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/validators/form_validator.dart';
|
||||||
|
|
||||||
|
const bool _autoTextFieldValidation = true;
|
||||||
|
|
||||||
|
const String AnswerValueKey = 'answer';
|
||||||
|
|
||||||
|
final Map<String, TextEditingController>
|
||||||
|
_CoursePracticeQuestionViewTextEditingControllers = {};
|
||||||
|
|
||||||
|
final Map<String, FocusNode> _CoursePracticeQuestionViewFocusNodes = {};
|
||||||
|
|
||||||
|
final Map<String, String? Function(String?)?>
|
||||||
|
_CoursePracticeQuestionViewTextValidations = {
|
||||||
|
AnswerValueKey: FormValidator.validateForm,
|
||||||
|
};
|
||||||
|
|
||||||
|
mixin $CoursePracticeQuestionView {
|
||||||
|
TextEditingController get answerController =>
|
||||||
|
_getFormTextEditingController(AnswerValueKey);
|
||||||
|
|
||||||
|
FocusNode get answerFocusNode => _getFormFocusNode(AnswerValueKey);
|
||||||
|
|
||||||
|
TextEditingController _getFormTextEditingController(
|
||||||
|
String key, {
|
||||||
|
String? initialValue,
|
||||||
|
}) {
|
||||||
|
if (_CoursePracticeQuestionViewTextEditingControllers.containsKey(key)) {
|
||||||
|
return _CoursePracticeQuestionViewTextEditingControllers[key]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
_CoursePracticeQuestionViewTextEditingControllers[key] =
|
||||||
|
TextEditingController(text: initialValue);
|
||||||
|
return _CoursePracticeQuestionViewTextEditingControllers[key]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusNode _getFormFocusNode(String key) {
|
||||||
|
if (_CoursePracticeQuestionViewFocusNodes.containsKey(key)) {
|
||||||
|
return _CoursePracticeQuestionViewFocusNodes[key]!;
|
||||||
|
}
|
||||||
|
_CoursePracticeQuestionViewFocusNodes[key] = FocusNode();
|
||||||
|
return _CoursePracticeQuestionViewFocusNodes[key]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a listener on every generated controller that calls [model.setData()]
|
||||||
|
/// with the latest textController values
|
||||||
|
void syncFormWithViewModel(FormStateHelper model) {
|
||||||
|
answerController.addListener(() => _updateFormData(model));
|
||||||
|
|
||||||
|
_updateFormData(model, forceValidate: _autoTextFieldValidation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a listener on every generated controller that calls [model.setData()]
|
||||||
|
/// with the latest textController values
|
||||||
|
@Deprecated(
|
||||||
|
'Use syncFormWithViewModel instead.'
|
||||||
|
'This feature was deprecated after 3.1.0.',
|
||||||
|
)
|
||||||
|
void listenToFormUpdated(FormViewModel model) {
|
||||||
|
answerController.addListener(() => _updateFormData(model));
|
||||||
|
|
||||||
|
_updateFormData(model, forceValidate: _autoTextFieldValidation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the formData on the FormViewModel
|
||||||
|
void _updateFormData(FormStateHelper model, {bool forceValidate = false}) {
|
||||||
|
model.setData(
|
||||||
|
model.formValueMap
|
||||||
|
..addAll({
|
||||||
|
AnswerValueKey: answerController.text,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (_autoTextFieldValidation || forceValidate) {
|
||||||
|
updateValidationData(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateFormFields(FormViewModel model) {
|
||||||
|
_updateFormData(model, forceValidate: true);
|
||||||
|
return model.isFormValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls dispose on all the generated controllers and focus nodes
|
||||||
|
void disposeForm() {
|
||||||
|
// The dispose function for a TextEditingController sets all listeners to null
|
||||||
|
|
||||||
|
for (var controller
|
||||||
|
in _CoursePracticeQuestionViewTextEditingControllers.values) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
for (var focusNode in _CoursePracticeQuestionViewFocusNodes.values) {
|
||||||
|
focusNode.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_CoursePracticeQuestionViewTextEditingControllers.clear();
|
||||||
|
_CoursePracticeQuestionViewFocusNodes.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ValueProperties on FormStateHelper {
|
||||||
|
bool get hasAnyValidationMessage => this
|
||||||
|
.fieldsValidationMessages
|
||||||
|
.values
|
||||||
|
.any((validation) => validation != null);
|
||||||
|
|
||||||
|
bool get isFormValid {
|
||||||
|
if (!_autoTextFieldValidation) this.validateForm();
|
||||||
|
|
||||||
|
return !hasAnyValidationMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? get answerValue => this.formValueMap[AnswerValueKey] as String?;
|
||||||
|
|
||||||
|
set answerValue(String? value) {
|
||||||
|
this.setData(
|
||||||
|
this.formValueMap..addAll({AnswerValueKey: value}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (_CoursePracticeQuestionViewTextEditingControllers.containsKey(
|
||||||
|
AnswerValueKey)) {
|
||||||
|
_CoursePracticeQuestionViewTextEditingControllers[AnswerValueKey]?.text =
|
||||||
|
value ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get hasAnswer =>
|
||||||
|
this.formValueMap.containsKey(AnswerValueKey) &&
|
||||||
|
(answerValue?.isNotEmpty ?? false);
|
||||||
|
|
||||||
|
bool get hasAnswerValidationMessage =>
|
||||||
|
this.fieldsValidationMessages[AnswerValueKey]?.isNotEmpty ?? false;
|
||||||
|
|
||||||
|
String? get answerValidationMessage =>
|
||||||
|
this.fieldsValidationMessages[AnswerValueKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Methods on FormStateHelper {
|
||||||
|
setAnswerValidationMessage(String? validationMessage) =>
|
||||||
|
this.fieldsValidationMessages[AnswerValueKey] = validationMessage;
|
||||||
|
|
||||||
|
/// Clears text input fields on the Form
|
||||||
|
void clearForm() {
|
||||||
|
answerValue = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates text input fields on the Form
|
||||||
|
void validateForm() {
|
||||||
|
this.setValidationMessages({
|
||||||
|
AnswerValueKey: getValidationMessage(AnswerValueKey),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the validation message for the given key
|
||||||
|
String? getValidationMessage(String key) {
|
||||||
|
final validatorForKey = _CoursePracticeQuestionViewTextValidations[key];
|
||||||
|
if (validatorForKey == null) return null;
|
||||||
|
|
||||||
|
String? validationMessageForKey = validatorForKey(
|
||||||
|
_CoursePracticeQuestionViewTextEditingControllers[key]!.text,
|
||||||
|
);
|
||||||
|
|
||||||
|
return validationMessageForKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the fieldsValidationMessages on the FormViewModel
|
||||||
|
void updateValidationData(FormStateHelper model) =>
|
||||||
|
model.setValidationMessages({
|
||||||
|
AnswerValueKey: getValidationMessage(AnswerValueKey),
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
import 'package:yimaru_app/models/practice_question.dart';
|
||||||
|
|
||||||
|
import '../../../app/app.locator.dart';
|
||||||
|
import '../../../models/option.dart';
|
||||||
|
import '../../../models/question.dart';
|
||||||
|
import '../../../services/api_service.dart';
|
||||||
|
import '../../../services/status_checker_service.dart';
|
||||||
|
import '../../common/app_colors.dart';
|
||||||
|
import '../../common/enmus.dart';
|
||||||
|
|
||||||
|
class CoursePracticeQuestionViewModel extends FormViewModel {
|
||||||
|
// Dependency injection
|
||||||
|
final _apiService = locator<ApiService>();
|
||||||
|
|
||||||
|
final _dialogService = locator<DialogService>();
|
||||||
|
|
||||||
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
|
|
||||||
|
final _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
|
// In-app navigation
|
||||||
|
int _currentPage = 0;
|
||||||
|
|
||||||
|
int get currentPage => _currentPage;
|
||||||
|
|
||||||
|
int _previousPage = 0;
|
||||||
|
|
||||||
|
int get previousPage => _previousPage;
|
||||||
|
|
||||||
|
final PageController _pageController = PageController();
|
||||||
|
|
||||||
|
PageController get pageController => _pageController;
|
||||||
|
|
||||||
|
// Course practice questions
|
||||||
|
bool _focusAnswer = false;
|
||||||
|
|
||||||
|
bool get focusAnswer => _focusAnswer;
|
||||||
|
|
||||||
|
Question? _currentQuestion;
|
||||||
|
|
||||||
|
Question? get currentQuestion => _currentQuestion;
|
||||||
|
|
||||||
|
List<PracticeQuestion> _coursePracticeQuestions = [];
|
||||||
|
|
||||||
|
List<PracticeQuestion> get coursePracticeQuestions =>
|
||||||
|
_coursePracticeQuestions;
|
||||||
|
|
||||||
|
int _currentQuestionIndex = 0;
|
||||||
|
|
||||||
|
int get currentQuestionIndex => _currentQuestionIndex;
|
||||||
|
|
||||||
|
final Map<String, dynamic> _selectedAnswers = {};
|
||||||
|
|
||||||
|
Map<String, dynamic> get selectedAnswers => _selectedAnswers;
|
||||||
|
|
||||||
|
// Question
|
||||||
|
|
||||||
|
void setAnswerFocus() {
|
||||||
|
_focusAnswer = true;
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSelectedAnswer({required int question, required Option? option}) {
|
||||||
|
bool correct = false;
|
||||||
|
if (option?.isCorrect ?? false) {
|
||||||
|
correct = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = {
|
||||||
|
question.toString(): {
|
||||||
|
'correct': correct,
|
||||||
|
'option': option?.optionText,
|
||||||
|
'answer': _currentQuestion?.options
|
||||||
|
?.firstWhere((e) => e.isCorrect ?? false)
|
||||||
|
.optionText
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_selectedAnswers.addAll(data);
|
||||||
|
|
||||||
|
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
|
||||||
|
void pop() => _navigationService.back();
|
||||||
|
|
||||||
|
// Remote api call
|
||||||
|
|
||||||
|
// Course practice questions
|
||||||
|
Future<void> getCoursePracticeQuestions(int id) async =>
|
||||||
|
await runBusyFuture(_getCoursePracticeQuestions(id),
|
||||||
|
busyObject: StateObjects.coursePracticeQuestions);
|
||||||
|
|
||||||
|
Future<void> _getCoursePracticeQuestions(int id) async {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
_coursePracticeQuestions =
|
||||||
|
await _apiService.getCoursePracticeQuestions(id);
|
||||||
|
if (_coursePracticeQuestions.isNotEmpty) {
|
||||||
|
_currentQuestion = await _apiService.getCoursePracticeQuestion(
|
||||||
|
coursePracticeQuestions.first.questionId ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Course practice question
|
||||||
|
Future<void> getCoursePracticeQuestion(int id) async =>
|
||||||
|
await runBusyFuture(_getCoursePracticeQuestion(id),
|
||||||
|
busyObject: StateObjects.coursePractice);
|
||||||
|
|
||||||
|
Future<void> _getCoursePracticeQuestion(int id) async {
|
||||||
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
_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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/app_colors.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_viewmodel.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/selectable_course_practice_question.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/writing_course_practice_question.dart';
|
||||||
|
|
||||||
|
import 'question_loading_screen.dart';
|
||||||
|
|
||||||
|
class PracticeQuestionsScreen
|
||||||
|
extends ViewModelWidget<CoursePracticeQuestionViewModel> {
|
||||||
|
final int id;
|
||||||
|
final TextEditingController answerController;
|
||||||
|
|
||||||
|
const PracticeQuestionsScreen(
|
||||||
|
{super.key, required this.id, required this.answerController});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(
|
||||||
|
BuildContext context, CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
_buildAssessmentScreens(viewModel);
|
||||||
|
|
||||||
|
Widget _buildAssessmentScreens(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
viewModel.busy(StateObjects.coursePracticeQuestions) ||
|
||||||
|
viewModel.busy(StateObjects.coursePracticeQuestion) ||
|
||||||
|
viewModel.coursePracticeQuestions.isEmpty ||
|
||||||
|
viewModel.currentQuestion == null
|
||||||
|
? _buildPageLoadingIndicator(viewModel)
|
||||||
|
: _buildScaffoldWrapper(viewModel);
|
||||||
|
|
||||||
|
Widget _buildPageLoadingIndicator(
|
||||||
|
CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
QuestionLoadingScreen(
|
||||||
|
onPop: viewModel.coursePracticeQuestions.isEmpty ||
|
||||||
|
viewModel.currentQuestion == null
|
||||||
|
? viewModel.pop
|
||||||
|
: null,
|
||||||
|
isEmpty: viewModel.coursePracticeQuestions.isEmpty ||
|
||||||
|
viewModel.currentQuestion == null
|
||||||
|
? true
|
||||||
|
: false,
|
||||||
|
isLoading: viewModel.busy(StateObjects.coursePracticeQuestions) ||
|
||||||
|
viewModel.busy(StateObjects.coursePracticeQuestion),
|
||||||
|
onTap: () async => await viewModel.getCoursePracticeQuestions(id),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildScaffoldWrapper(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
Scaffold(
|
||||||
|
backgroundColor: kcBackgroundColor,
|
||||||
|
body: _buildScaffold(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildScaffold(CoursePracticeQuestionViewModel viewModel) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: _buildScaffoldChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildScaffoldChildren(
|
||||||
|
CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
|
||||||
|
|
||||||
|
Widget _buildAppBar(CoursePracticeQuestionViewModel viewModel) => LargeAppBar(
|
||||||
|
onClose: viewModel.abort,
|
||||||
|
showLanguageSelection: false,
|
||||||
|
onPop: viewModel.previousQuestion,
|
||||||
|
showBackButton: viewModel.currentQuestionIndex == 0 ? false : true,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildExpandedBody(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
Expanded(child: _buildBodyWrapper(viewModel));
|
||||||
|
|
||||||
|
Widget _buildBodyWrapper(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
child: _buildQuestion(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildQuestion(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
PageView.builder(
|
||||||
|
controller: viewModel.pageController,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: viewModel.coursePracticeQuestions.length,
|
||||||
|
itemBuilder: (cotext, index) =>
|
||||||
|
_buildQuestionType(index: index, viewModel: viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildQuestionType(
|
||||||
|
{required int index,
|
||||||
|
required CoursePracticeQuestionViewModel viewModel}) =>
|
||||||
|
viewModel.currentQuestion?.questionType == 'SHORT_ANSWER'
|
||||||
|
? _buildWritingCoursePracticeQuestion(index)
|
||||||
|
: _buildCoursePracticeQuestionWrapper(index);
|
||||||
|
|
||||||
|
Widget _buildCoursePracticeQuestionWrapper(int index) =>
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: _buildSelectableCoursePracticeQuestion(index),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSelectableCoursePracticeQuestion(int index) =>
|
||||||
|
SelectableCoursePracticeQuestion(index: index);
|
||||||
|
|
||||||
|
Widget _buildWritingCoursePracticeQuestion(int index) =>
|
||||||
|
WritingCoursePracticeQuestion(
|
||||||
|
index: index, answerController: answerController);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_viewmodel.dart';
|
||||||
|
|
||||||
|
import '../../../common/app_colors.dart';
|
||||||
|
import '../../../common/ui_helpers.dart';
|
||||||
|
import '../../../widgets/custom_elevated_button.dart';
|
||||||
|
import '../../../widgets/large_app_bar.dart';
|
||||||
|
|
||||||
|
class PracticeResultScreen
|
||||||
|
extends ViewModelWidget<CoursePracticeQuestionViewModel> {
|
||||||
|
const PracticeResultScreen({super.key});
|
||||||
|
|
||||||
|
void _retake(CoursePracticeQuestionViewModel viewModel) {
|
||||||
|
viewModel.reset();
|
||||||
|
viewModel.goTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(
|
||||||
|
BuildContext context, CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
_buildScaffoldWrapper(viewModel);
|
||||||
|
|
||||||
|
Widget _buildScaffoldWrapper(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
Scaffold(
|
||||||
|
backgroundColor: kcBackgroundColor,
|
||||||
|
body: _buildScaffold(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildScaffold(CoursePracticeQuestionViewModel viewModel) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: _buildScaffoldChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildScaffoldChildren(
|
||||||
|
CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
[
|
||||||
|
_buildAppBar(viewModel),
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildExpandedBody(viewModel)
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildExpandedBody(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
Expanded(child: _buildBodyWrapper(viewModel));
|
||||||
|
|
||||||
|
Widget _buildBodyWrapper(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
child: _buildBody(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildBody(CoursePracticeQuestionViewModel viewModel) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: _buildBodyChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildBodyChildren(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
[_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)];
|
||||||
|
|
||||||
|
Widget _buildUpperColumn(CoursePracticeQuestionViewModel viewModel) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: _buildUpperColumnChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildUpperColumnChildren(
|
||||||
|
CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
[
|
||||||
|
verticalSpaceLarge,
|
||||||
|
_buildIcon(),
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildTitle(),
|
||||||
|
verticalSpaceSmall,
|
||||||
|
_buildSubtitle(),
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildAppBar(CoursePracticeQuestionViewModel viewModel) => LargeAppBar(
|
||||||
|
showBackButton: true,
|
||||||
|
showLanguageSelection: false,
|
||||||
|
onPop: () => viewModel.pop(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildIcon() => SvgPicture.asset(
|
||||||
|
'assets/icons/complete.svg',
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildTitle() => Text(
|
||||||
|
'Practice Completed',
|
||||||
|
style: style25DG600,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSubtitle() => Text(
|
||||||
|
'You’ve finished this practice. Great work!',
|
||||||
|
style: style14MG400,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildLowerColumn(CoursePracticeQuestionViewModel viewModel) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: _buildLowerColumnChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildLowerColumnChildren(
|
||||||
|
CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
[
|
||||||
|
_buildContinueButton(viewModel),
|
||||||
|
verticalSpaceSmall,
|
||||||
|
_buildSkipButtonWrapper(viewModel)
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildContinueButton(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
CustomElevatedButton(
|
||||||
|
height: 55,
|
||||||
|
safe: false,
|
||||||
|
borderRadius: 12,
|
||||||
|
text: 'Practice Again',
|
||||||
|
foregroundColor: kcWhite,
|
||||||
|
onTap: () => _retake(viewModel),
|
||||||
|
backgroundColor: kcPrimaryColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSkipButtonWrapper(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 50),
|
||||||
|
child: _buildSkipButton(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSkipButton(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
CustomElevatedButton(
|
||||||
|
height: 55,
|
||||||
|
text: 'Continue',
|
||||||
|
borderRadius: 12,
|
||||||
|
onTap: viewModel.pop,
|
||||||
|
backgroundColor: kcWhite,
|
||||||
|
borderColor: kcPrimaryColor,
|
||||||
|
foregroundColor: kcPrimaryColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
|
||||||
|
|
||||||
|
import '../../../common/app_colors.dart';
|
||||||
|
import '../../../widgets/large_app_bar.dart';
|
||||||
|
import '../../../widgets/refresh_button.dart';
|
||||||
|
|
||||||
|
class QuestionLoadingScreen extends StatelessWidget {
|
||||||
|
final bool isEmpty;
|
||||||
|
final bool isLoading;
|
||||||
|
final GestureTapCallback? onPop;
|
||||||
|
final GestureTapCallback? onTap;
|
||||||
|
const QuestionLoadingScreen(
|
||||||
|
{super.key,
|
||||||
|
this.onTap,
|
||||||
|
this.onPop,
|
||||||
|
required this.isEmpty,
|
||||||
|
required this.isLoading});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => _buildScaffoldWrapper();
|
||||||
|
|
||||||
|
Widget _buildScaffoldWrapper() => Scaffold(
|
||||||
|
backgroundColor: kcBackgroundColor,
|
||||||
|
body: _buildScaffold(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildScaffold() => Stack(
|
||||||
|
children: [
|
||||||
|
_buildColumn(),
|
||||||
|
if (isEmpty) _buildRefreshButton(),
|
||||||
|
if (isLoading) _buildPageIndicator()
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildColumn() => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: _buildColumnChildren(),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildColumnChildren() => [_buildAppBar(), _buildBody()];
|
||||||
|
|
||||||
|
Widget _buildAppBar() => LargeAppBar(
|
||||||
|
onPop: onPop,
|
||||||
|
showBackButton: true,
|
||||||
|
showLanguageSelection: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildBody() => Expanded(child: Container());
|
||||||
|
|
||||||
|
Widget _buildPageIndicator() => const PageLoadingIndicator();
|
||||||
|
|
||||||
|
Widget _buildRefreshButton() => RefreshButton(onTap: onTap);
|
||||||
|
}
|
||||||
|
|
@ -41,7 +41,6 @@ class CourseSubcategoryViewModel extends BaseViewModel {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
_subcategories = await _apiService.getCourseSubcategories(id);
|
_subcategories = await _apiService.getCourseSubcategories(id);
|
||||||
|
|
||||||
rebuildUi();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ class LearnPracticeViewModel extends ReactiveViewModel {
|
||||||
|
|
||||||
final _voiceRecorderService = locator<VoiceRecorderService>();
|
final _voiceRecorderService = locator<VoiceRecorderService>();
|
||||||
|
|
||||||
|
|
||||||
final _authenticationService = locator<AuthenticationService>();
|
final _authenticationService = locator<AuthenticationService>();
|
||||||
|
|
||||||
LearnPracticeViewModel() {
|
LearnPracticeViewModel() {
|
||||||
|
|
@ -68,7 +67,6 @@ class LearnPracticeViewModel extends ReactiveViewModel {
|
||||||
|
|
||||||
VoiceRecordingState get recordingState => _recordingState;
|
VoiceRecordingState get recordingState => _recordingState;
|
||||||
|
|
||||||
|
|
||||||
// Busy object
|
// Busy object
|
||||||
StateObjects _busyObject = StateObjects.none;
|
StateObjects _busyObject = StateObjects.none;
|
||||||
|
|
||||||
|
|
@ -84,18 +82,15 @@ class LearnPracticeViewModel extends ReactiveViewModel {
|
||||||
|
|
||||||
Map<String, dynamic> get selectedPractice => _selectedPractice;
|
Map<String, dynamic> get selectedPractice => _selectedPractice;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Practice
|
// Practice
|
||||||
void setPractice(Map<String, dynamic> practice) {
|
void setPractice(Map<String, dynamic> practice) {
|
||||||
_selectedPractice = practice;
|
_selectedPractice = practice;
|
||||||
goTo(1);
|
goTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Play practice audio
|
// Play practice audio
|
||||||
Future<void> playQuestionAudio() async => await runBusyFuture(_playQuestionAudio(),
|
Future<void> playQuestionAudio() async =>
|
||||||
|
await runBusyFuture(_playQuestionAudio(),
|
||||||
busyObject: StateObjects.learnPracticeQuestion);
|
busyObject: StateObjects.learnPracticeQuestion);
|
||||||
|
|
||||||
Future<void> _playQuestionAudio() async {
|
Future<void> _playQuestionAudio() async {
|
||||||
|
|
@ -115,7 +110,6 @@ class LearnPracticeViewModel extends ReactiveViewModel {
|
||||||
print('POSITION: $_position');
|
print('POSITION: $_position');
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set busy object
|
// Set busy object
|
||||||
|
|
@ -125,15 +119,17 @@ class LearnPracticeViewModel extends ReactiveViewModel {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Sample audio
|
// Sample audio
|
||||||
Future<void> playSampleAudio() async => await runBusyFuture(_playSampleAudio(),
|
Future<void> playSampleAudio() async =>
|
||||||
|
await runBusyFuture(_playSampleAudio(),
|
||||||
busyObject: StateObjects.learnPracticeSample);
|
busyObject: StateObjects.learnPracticeSample);
|
||||||
Future<void> _playSampleAudio() async {
|
Future<void> _playSampleAudio() async {
|
||||||
setBusyObject(StateObjects.learnPracticeSample);
|
setBusyObject(StateObjects.learnPracticeSample);
|
||||||
await _audioPlayerService.playUrl(_selectedPractice['sample_answer']);
|
await _audioPlayerService.playUrl(_selectedPractice['sample_answer']);
|
||||||
}
|
}
|
||||||
Future<void> pauseSampleAudio()async=> await runBusyFuture(_pauseSampleAudio(),
|
|
||||||
|
Future<void> pauseSampleAudio() async =>
|
||||||
|
await runBusyFuture(_pauseSampleAudio(),
|
||||||
busyObject: StateObjects.learnPracticeSample);
|
busyObject: StateObjects.learnPracticeSample);
|
||||||
|
|
||||||
Future<void> _pauseSampleAudio() async {
|
Future<void> _pauseSampleAudio() async {
|
||||||
|
|
@ -141,31 +137,32 @@ class LearnPracticeViewModel extends ReactiveViewModel {
|
||||||
await _audioPlayerService.pause();
|
await _audioPlayerService.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Recorded audio
|
// Recorded audio
|
||||||
Future<void> playRecordedAudio() async => await runBusyFuture(_playRecordedAudio(),
|
Future<void> playRecordedAudio() async =>
|
||||||
|
await runBusyFuture(_playRecordedAudio(),
|
||||||
busyObject: StateObjects.learnPracticeAnswer);
|
busyObject: StateObjects.learnPracticeAnswer);
|
||||||
|
|
||||||
Future<void> _playRecordedAudio() async {
|
Future<void> _playRecordedAudio() async {
|
||||||
setBusyObject(StateObjects.learnPracticeAnswer);
|
setBusyObject(StateObjects.learnPracticeAnswer);
|
||||||
await _audioPlayerService.playLocal(await _voiceRecorderService.getRecordedAudio() ?? '');
|
await _audioPlayerService
|
||||||
|
.playLocal(await _voiceRecorderService.getRecordedAudio() ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pauseRecordedAudio()async=> await runBusyFuture(_pauseRecordedAudio(),
|
Future<void> pauseRecordedAudio() async =>
|
||||||
|
await runBusyFuture(_pauseRecordedAudio(),
|
||||||
busyObject: StateObjects.learnPracticeAnswer);
|
busyObject: StateObjects.learnPracticeAnswer);
|
||||||
|
|
||||||
|
|
||||||
Future<void> _pauseRecordedAudio() async {
|
Future<void> _pauseRecordedAudio() async {
|
||||||
setBusyObject(StateObjects.learnPracticeAnswer);
|
setBusyObject(StateObjects.learnPracticeAnswer);
|
||||||
await _audioPlayerService.pause();
|
await _audioPlayerService.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Voice recorder
|
// Voice recorder
|
||||||
Future<void> startRecording() async => await runBusyFuture(_startRecording(),busyObject: StateObjects.recordLearnPracticeAnswer );
|
Future<void> startRecording() async => await runBusyFuture(_startRecording(),
|
||||||
|
busyObject: StateObjects.recordLearnPracticeAnswer);
|
||||||
Future<void> _startRecording() async => await _voiceRecorderService.startRecording();
|
|
||||||
|
|
||||||
|
Future<void> _startRecording() async =>
|
||||||
|
await _voiceRecorderService.startRecording();
|
||||||
|
|
||||||
Future<void> stopRecording() async =>
|
Future<void> stopRecording() async =>
|
||||||
await _voiceRecorderService.stopRecording();
|
await _voiceRecorderService.stopRecording();
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,6 @@ class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
|
||||||
child: _buildStartButtonContainer(viewModel),
|
child: _buildStartButtonContainer(viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
Widget _buildStartButtonContainer(LearnPracticeViewModel viewModel) =>
|
Widget _buildStartButtonContainer(LearnPracticeViewModel viewModel) =>
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => _start(viewModel),
|
onTap: () => _start(viewModel),
|
||||||
|
|
@ -170,7 +169,6 @@ class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
|
||||||
child: _buildMicIcon(),
|
child: _buildMicIcon(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
Widget _buildMicIcon() => const Icon(
|
Widget _buildMicIcon() => const Icon(
|
||||||
Icons.mic,
|
Icons.mic,
|
||||||
size: 35,
|
size: 35,
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,15 @@ import 'custom_elevated_button.dart';
|
||||||
|
|
||||||
class CoursePracticeCard extends StatelessWidget {
|
class CoursePracticeCard extends StatelessWidget {
|
||||||
final String title;
|
final String title;
|
||||||
|
final GestureTapCallback? onTap;
|
||||||
|
|
||||||
const CoursePracticeCard({super.key, required this.title});
|
const CoursePracticeCard({super.key, this.onTap, required this.title});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => _buildContainer();
|
Widget build(BuildContext context) => _buildContainer();
|
||||||
|
|
||||||
Widget _buildContainer() => Container(
|
Widget _buildContainer() => Container(
|
||||||
height: 200,
|
// height: 250,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
color: kcPrimaryColor.withValues(alpha: 0.25),
|
color: kcPrimaryColor.withValues(alpha: 0.25),
|
||||||
|
|
@ -27,14 +28,19 @@ class CoursePracticeCard extends StatelessWidget {
|
||||||
|
|
||||||
List<Widget> _buildColumnChildren() => [
|
List<Widget> _buildColumnChildren() => [
|
||||||
verticalSpaceTiny,
|
verticalSpaceTiny,
|
||||||
_buildTitle(),
|
_buildTitleWrapper(),
|
||||||
|
verticalSpaceSmall,
|
||||||
|
_buildStartButtonWrapper(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildStartButtonWrapper()
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Widget _buildTitleWrapper() => Expanded(child: _buildTitle());
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
title,
|
title,
|
||||||
|
maxLines: 2,
|
||||||
style: style18DG700,
|
style: style18DG700,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildStartButtonWrapper() => SizedBox(
|
Widget _buildStartButtonWrapper() => SizedBox(
|
||||||
|
|
@ -42,11 +48,12 @@ class CoursePracticeCard extends StatelessWidget {
|
||||||
child: _buildStartButton(),
|
child: _buildStartButton(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildStartButton() => const CustomElevatedButton(
|
Widget _buildStartButton() => CustomElevatedButton(
|
||||||
height: 50,
|
height: 50,
|
||||||
width: 200,
|
width: 200,
|
||||||
|
onTap: onTap,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
text: 'Start Test',
|
text: 'Start Practice',
|
||||||
foregroundColor: kcWhite,
|
foregroundColor: kcWhite,
|
||||||
backgroundColor: kcPrimaryColor,
|
backgroundColor: kcPrimaryColor,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,13 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
||||||
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
|
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
|
||||||
import 'package:yimaru_app/ui/widgets/learn_practice_answer_card.dart';
|
import 'package:yimaru_app/ui/widgets/learn_practice_answer_card.dart';
|
||||||
|
|
||||||
|
|
||||||
class LearnPracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
|
class LearnPracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
|
||||||
final Map<String, dynamic> data;
|
final Map<String, dynamic> data;
|
||||||
const LearnPracticeResultCard(
|
const LearnPracticeResultCard({super.key, required this.data});
|
||||||
{super.key, required this.data});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context,LearnPracticeViewModel viewModel) => _buildColumnWrapper();
|
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
|
||||||
|
_buildColumnWrapper();
|
||||||
|
|
||||||
Widget _buildColumnWrapper() => SizedBox(
|
Widget _buildColumnWrapper() => SizedBox(
|
||||||
height: 100,
|
height: 100,
|
||||||
|
|
@ -47,15 +46,16 @@ class LearnPracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
|
||||||
Widget _buildSampleResponseWrapper() =>
|
Widget _buildSampleResponseWrapper() =>
|
||||||
Expanded(child: _buildSampleResponse());
|
Expanded(child: _buildSampleResponse());
|
||||||
|
|
||||||
Widget _buildSampleResponse() =>
|
Widget _buildSampleResponse() => const LearnPracticeAnswerCard(
|
||||||
const LearnPracticeAnswerCard(title: 'Sample Answer' ,object: StateObjects.learnPracticeSample,);
|
title: 'Sample Answer',
|
||||||
|
object: StateObjects.learnPracticeSample,
|
||||||
|
);
|
||||||
|
|
||||||
Widget _buildActualResponseWrapper() =>
|
Widget _buildActualResponseWrapper() =>
|
||||||
Expanded(child: _buildActualResponse());
|
Expanded(child: _buildActualResponse());
|
||||||
|
|
||||||
Widget _buildActualResponse() =>
|
Widget _buildActualResponse() => const LearnPracticeAnswerCard(
|
||||||
const LearnPracticeAnswerCard(title: 'Your Answer',object: StateObjects.learnPracticeAnswer,);
|
title: 'Your Answer',
|
||||||
|
object: StateObjects.learnPracticeAnswer,
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ import 'package:yimaru_app/ui/widgets/custom_response_card.dart';
|
||||||
|
|
||||||
class PracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
|
class PracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
|
||||||
final Map<String, dynamic> data;
|
final Map<String, dynamic> data;
|
||||||
const PracticeResultCard(
|
const PracticeResultCard({super.key, required this.data});
|
||||||
{super.key, required this.data});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context,LearnPracticeViewModel viewModel) => _buildColumnWrapper();
|
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
|
||||||
|
_buildColumnWrapper();
|
||||||
|
|
||||||
Widget _buildColumnWrapper() => SizedBox(
|
Widget _buildColumnWrapper() => SizedBox(
|
||||||
height: 100,
|
height: 100,
|
||||||
|
|
|
||||||
113
lib/ui/widgets/selectable_course_practice_question.dart
Normal file
113
lib/ui/widgets/selectable_course_practice_question.dart
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/custom_circular_progress_indicator.dart';
|
||||||
|
|
||||||
|
import '../common/app_colors.dart';
|
||||||
|
import '../common/ui_helpers.dart';
|
||||||
|
import '../views/course_practice_question/course_practice_question_viewmodel.dart';
|
||||||
|
import 'custom_elevated_button.dart';
|
||||||
|
import 'custom_small_radio_button.dart';
|
||||||
|
|
||||||
|
class SelectableCoursePracticeQuestion
|
||||||
|
extends ViewModelWidget<CoursePracticeQuestionViewModel> {
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
const SelectableCoursePracticeQuestion({super.key, required this.index});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(
|
||||||
|
BuildContext context, CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
_buildBodyScroller(viewModel);
|
||||||
|
|
||||||
|
Widget _buildBodyScroller(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: _buildBody(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildBody(CoursePracticeQuestionViewModel viewModel) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: _buildBodyChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildBodyChildren(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
[
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildTitle(viewModel),
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildAnswers(viewModel),
|
||||||
|
_buildContinueButtonWrapper(viewModel)
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildTitle(CoursePracticeQuestionViewModel viewModel) => Text(
|
||||||
|
'Q${index + 1}. ${viewModel.currentQuestion?.questionText} ',
|
||||||
|
style: style16DG600,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildAnswers(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: viewModel.currentQuestion?.options?.length,
|
||||||
|
itemBuilder: (context, inner) => _buildAnswer(
|
||||||
|
title: viewModel.currentQuestion?.options?[inner].optionText ?? '',
|
||||||
|
selected: viewModel.isSelectedAnswer(
|
||||||
|
question: index + 1,
|
||||||
|
answer:
|
||||||
|
viewModel.currentQuestion?.options?[inner].optionText ?? ''),
|
||||||
|
onTap: () => viewModel.setSelectedAnswer(
|
||||||
|
question: index + 1,
|
||||||
|
option: viewModel.currentQuestion?.options?[inner]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildAnswer(
|
||||||
|
{required String title,
|
||||||
|
required bool selected,
|
||||||
|
required GestureTapCallback onTap}) =>
|
||||||
|
CustomSmallRadioButton(
|
||||||
|
title: title,
|
||||||
|
onTap: onTap,
|
||||||
|
selected: selected,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildContinueButtonWrapper(
|
||||||
|
CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 50),
|
||||||
|
child: _buildContinueButtonState(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildContinueButtonState(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
viewModel.busy(StateObjects.coursePracticeQuestion)
|
||||||
|
? _buildProgressIndicator()
|
||||||
|
: _buildContinueButton(viewModel);
|
||||||
|
|
||||||
|
Widget _buildProgressIndicator()=>const CustomCircularProgressIndicator(color: kcPrimaryColor);
|
||||||
|
|
||||||
|
Widget _buildContinueButton(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
CustomElevatedButton(
|
||||||
|
height: 55,
|
||||||
|
borderRadius: 12,
|
||||||
|
foregroundColor: kcWhite,
|
||||||
|
text: viewModel.currentQuestionIndex ==
|
||||||
|
viewModel.coursePracticeQuestions.length - 1
|
||||||
|
? 'Finish'
|
||||||
|
: 'Continue',
|
||||||
|
backgroundColor:
|
||||||
|
viewModel.selectedAnswers.containsKey((index + 1).toString())
|
||||||
|
? kcPrimaryColor
|
||||||
|
: kcPrimaryColor.withOpacity(0.1),
|
||||||
|
onTap: viewModel.selectedAnswers.containsKey((index + 1).toString())
|
||||||
|
? ()async =>await viewModel.nextQuestion(viewModel
|
||||||
|
.coursePracticeQuestions[
|
||||||
|
index + 1 < viewModel.coursePracticeQuestions.length
|
||||||
|
? index + 1
|
||||||
|
: index]
|
||||||
|
.questionId ??
|
||||||
|
0)
|
||||||
|
: null,
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
123
lib/ui/widgets/writing_course_practice_question.dart
Normal file
123
lib/ui/widgets/writing_course_practice_question.dart
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
|
import '../common/app_colors.dart';
|
||||||
|
import '../common/enmus.dart';
|
||||||
|
import '../common/ui_helpers.dart';
|
||||||
|
import '../views/course_practice_question/course_practice_question_view.form.dart';
|
||||||
|
import '../views/course_practice_question/course_practice_question_viewmodel.dart';
|
||||||
|
import 'custom_circular_progress_indicator.dart';
|
||||||
|
import 'custom_elevated_button.dart';
|
||||||
|
|
||||||
|
class WritingCoursePracticeQuestion
|
||||||
|
extends ViewModelWidget<CoursePracticeQuestionViewModel> {
|
||||||
|
final int index;
|
||||||
|
final TextEditingController answerController;
|
||||||
|
|
||||||
|
const WritingCoursePracticeQuestion(
|
||||||
|
{super.key, required this.index, required this.answerController});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(
|
||||||
|
BuildContext context, CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
_buildBody(viewModel);
|
||||||
|
|
||||||
|
Widget _buildBody(CoursePracticeQuestionViewModel viewModel) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: _buildBodyChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildBodyChildren(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
[_buildColumnScroller(viewModel), _buildContinueButtonWrapper(viewModel)];
|
||||||
|
|
||||||
|
Widget _buildColumnScroller(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: _buildUpperColumn(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildUpperColumn(CoursePracticeQuestionViewModel viewModel) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: _buildUpperColumnChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildUpperColumnChildren(
|
||||||
|
CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
[
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildTitle(viewModel),
|
||||||
|
verticalSpaceLarge,
|
||||||
|
_buildQuestionFormField(viewModel),
|
||||||
|
if (viewModel.hasAnswerValidationMessage && viewModel.focusAnswer)
|
||||||
|
verticalSpaceTiny,
|
||||||
|
if (viewModel.hasAnswerValidationMessage && viewModel.focusAnswer)
|
||||||
|
_buildQuestionValidatorWrapper(viewModel),
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildTitle(CoursePracticeQuestionViewModel viewModel) => Text(
|
||||||
|
'Q${index + 1}. ${viewModel.coursePracticeQuestions[index].questionText} ',
|
||||||
|
style: style16DG600,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildQuestionFormField(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
TextFormField(
|
||||||
|
maxLines: 3,
|
||||||
|
controller: answerController,
|
||||||
|
onTap: viewModel.setAnswerFocus,
|
||||||
|
decoration: inputDecoration(
|
||||||
|
hint: 'Enter Your Answer',
|
||||||
|
focus: viewModel.focusAnswer,
|
||||||
|
filled: answerController.text.isNotEmpty),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildQuestionValidatorWrapper(
|
||||||
|
CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
viewModel.hasAnswerValidationMessage
|
||||||
|
? _buildQuestionValidator(viewModel)
|
||||||
|
: Container();
|
||||||
|
|
||||||
|
Widget _buildQuestionValidator(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
Text(
|
||||||
|
viewModel.answerValidationMessage!,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.red,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildContinueButtonWrapper(
|
||||||
|
CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 50),
|
||||||
|
child: _buildContinueButtonState(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildContinueButtonState(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
viewModel.busy(StateObjects.coursePracticeQuestion)
|
||||||
|
? _buildProgressIndicator()
|
||||||
|
: _buildContinueButton(viewModel);
|
||||||
|
|
||||||
|
Widget _buildProgressIndicator()=>const CustomCircularProgressIndicator(color: kcPrimaryColor);
|
||||||
|
|
||||||
|
Widget _buildContinueButton(CoursePracticeQuestionViewModel viewModel) =>
|
||||||
|
CustomElevatedButton(
|
||||||
|
height: 55,
|
||||||
|
borderRadius: 12,
|
||||||
|
foregroundColor: kcWhite,
|
||||||
|
backgroundColor: answerController.text.isNotEmpty
|
||||||
|
? kcPrimaryColor
|
||||||
|
: kcPrimaryColor.withOpacity(0.1),
|
||||||
|
onTap: answerController.text.isNotEmpty
|
||||||
|
? ()async =>await viewModel.nextQuestion(
|
||||||
|
index + 1 < viewModel.coursePracticeQuestions.length
|
||||||
|
? index + 1
|
||||||
|
: index)
|
||||||
|
: null,
|
||||||
|
text: viewModel.currentQuestionIndex ==
|
||||||
|
viewModel.coursePracticeQuestions.length - 1
|
||||||
|
? 'Finish'
|
||||||
|
: 'Continue',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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.0
|
version: 0.1.1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.3 <4.0.0'
|
sdk: '>=3.0.3 <4.0.0'
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
11
test/viewmodels/course_practice_question_viewmodel_test.dart
Normal file
11
test/viewmodels/course_practice_question_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('CoursePracticeQuestionViewModel Tests -', () {
|
||||||
|
setUp(() => registerServices());
|
||||||
|
tearDown(() => locator.reset());
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user