fix(onboarding): Change onboarding assessment implementation

This commit is contained in:
BisratHailu 2026-04-30 10:57:45 +03:00
parent 85a09151e3
commit 6a5e531a68
71 changed files with 1076 additions and 792 deletions

View File

@ -25,7 +25,6 @@ import 'package:yimaru_app/services/secure_storage_service.dart';
import 'package:yimaru_app/services/dio_service.dart'; import 'package:yimaru_app/services/dio_service.dart';
import 'package:yimaru_app/services/status_checker_service.dart'; import 'package:yimaru_app/services/status_checker_service.dart';
import 'package:yimaru_app/ui/views/welcome/welcome_view.dart'; import 'package:yimaru_app/ui/views/welcome/welcome_view.dart';
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart';
import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart'; import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart';
import 'package:yimaru_app/services/permission_handler_service.dart'; import 'package:yimaru_app/services/permission_handler_service.dart';
import 'package:yimaru_app/services/image_picker_service.dart'; import 'package:yimaru_app/services/image_picker_service.dart';
@ -52,6 +51,7 @@ import 'package:yimaru_app/ui/views/course_practice_question/course_practice_que
import 'package:yimaru_app/services/in_app_update_service.dart'; import 'package:yimaru_app/services/in_app_update_service.dart';
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'; import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart'; import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart';
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart';
// @stacked-import // @stacked-import
@StackedApp( @StackedApp(
@ -74,7 +74,6 @@ import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart';
MaterialRoute(page: LoginView), MaterialRoute(page: LoginView),
MaterialRoute(page: LearnModuleView), MaterialRoute(page: LearnModuleView),
MaterialRoute(page: WelcomeView), MaterialRoute(page: WelcomeView),
MaterialRoute(page: AssessmentView),
MaterialRoute(page: LearnLessonView), MaterialRoute(page: LearnLessonView),
MaterialRoute(page: ForgetPasswordView), MaterialRoute(page: ForgetPasswordView),
MaterialRoute(page: LearnLessonDetailView), MaterialRoute(page: LearnLessonDetailView),
@ -91,6 +90,7 @@ import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart';
MaterialRoute(page: CoursePracticeQuestionView), MaterialRoute(page: CoursePracticeQuestionView),
MaterialRoute(page: LearnProgramView), MaterialRoute(page: LearnProgramView),
MaterialRoute(page: LearnCourseView), MaterialRoute(page: LearnCourseView),
MaterialRoute(page: AssessmentView),
// @stacked-route // @stacked-route
], ],
dependencies: [ dependencies: [

View File

@ -20,43 +20,43 @@ import 'package:yimaru_app/models/subcategory.dart' as _i45;
import 'package:yimaru_app/ui/common/enmus.dart' as _i41; import 'package:yimaru_app/ui/common/enmus.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 _i20; import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i36;
import 'package:yimaru_app/ui/views/call_support/call_support_view.dart' import 'package:yimaru_app/ui/views/call_support/call_support_view.dart'
as _i12; as _i12;
import 'package:yimaru_app/ui/views/course/course_view.dart' as _i33; import 'package:yimaru_app/ui/views/course/course_view.dart' as _i32;
import 'package:yimaru_app/ui/views/course_category/course_category_view.dart' import 'package:yimaru_app/ui/views/course_category/course_category_view.dart'
as _i27;
import 'package:yimaru_app/ui/views/course_lesson/course_lesson_view.dart'
as _i29;
import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart'
as _i30;
import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart'
as _i26; as _i26;
import 'package:yimaru_app/ui/views/course_practice/course_practice_view.dart' import 'package:yimaru_app/ui/views/course_lesson/course_lesson_view.dart'
as _i28;
import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart'
as _i29;
import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart'
as _i25; as _i25;
import 'package:yimaru_app/ui/views/course_practice/course_practice_view.dart'
as _i24;
import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_view.dart' import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_view.dart'
as _i34; as _i33;
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 _i32; as _i31;
import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7; import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7;
import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart' as _i31; import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart' as _i30;
import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i28; import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i27;
import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart' import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart'
as _i22; as _i21;
import 'package:yimaru_app/ui/views/home/home_view.dart' as _i2; import 'package:yimaru_app/ui/views/home/home_view.dart' as _i2;
import 'package:yimaru_app/ui/views/language/language_view.dart' as _i13; import 'package:yimaru_app/ui/views/language/language_view.dart' as _i13;
import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart' import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart'
as _i36; as _i35;
import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart' import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart'
as _i21; as _i20;
import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart' import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart'
as _i23; as _i22;
import 'package:yimaru_app/ui/views/learn_module/learn_module_view.dart' import 'package:yimaru_app/ui/views/learn_module/learn_module_view.dart'
as _i18; as _i18;
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart' import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart'
as _i24; as _i23;
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart' import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'
as _i35; as _i34;
import 'package:yimaru_app/ui/views/login/login_view.dart' as _i17; import 'package:yimaru_app/ui/views/login/login_view.dart' as _i17;
import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i3; import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i3;
import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart' import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart'
@ -111,8 +111,6 @@ class Routes {
static const welcomeView = '/welcome-view'; static const welcomeView = '/welcome-view';
static const assessmentView = '/assessment-view';
static const learnLessonView = '/learn-lesson-view'; static const learnLessonView = '/learn-lesson-view';
static const forgetPasswordView = '/forget-password-view'; static const forgetPasswordView = '/forget-password-view';
@ -145,6 +143,8 @@ class Routes {
static const learnCourseView = '/learn-course-view'; static const learnCourseView = '/learn-course-view';
static const assessmentView = '/assessment-view';
static const all = <String>{ static const all = <String>{
homeView, homeView,
onboardingView, onboardingView,
@ -164,7 +164,6 @@ class Routes {
loginView, loginView,
learnModuleView, learnModuleView,
welcomeView, welcomeView,
assessmentView,
learnLessonView, learnLessonView,
forgetPasswordView, forgetPasswordView,
learnLessonDetailView, learnLessonDetailView,
@ -181,6 +180,7 @@ class Routes {
coursePracticeQuestionView, coursePracticeQuestionView,
learnProgramView, learnProgramView,
learnCourseView, learnCourseView,
assessmentView,
}; };
} }
@ -258,73 +258,73 @@ class StackedRouter extends _i1.RouterBase {
Routes.welcomeView, Routes.welcomeView,
page: _i19.WelcomeView, page: _i19.WelcomeView,
), ),
_i1.RouteDef(
Routes.assessmentView,
page: _i20.AssessmentView,
),
_i1.RouteDef( _i1.RouteDef(
Routes.learnLessonView, Routes.learnLessonView,
page: _i21.LearnLessonView, page: _i20.LearnLessonView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.forgetPasswordView, Routes.forgetPasswordView,
page: _i22.ForgetPasswordView, page: _i21.ForgetPasswordView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.learnLessonDetailView, Routes.learnLessonDetailView,
page: _i23.LearnLessonDetailView, page: _i22.LearnLessonDetailView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.learnPracticeView, Routes.learnPracticeView,
page: _i24.LearnPracticeView, page: _i23.LearnPracticeView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.coursePracticeView, Routes.coursePracticeView,
page: _i25.CoursePracticeView, page: _i24.CoursePracticeView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.coursePaymentView, Routes.coursePaymentView,
page: _i26.CoursePaymentView, page: _i25.CoursePaymentView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.courseCategoryView, Routes.courseCategoryView,
page: _i27.CourseCategoryView, page: _i26.CourseCategoryView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.failureView, Routes.failureView,
page: _i28.FailureView, page: _i27.FailureView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.courseLessonView, Routes.courseLessonView,
page: _i29.CourseLessonView, page: _i28.CourseLessonView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.courseLessonDetailView, Routes.courseLessonDetailView,
page: _i30.CourseLessonDetailView, page: _i29.CourseLessonDetailView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.duolingoView, Routes.duolingoView,
page: _i31.DuolingoView, page: _i30.DuolingoView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.courseSubcategoryView, Routes.courseSubcategoryView,
page: _i32.CourseSubcategoryView, page: _i31.CourseSubcategoryView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.courseView, Routes.courseView,
page: _i33.CourseView, page: _i32.CourseView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.coursePracticeQuestionView, Routes.coursePracticeQuestionView,
page: _i34.CoursePracticeQuestionView, page: _i33.CoursePracticeQuestionView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.learnProgramView, Routes.learnProgramView,
page: _i35.LearnProgramView, page: _i34.LearnProgramView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.learnCourseView, Routes.learnCourseView,
page: _i36.LearnCourseView, page: _i35.LearnCourseView,
),
_i1.RouteDef(
Routes.assessmentView,
page: _i36.AssessmentView,
), ),
]; ];
@ -490,143 +490,143 @@ class StackedRouter extends _i1.RouterBase {
settings: data, settings: data,
); );
}, },
_i20.AssessmentView: (data) { _i20.LearnLessonView: (data) {
final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i20.AssessmentView(key: args.key, data: args.data),
settings: data,
);
},
_i21.LearnLessonView: (data) {
final args = data.getArgs<LearnLessonViewArguments>(nullOk: false); final args = data.getArgs<LearnLessonViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i21.LearnLessonView(key: args.key, module: args.module), _i20.LearnLessonView(key: args.key, module: args.module),
settings: data, settings: data,
); );
}, },
_i22.ForgetPasswordView: (data) { _i21.ForgetPasswordView: (data) {
final args = data.getArgs<ForgetPasswordViewArguments>( final args = data.getArgs<ForgetPasswordViewArguments>(
orElse: () => const ForgetPasswordViewArguments(), orElse: () => const ForgetPasswordViewArguments(),
); );
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i22.ForgetPasswordView(key: args.key), builder: (context) => _i21.ForgetPasswordView(key: args.key),
settings: data, settings: data,
); );
}, },
_i23.LearnLessonDetailView: (data) { _i22.LearnLessonDetailView: (data) {
final args = data.getArgs<LearnLessonDetailViewArguments>(nullOk: false); final args = data.getArgs<LearnLessonDetailViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i23.LearnLessonDetailView(key: args.key, lesson: args.lesson), _i22.LearnLessonDetailView(key: args.key, lesson: args.lesson),
settings: data, settings: data,
); );
}, },
_i24.LearnPracticeView: (data) { _i23.LearnPracticeView: (data) {
final args = data.getArgs<LearnPracticeViewArguments>(nullOk: false); final args = data.getArgs<LearnPracticeViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i24.LearnPracticeView( builder: (context) => _i23.LearnPracticeView(
key: args.key, id: args.id, practice: args.practice), key: args.key, id: args.id, practice: args.practice),
settings: data, settings: data,
); );
}, },
_i25.CoursePracticeView: (data) { _i24.CoursePracticeView: (data) {
final args = data.getArgs<CoursePracticeViewArguments>(nullOk: false); final args = data.getArgs<CoursePracticeViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i25.CoursePracticeView(key: args.key, id: args.id), _i24.CoursePracticeView(key: args.key, id: args.id),
settings: data, settings: data,
); );
}, },
_i26.CoursePaymentView: (data) { _i25.CoursePaymentView: (data) {
final args = data.getArgs<CoursePaymentViewArguments>(nullOk: false); final args = data.getArgs<CoursePaymentViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i26.CoursePaymentView(key: args.key, course: args.course), _i25.CoursePaymentView(key: args.key, course: args.course),
settings: data, settings: data,
); );
}, },
_i27.CourseCategoryView: (data) { _i26.CourseCategoryView: (data) {
final args = data.getArgs<CourseCategoryViewArguments>( final args = data.getArgs<CourseCategoryViewArguments>(
orElse: () => const CourseCategoryViewArguments(), orElse: () => const CourseCategoryViewArguments(),
); );
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i27.CourseCategoryView(key: args.key), builder: (context) => _i26.CourseCategoryView(key: args.key),
settings: data, settings: data,
); );
}, },
_i28.FailureView: (data) { _i27.FailureView: (data) {
final args = data.getArgs<FailureViewArguments>(nullOk: false); final args = data.getArgs<FailureViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) => _i27.FailureView(
_i28.FailureView(key: args.key, label: args.label), key: args.key, onTap: args.onTap, label: args.label),
settings: data, settings: data,
); );
}, },
_i29.CourseLessonView: (data) { _i28.CourseLessonView: (data) {
final args = data.getArgs<CourseLessonViewArguments>(nullOk: false); final args = data.getArgs<CourseLessonViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i29.CourseLessonView(key: args.key, course: args.course), _i28.CourseLessonView(key: args.key, course: args.course),
settings: data, settings: data,
); );
}, },
_i30.CourseLessonDetailView: (data) { _i29.CourseLessonDetailView: (data) {
final args = data.getArgs<CourseLessonDetailViewArguments>(nullOk: false); final args = data.getArgs<CourseLessonDetailViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i30.CourseLessonDetailView(key: args.key, lesson: args.lesson), _i29.CourseLessonDetailView(key: args.key, lesson: args.lesson),
settings: data, settings: data,
); );
}, },
_i31.DuolingoView: (data) { _i30.DuolingoView: (data) {
final args = data.getArgs<DuolingoViewArguments>( final args = data.getArgs<DuolingoViewArguments>(
orElse: () => const DuolingoViewArguments(), orElse: () => const DuolingoViewArguments(),
); );
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i31.DuolingoView(key: args.key), builder: (context) => _i30.DuolingoView(key: args.key),
settings: data, settings: data,
); );
}, },
_i32.CourseSubcategoryView: (data) { _i31.CourseSubcategoryView: (data) {
final args = data.getArgs<CourseSubcategoryViewArguments>(nullOk: false); final args = data.getArgs<CourseSubcategoryViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i32.CourseSubcategoryView(key: args.key, category: args.category), _i31.CourseSubcategoryView(key: args.key, category: args.category),
settings: data, settings: data,
); );
}, },
_i33.CourseView: (data) { _i32.CourseView: (data) {
final args = data.getArgs<CourseViewArguments>(nullOk: false); final args = data.getArgs<CourseViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i33.CourseView(key: args.key, subcategory: args.subcategory), _i32.CourseView(key: args.key, subcategory: args.subcategory),
settings: data, settings: data,
); );
}, },
_i34.CoursePracticeQuestionView: (data) { _i33.CoursePracticeQuestionView: (data) {
final args = final args =
data.getArgs<CoursePracticeQuestionViewArguments>(nullOk: false); data.getArgs<CoursePracticeQuestionViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i34.CoursePracticeQuestionView(key: args.key, id: args.id), _i33.CoursePracticeQuestionView(key: args.key, id: args.id),
settings: data, settings: data,
); );
}, },
_i35.LearnProgramView: (data) { _i34.LearnProgramView: (data) {
final args = data.getArgs<LearnProgramViewArguments>( final args = data.getArgs<LearnProgramViewArguments>(
orElse: () => const LearnProgramViewArguments(), orElse: () => const LearnProgramViewArguments(),
); );
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i35.LearnProgramView(key: args.key), builder: (context) => _i34.LearnProgramView(key: args.key),
settings: data, settings: data,
); );
}, },
_i36.LearnCourseView: (data) { _i35.LearnCourseView: (data) {
final args = data.getArgs<LearnCourseViewArguments>(nullOk: false); final args = data.getArgs<LearnCourseViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i36.LearnCourseView(key: args.key, id: args.id), builder: (context) => _i35.LearnCourseView(key: args.key, id: args.id),
settings: data,
);
},
_i36.AssessmentView: (data) {
final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i36.AssessmentView(key: args.key, data: args.data),
settings: data, settings: data,
); );
}, },
@ -1045,33 +1045,6 @@ class WelcomeViewArguments {
} }
} }
class AssessmentViewArguments {
const AssessmentViewArguments({
this.key,
required this.data,
});
final _i37.Key? key;
final Map<String, dynamic> data;
@override
String toString() {
return '{"key": "$key", "data": "$data"}';
}
@override
bool operator ==(covariant AssessmentViewArguments other) {
if (identical(this, other)) return true;
return other.key == key && other.data == data;
}
@override
int get hashCode {
return key.hashCode ^ data.hashCode;
}
}
class LearnLessonViewArguments { class LearnLessonViewArguments {
const LearnLessonViewArguments({ const LearnLessonViewArguments({
this.key, this.key,
@ -1257,27 +1230,30 @@ class CourseCategoryViewArguments {
class FailureViewArguments { class FailureViewArguments {
const FailureViewArguments({ const FailureViewArguments({
this.key, this.key,
required this.onTap,
required this.label, required this.label,
}); });
final _i37.Key? key; final _i37.Key? key;
final void Function() onTap;
final String label; final String label;
@override @override
String toString() { String toString() {
return '{"key": "$key", "label": "$label"}'; return '{"key": "$key", "onTap": "$onTap", "label": "$label"}';
} }
@override @override
bool operator ==(covariant FailureViewArguments other) { bool operator ==(covariant FailureViewArguments other) {
if (identical(this, other)) return true; if (identical(this, other)) return true;
return other.key == key && other.label == label; return other.key == key && other.onTap == onTap && other.label == label;
} }
@override @override
int get hashCode { int get hashCode {
return key.hashCode ^ label.hashCode; return key.hashCode ^ onTap.hashCode ^ label.hashCode;
} }
} }
@ -1487,6 +1463,33 @@ class LearnCourseViewArguments {
} }
} }
class AssessmentViewArguments {
const AssessmentViewArguments({
this.key,
required this.data,
});
final _i37.Key? key;
final Map<String, dynamic> data;
@override
String toString() {
return '{"key": "$key", "data": "$data"}';
}
@override
bool operator ==(covariant AssessmentViewArguments other) {
if (identical(this, other)) return true;
return other.key == key && other.data == data;
}
@override
int get hashCode {
return key.hashCode ^ data.hashCode;
}
}
extension NavigatorStateExtension on _i46.NavigationService { extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> navigateToHomeView({ Future<dynamic> navigateToHomeView({
_i37.Key? key, _i37.Key? key,
@ -1778,23 +1781,6 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> navigateToAssessmentView({
_i37.Key? key,
required Map<String, dynamic> data,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return navigateTo<dynamic>(Routes.assessmentView,
arguments: AssessmentViewArguments(key: key, data: data),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> navigateToLearnLessonView({ Future<dynamic> navigateToLearnLessonView({
_i37.Key? key, _i37.Key? key,
required _i39.LearnModule module, required _i39.LearnModule module,
@ -1916,6 +1902,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> navigateToFailureView({ Future<dynamic> navigateToFailureView({
_i37.Key? key, _i37.Key? key,
required void Function() onTap,
required String label, required String label,
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
@ -1924,7 +1911,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition, transition,
}) async { }) async {
return navigateTo<dynamic>(Routes.failureView, return navigateTo<dynamic>(Routes.failureView,
arguments: FailureViewArguments(key: key, label: label), arguments: FailureViewArguments(key: key, onTap: onTap, label: label),
id: routerId, id: routerId,
preventDuplicates: preventDuplicates, preventDuplicates: preventDuplicates,
parameters: parameters, parameters: parameters,
@ -2065,6 +2052,23 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> navigateToAssessmentView({
_i37.Key? key,
required Map<String, dynamic> data,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return navigateTo<dynamic>(Routes.assessmentView,
arguments: AssessmentViewArguments(key: key, data: data),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithHomeView({ Future<dynamic> replaceWithHomeView({
_i37.Key? key, _i37.Key? key,
int? routerId, int? routerId,
@ -2355,23 +2359,6 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> replaceWithAssessmentView({
_i37.Key? key,
required Map<String, dynamic> data,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return replaceWith<dynamic>(Routes.assessmentView,
arguments: AssessmentViewArguments(key: key, data: data),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithLearnLessonView({ Future<dynamic> replaceWithLearnLessonView({
_i37.Key? key, _i37.Key? key,
required _i39.LearnModule module, required _i39.LearnModule module,
@ -2493,6 +2480,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> replaceWithFailureView({ Future<dynamic> replaceWithFailureView({
_i37.Key? key, _i37.Key? key,
required void Function() onTap,
required String label, required String label,
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
@ -2501,7 +2489,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition, transition,
}) async { }) async {
return replaceWith<dynamic>(Routes.failureView, return replaceWith<dynamic>(Routes.failureView,
arguments: FailureViewArguments(key: key, label: label), arguments: FailureViewArguments(key: key, onTap: onTap, label: label),
id: routerId, id: routerId,
preventDuplicates: preventDuplicates, preventDuplicates: preventDuplicates,
parameters: parameters, parameters: parameters,
@ -2641,4 +2629,21 @@ extension NavigatorStateExtension on _i46.NavigationService {
parameters: parameters, parameters: parameters,
transition: transition); transition: transition);
} }
Future<dynamic> replaceWithAssessmentView({
_i37.Key? key,
required Map<String, dynamic> data,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return replaceWith<dynamic>(Routes.assessmentView,
arguments: AssessmentViewArguments(key: key, data: data),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
} }

View File

@ -0,0 +1,37 @@
import 'package:json_annotation/json_annotation.dart';
part 'assessment.g.dart';
@JsonSerializable()
class Assessment {
final int? id;
final String? title;
final String? status;
final String? description;
@JsonKey(name: 'set_type')
final String? setType;
@JsonKey(name: 'passing_score')
final int? passingScore;
@JsonKey(name: 'shuffle_questions')
final bool? shuffleQuestions;
const Assessment(
{this.id,
this.title,
this.status,
this.setType,
this.description,
this.passingScore,
this.shuffleQuestions});
factory Assessment.fromJson(Map<String, dynamic> json) =>
_$AssessmentFromJson(json);
Map<String, dynamic> toJson() => _$AssessmentToJson(this);
}

View File

@ -0,0 +1,28 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'assessment.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Assessment _$AssessmentFromJson(Map<String, dynamic> json) => Assessment(
id: (json['id'] as num?)?.toInt(),
title: json['title'] as String?,
status: json['status'] as String?,
setType: json['set_type'] as String?,
description: json['description'] as String?,
passingScore: (json['passing_score'] as num?)?.toInt(),
shuffleQuestions: json['shuffle_questions'] as bool?,
);
Map<String, dynamic> _$AssessmentToJson(Assessment instance) =>
<String, dynamic>{
'id': instance.id,
'title': instance.title,
'status': instance.status,
'description': instance.description,
'set_type': instance.setType,
'passing_score': instance.passingScore,
'shuffle_questions': instance.shuffleQuestions,
};

View File

@ -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 'question.g.dart'; part 'assessment_question.g.dart';
@JsonSerializable() @JsonSerializable()
class Question { class AssessmentQuestion {
final int? id; final int? id;
final int? points; final int? points;
@ -18,21 +18,17 @@ class Question {
@JsonKey(name: 'question_text') @JsonKey(name: 'question_text')
final String? questionText; final String? questionText;
@JsonKey(name: 'difficulty_level') const AssessmentQuestion({
final String? difficultyLevel;
const Question({
this.id, this.id,
this.points, this.points,
this.status, this.status,
this.options, this.options,
this.questionText, this.questionText,
this.questionType, this.questionType,
this.difficultyLevel,
}); });
factory Question.fromJson(Map<String, dynamic> json) => factory AssessmentQuestion.fromJson(Map<String, dynamic> json) =>
_$QuestionFromJson(json); _$AssessmentQuestionFromJson(json);
Map<String, dynamic> toJson() => _$QuestionToJson(this); Map<String, dynamic> toJson() => _$AssessmentQuestionToJson(this);
} }

View File

@ -1,12 +1,13 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'question.dart'; part of 'assessment_question.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
Question _$QuestionFromJson(Map<String, dynamic> json) => Question( AssessmentQuestion _$AssessmentQuestionFromJson(Map<String, dynamic> json) =>
AssessmentQuestion(
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?,
@ -15,15 +16,14 @@ Question _$QuestionFromJson(Map<String, dynamic> json) => Question(
.toList(), .toList(),
questionText: json['question_text'] as String?, questionText: json['question_text'] as String?,
questionType: json['question_type'] as String?, questionType: json['question_type'] as String?,
difficultyLevel: json['difficulty_level'] as String?,
); );
Map<String, dynamic> _$QuestionToJson(Question instance) => <String, dynamic>{ Map<String, dynamic> _$AssessmentQuestionToJson(AssessmentQuestion instance) =>
<String, dynamic>{
'id': instance.id, 'id': instance.id,
'points': instance.points, 'points': instance.points,
'status': instance.status, 'status': instance.status,
'options': instance.options, 'options': instance.options,
'question_type': instance.questionType, 'question_type': instance.questionType,
'question_text': instance.questionText, 'question_text': instance.questionText,
'difficulty_level': instance.difficultyLevel,
}; };

View File

@ -11,7 +11,10 @@ class Option {
@JsonKey(name: 'option_text') @JsonKey(name: 'option_text')
final String? optionText; final String? optionText;
const Option({this.id, this.optionText, this.isCorrect}); @JsonKey(name: 'option_order')
final int? optionOrder;
const Option({this.id, this.optionText, this.isCorrect, this.optionOrder});
factory Option.fromJson(Map<String, dynamic> json) => _$OptionFromJson(json); factory Option.fromJson(Map<String, dynamic> json) => _$OptionFromJson(json);

View File

@ -10,10 +10,12 @@ Option _$OptionFromJson(Map<String, dynamic> json) => Option(
id: (json['id'] as num?)?.toInt(), id: (json['id'] as num?)?.toInt(),
optionText: json['option_text'] as String?, optionText: json['option_text'] as String?,
isCorrect: json['is_correct'] as bool?, isCorrect: json['is_correct'] as bool?,
optionOrder: (json['option_order'] as num?)?.toInt(),
); );
Map<String, dynamic> _$OptionToJson(Option instance) => <String, dynamic>{ Map<String, dynamic> _$OptionToJson(Option instance) => <String, dynamic>{
'id': instance.id, 'id': instance.id,
'is_correct': instance.isCorrect, 'is_correct': instance.isCorrect,
'option_text': instance.optionText, 'option_text': instance.optionText,
'option_order': instance.optionOrder,
}; };

View File

@ -3,7 +3,7 @@ import 'package:yimaru_app/models/learn_lesson.dart';
import 'package:yimaru_app/models/learn_practice.dart'; import 'package:yimaru_app/models/learn_practice.dart';
import 'package:yimaru_app/models/learn_program.dart'; import 'package:yimaru_app/models/learn_program.dart';
import 'package:yimaru_app/models/level.dart'; import 'package:yimaru_app/models/level.dart';
import 'package:yimaru_app/models/question.dart'; import 'package:yimaru_app/models/assessment_question.dart';
import 'package:yimaru_app/models/subcategory.dart'; import 'package:yimaru_app/models/subcategory.dart';
import 'package:yimaru_app/models/category.dart'; import 'package:yimaru_app/models/category.dart';
import 'package:yimaru_app/models/course_lesson.dart'; import 'package:yimaru_app/models/course_lesson.dart';
@ -20,6 +20,7 @@ import '../models/learn_module.dart';
import '../models/learn_question.dart'; import '../models/learn_question.dart';
import '../models/lesson.dart'; import '../models/lesson.dart';
import '../models/module.dart'; import '../models/module.dart';
import '../models/assessment.dart';
import '../models/submodule.dart'; import '../models/submodule.dart';
import '../ui/common/enmus.dart'; import '../ui/common/enmus.dart';
@ -354,23 +355,47 @@ class ApiService {
} }
} }
// Get assessments // Get assessment question sets
Future<List<Question>> getAssessments() async { Future<List<Assessment>> getAssessments() async {
try { try {
List<Question> assessments = []; List<Assessment> assessments = [];
final Response response = final Response response = await _service.dio.get(
await _service.dio.get('$kBaseUrl/$kAssessmentsUrl'); '$kBaseUrl/api/$kApiVersionUrl/$kQuestionSetsUrl?set_type=INITIAL_ASSESSMENT&limit=10&offset=0');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['question_sets'] as List;
assessments = decodedData.map(
(e) {
return Assessment.fromJson(e);
},
).toList();
return assessments;
}
return [];
} catch (e) {
return [];
}
}
// Get assessment questions
Future<List<AssessmentQuestion>> getAssessmentQuestions(int id) async {
try {
List<AssessmentQuestion> questions = [];
final Response response = await _service.dio.get(
'$kBaseUrl/api/$kApiVersionUrl/$kQuestionSetsUrl/$id/$kQuestionsUrl');
if (response.statusCode == 200) { if (response.statusCode == 200) {
var data = response.data; var data = response.data;
var decodedData = data['data'] as List; var decodedData = data['data'] as List;
assessments = decodedData.map( questions = decodedData.map(
(e) { (e) {
return Question.fromJson(e); return AssessmentQuestion.fromJson(e);
}, },
).toList(); ).toList();
return assessments; return questions;
} }
return []; return [];
} catch (e) { } catch (e) {
@ -741,9 +766,9 @@ class ApiService {
} }
// Get course practic questions // Get course practic questions
Future<List<Question>> getCoursePracticeQuestions(int id) async { Future<List<AssessmentQuestion>> getCoursePracticeQuestions(int id) async {
try { try {
List<Question> coursePracticeQuestions = []; List<AssessmentQuestion> coursePracticeQuestions = [];
final Response response = await _service.dio final Response response = await _service.dio
.get('$kBaseUrl/$kPracticeBaseUrl/$id/$kCoursePracticeQuestions'); .get('$kBaseUrl/$kPracticeBaseUrl/$id/$kCoursePracticeQuestions');
@ -753,7 +778,7 @@ class ApiService {
var decodedData = data['data'] as List; var decodedData = data['data'] as List;
coursePracticeQuestions = decodedData.map( coursePracticeQuestions = decodedData.map(
(e) { (e) {
return Question.fromJson(e); return AssessmentQuestion.fromJson(e);
}, },
).toList(); ).toList();
return coursePracticeQuestions; return coursePracticeQuestions;
@ -765,13 +790,14 @@ class ApiService {
} }
// Get course practice question // Get course practice question
Future<Question?> getCoursePracticeQuestion(int id) async { Future<AssessmentQuestion?> getCoursePracticeQuestion(int id) async {
try { try {
final Response response = final Response response =
await _service.dio.get('$kBaseUrl/$kCoursePracticeQuestion/$id'); await _service.dio.get('$kBaseUrl/$kCoursePracticeQuestion/$id');
if (response.statusCode == 200) { if (response.statusCode == 200) {
Question question = Question.fromJson(response.data['data']); AssessmentQuestion question =
AssessmentQuestion.fromJson(response.data['data']);
return question; return question;
} }
@ -950,9 +976,9 @@ class ApiService {
} }
// Questions // Questions
Future<List<Question>> getQuestions(int id) async { Future<List<AssessmentQuestion>> getQuestions(int id) async {
try { try {
List<Question> questions = []; List<AssessmentQuestion> questions = [];
final Response response = await _service.dio.get( final Response response = await _service.dio.get(
'$kBaseUrl/api/$kApiVersionUrl/$kQuestionSetsUrl/$id/$kQuestionsUrl'); '$kBaseUrl/api/$kApiVersionUrl/$kQuestionSetsUrl/$id/$kQuestionsUrl');
@ -962,7 +988,7 @@ class ApiService {
var decodedData = data['data'] as List; var decodedData = data['data'] as List;
questions = decodedData.map( questions = decodedData.map(
(e) { (e) {
return Question.fromJson(e); return AssessmentQuestion.fromJson(e);
}, },
).toList(); ).toList();
return questions; return questions;

View File

@ -173,5 +173,6 @@ class AuthenticationService with ListenableServiceMixin {
_user = null; _user = null;
await _secureService.clear(); await _secureService.clear();
await setFirstTimeInstall(firstTimeInstall); await setFirstTimeInstall(firstTimeInstall);
notifyListeners();
} }
} }

View File

@ -27,8 +27,8 @@ class DioService {
DioService() { DioService() {
_dio.options _dio.options
..baseUrl = kBaseUrl ..baseUrl = kBaseUrl
..connectTimeout = const Duration(seconds: 30) ..connectTimeout = const Duration(seconds: 5)
..receiveTimeout = const Duration(seconds: 30); ..receiveTimeout = const Duration(seconds: 15);
_dio.interceptors.add( _dio.interceptors.add(
InterceptorsWrapper( InterceptorsWrapper(

View File

@ -16,8 +16,8 @@ enum LearnPractices { course, module, lesson }
// Voice recording state // Voice recording state
enum VoiceRecordingState { pending, recording } enum VoiceRecordingState { pending, recording }
// Levels // // Levels
enum ProficiencyLevels { a1, a2, b1, b2, none } // enum ProficiencyLevels { a1, a2, b1, b2, none }
// Progress status // Progress status
enum ProgressStatuses { pending, started, completed } enum ProgressStatuses { pending, started, completed }
@ -29,10 +29,11 @@ enum DuolingoAssessments { speaking, reading, writing, listening }
enum StateObjects { enum StateObjects {
none, none,
courses, courses,
startupView,
register, register,
verifyOtp, verifyOtp,
resendOtp, resendOtp,
assessments,
startupView,
learnLessons, learnLessons,
learnModules, learnModules,
learnCourses, learnCourses,
@ -56,6 +57,7 @@ enum StateObjects {
learnPracticeSample, learnPracticeSample,
learnPracticeAnswer, learnPracticeAnswer,
loginWithPhoneNumber, loginWithPhoneNumber,
assessmentQuestions,
learnPracticeQuestion, learnPracticeQuestion,
coursePracticeQuestion, coursePracticeQuestion,
coursePracticeQuestions, coursePracticeQuestions,

View File

@ -312,6 +312,10 @@ TextStyle style14MG400 = const TextStyle(
TextStyle style14DG500 = TextStyle style14DG500 =
const TextStyle(color: kcDarkGrey, fontWeight: FontWeight.w500); const TextStyle(color: kcDarkGrey, fontWeight: FontWeight.w500);
TextStyle style18MG500 =
const TextStyle(fontSize: 18,color: kcMediumGrey, fontWeight: FontWeight.w500);
TextStyle style14DG400 = const TextStyle( TextStyle style14DG400 = const TextStyle(
color: kcDarkGrey, color: kcDarkGrey,
); );

View File

@ -56,7 +56,7 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
Widget _buildAppbar(AccountPrivacyViewModel viewModel) => SmallAppBar( Widget _buildAppbar(AccountPrivacyViewModel viewModel) => SmallAppBar(
showBackButton: true, showBackButton: true,
onTap: viewModel.pop, onPop: viewModel.pop,
title: 'Account Privacy', title: 'Account Privacy',
); );

View File

@ -1,10 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/assessment/screens/assessment_questions_screen.dart'; import 'package:yimaru_app/ui/views/assessment/screens/assessment_questions_screen.dart';
import 'package:yimaru_app/ui/views/assessment/screens/assessment_intro_screen.dart'; import 'package:yimaru_app/ui/views/assessment/screens/assessment_intro_screen.dart';
import 'package:yimaru_app/ui/views/assessment/screens/assessment_result_screen.dart'; import 'package:yimaru_app/ui/views/assessment/screens/assessment_result_screen.dart';
import 'package:yimaru_app/ui/views/assessment/screens/start_lesson_screen.dart'; import 'package:yimaru_app/ui/views/assessment/screens/start_lesson_screen.dart';
import '../../widgets/assessment_loading_screen.dart';
import 'assessment_viewmodel.dart'; import 'assessment_viewmodel.dart';
class AssessmentView extends StackedView<AssessmentViewModel> { class AssessmentView extends StackedView<AssessmentViewModel> {
@ -39,16 +41,29 @@ class AssessmentView extends StackedView<AssessmentViewModel> {
Widget _buildAssessmentScreens(AssessmentViewModel viewModel) => IndexedStack( Widget _buildAssessmentScreens(AssessmentViewModel viewModel) => IndexedStack(
index: viewModel.currentPage, index: viewModel.currentPage,
children: _buildScreens(), children: _buildScreens(viewModel),
); );
List<Widget> _buildScreens() => [ List<Widget> _buildScreens(AssessmentViewModel viewModel) => [
_buildAssessmentIntro(), _buildAssessmentIntroWrapper(viewModel),
_buildAssessment(), _buildAssessment(),
_buildAssessmentResult(), _buildAssessmentResult(),
_buildStartLesson(), _buildStartLesson(),
]; ];
Widget _buildAssessmentIntroWrapper(AssessmentViewModel viewModel) =>
viewModel.busy(StateObjects.assessments) || viewModel.assessments.isEmpty
? _buildPageLoadingIndicator(viewModel)
: _buildAssessmentIntro();
Widget _buildPageLoadingIndicator(AssessmentViewModel viewModel) =>
AssessmentLoadingScreen(
isEmpty: viewModel.assessments.isEmpty,
onTap: () async => await viewModel.getAssessments(),
isLoading: viewModel.busy(StateObjects.assessments),
onPop: viewModel.assessments.isEmpty ? viewModel.pop : null,
);
Widget _buildAssessmentIntro() => const AssessmentIntroScreen(); Widget _buildAssessmentIntro() => const AssessmentIntroScreen();
Widget _buildAssessment() => const AssessmentQuestionsScreen(); Widget _buildAssessment() => const AssessmentQuestionsScreen();

View File

@ -1,13 +1,14 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/models/assessment_question.dart';
import 'package:yimaru_app/models/option.dart'; import 'package:yimaru_app/models/option.dart';
import 'package:yimaru_app/services/status_checker_service.dart'; import 'package:yimaru_app/services/status_checker_service.dart';
import 'package:yimaru_app/ui/common/enmus.dart'; 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/question.dart'; import '../../../models/assessment.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';
@ -33,82 +34,78 @@ class AssessmentViewModel extends BaseViewModel {
PageController get pageController => _pageController; PageController get pageController => _pageController;
// Assessment // Assessment
int _currentQuestion = 0;
int get currentQuestion => _currentQuestion; List<Assessment> _assessments = [];
List<Question> _assessments = []; List<Assessment> get assessments => _assessments;
List<Question> get assessments => _assessments; Assessment? _currentAssessment;
ProficiencyLevels _proficiencyLevel = ProficiencyLevels.none; Assessment? get currentAssessment => _currentAssessment;
ProficiencyLevels get proficiencyLevel => _proficiencyLevel; int _currentQuestionIndex = 0;
int get currentQuestionIndex => _currentQuestionIndex;
int _currentAssessmentIndex = 0;
int get currentAssessmentIndex => _currentAssessmentIndex;
String? _proficiencyLevel;
String? get proficiencyLevel => _proficiencyLevel;
final Map<String, dynamic> _selectedAnswers = {}; final Map<String, dynamic> _selectedAnswers = {};
Map<String, dynamic> get selectedAnswers => _selectedAnswers; Map<String, dynamic> get selectedAnswers => _selectedAnswers;
List<AssessmentQuestion> _assessmentQuestions = [];
List<AssessmentQuestion> get assessmentQuestions => _assessmentQuestions;
// User data // User data
final Map<String, dynamic> _userData = {}; final Map<String, dynamic> _userData = {};
Map<String, dynamic> get userData => _userData; Map<String, dynamic> get userData => _userData;
// Assessment // Assessment
Future<void> setFirstAssessment()async{
_proficiencyLevel = null;
_selectedAnswers.clear();
_currentQuestionIndex = 0;
_pageController.jumpToPage(_currentQuestionIndex);
_currentAssessment = assessments[currentAssessmentIndex];
await getAssessmentQuestions(
_currentAssessment?.id ?? 0);
next();
}
Map<String, dynamic> evaluateAssessment() { Map<String, dynamic> evaluateAssessment() {
if (_currentQuestion == 5) { bool levelPassed = canPassLevel();
// A1 if (levelPassed) {
final correctCount = countCorrectAnswersUntil(5); return {'passed': true, 'level': _currentAssessment?.description};
if (correctCount > 3) {
return {'continue': true, 'level': ProficiencyLevels.a1};
} else {
return {'continue': false, 'level': ProficiencyLevels.a1};
}
} else if (_currentQuestion == 10) {
// A2
final correctCount = countCorrectAnswersUntil(10);
if (correctCount > 3) {
return {'continue': true, 'level': ProficiencyLevels.a2};
} else {
return {'continue': false, 'level': ProficiencyLevels.a2};
}
} else if (_currentQuestion == 16) {
// B1
final correctCount = countCorrectAnswersUntil(16);
if (correctCount > 4) {
return {'continue': true, 'level': ProficiencyLevels.b1};
} else {
return {'continue': false, 'level': ProficiencyLevels.b1};
}
} else if (_currentQuestion == 22) {
final correctCount = countCorrectAnswersUntil(16);
if (correctCount > 4) {
return {'continue': false, 'level': ProficiencyLevels.b2};
} else {
return {'continue': false, 'level': ProficiencyLevels.b2};
}
} else { } else {
return {'continue': true, 'level': ProficiencyLevels.none}; return {'passed': false, 'level': _currentAssessment?.description};
} }
} }
int countCorrectAnswersUntil(int untilQuestion) { bool canPassLevel() {
int count = 0; int count = 0;
for (int i = 1; i <= untilQuestion; i++) { for (int i = 1; i <= _assessmentQuestions.length; i++) {
final answer = _selectedAnswers[i.toString()]; final answer = _selectedAnswers[i.toString()];
if (answer is Map<String, dynamic> && answer['correct'] == true) { if (answer is Map<String, dynamic> && answer['correct'] == true) {
count++; count++;
} }
} }
print('COUNT: $count');
print('ASSESSMENT: ${_currentAssessment?.passingScore}');
if (count >= (_currentAssessment?.passingScore ?? 0)) {
return true;
}
return count; return false;
} }
bool isSelectedAnswer({required int question, required String answer}) { bool isSelectedAnswer({required int question, required String answer}) {
@ -125,7 +122,7 @@ class AssessmentViewModel extends BaseViewModel {
question.toString(): { question.toString(): {
'correct': correct, 'correct': correct,
'option': option?.optionText, 'option': option?.optionText,
'answer': _assessments[question - 1] 'answer': _assessmentQuestions[question - 1]
.options .options
?.firstWhere((e) => e.isCorrect ?? false) ?.firstWhere((e) => e.isCorrect ?? false)
.optionText .optionText
@ -152,32 +149,41 @@ class AssessmentViewModel extends BaseViewModel {
} }
// Question navigation // Question navigation
void nextQuestion() { Future<void> nextQuestion() async {
_currentQuestion++; _currentQuestionIndex++;
Map<String, dynamic> response = evaluateAssessment(); Map<String, dynamic> response = evaluateAssessment();
print('LEVEL: $response');
if (_currentQuestion == _assessments.length) { print('LENGTH: ${_assessmentQuestions.length}');
_proficiencyLevel = response['level']; print('INDEX: $_currentQuestionIndex');
next(); if (_currentQuestionIndex == _assessmentQuestions.length) {
} else { _currentAssessmentIndex = _currentAssessmentIndex + 1;
if (response['level'] == ProficiencyLevels.none) { if (_currentAssessmentIndex == _assessments.length) {
_pageController.jumpToPage(_currentQuestion); _proficiencyLevel = response['level'];
next();
} else { } else {
if (response['continue']) { if (response['passed']) {
_pageController.jumpToPage(_currentQuestion); _selectedAnswers.clear();
_currentQuestionIndex = 0;
_proficiencyLevel = response['level'];
_currentAssessment = assessments[currentAssessmentIndex];
await getAssessmentQuestions(
_currentAssessment?.id ?? 0);
_pageController.jumpToPage(_currentQuestionIndex);
} else { } else {
_proficiencyLevel = response['level']; _proficiencyLevel = response['level'];
next(); next();
} }
} }
} else {
_pageController.jumpToPage(_currentQuestionIndex);
} }
rebuildUi(); rebuildUi();
} }
void previousQuestion() { void previousQuestion() {
if (_currentQuestion != 0) { if (_currentQuestionIndex != 0) {
_currentQuestion--; _currentQuestionIndex--;
_pageController.previousPage( _pageController.previousPage(
duration: const Duration(microseconds: 100), curve: Curves.linear); duration: const Duration(microseconds: 100), curve: Curves.linear);
rebuildUi(); rebuildUi();
@ -193,7 +199,7 @@ class AssessmentViewModel extends BaseViewModel {
_currentPage = 0; _currentPage = 0;
rebuildUi(); rebuildUi();
} else if (_currentPage == 3) { } else if (_currentPage == 3) {
if (_proficiencyLevel != ProficiencyLevels.none) { if (_proficiencyLevel != null) {
_currentPage--; _currentPage--;
} else { } else {
_currentPage = 0; _currentPage = 0;
@ -240,12 +246,12 @@ class AssessmentViewModel extends BaseViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> replaceWithStartUp() async =>
await _navigationService.clearStackAndShow(Routes.startupView);
Future<void> navigateToLanguage() async => Future<void> navigateToLanguage() async =>
await _navigationService.navigateToLanguageView(); await _navigationService.navigateToLanguageView();
Future<void> replaceWittStartUp() async =>
await _navigationService.clearStackAndShow(Routes.startupView);
// Remote api call // Remote api call
// Complete profile // Complete profile
@ -259,7 +265,7 @@ class AssessmentViewModel extends BaseViewModel {
await _apiService.completeProfile(_userData); await _apiService.completeProfile(_userData);
if (response['status'] == ResponseStatus.success) { if (response['status'] == ResponseStatus.success) {
clearUserData(); clearUserData();
await replaceWithStartUp(); await replaceWittStartUp();
showSuccessToast(response['message']); showSuccessToast(response['message']);
} else { } else {
showErrorToast(response['message']); showErrorToast(response['message']);
@ -268,11 +274,23 @@ class AssessmentViewModel extends BaseViewModel {
} }
// Assessments // Assessments
Future<void> getAssessments() async =>
await runBusyFuture(_getAssessments(),
busyObject: StateObjects.assessments);
Future<void> _getAssessments() async { Future<void> _getAssessments() async {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {
_assessments = await _apiService.getAssessments(); _assessments = await _apiService.getAssessments();
} }
} }
Future<void> getAssessments() async => await runBusyFuture(_getAssessments()); Future<void> getAssessmentQuestions(int id) async =>
await runBusyFuture(_getAssessmentQuestions(id),
busyObject: StateObjects.assessmentQuestions);
Future<void> _getAssessmentQuestions(int id) async {
if (await _statusChecker.checkConnection()) {
_assessmentQuestions = await _apiService.getAssessmentQuestions(id);
}
}
} }

View File

@ -10,6 +10,9 @@ import '../assessment_viewmodel.dart';
class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> { class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
const AssessmentIntroScreen({super.key}); const AssessmentIntroScreen({super.key});
Future<void> _next(AssessmentViewModel viewModel) async =>
viewModel.setFirstAssessment();
@override @override
Widget build(BuildContext context, AssessmentViewModel viewModel) => Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
@ -95,8 +98,8 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: () => viewModel.next(),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await _next(viewModel),
); );
Widget _buildSkipButtonWrapper(AssessmentViewModel viewModel) => Padding( Widget _buildSkipButtonWrapper(AssessmentViewModel viewModel) => Padding(

View File

@ -6,28 +6,15 @@ import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart'; import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
import '../../../widgets/assessment_loading_screen.dart';
import '../assessment_viewmodel.dart'; import '../assessment_viewmodel.dart';
import 'assessment_loading_screen.dart';
class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> { class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
const AssessmentQuestionsScreen({super.key}); const AssessmentQuestionsScreen({super.key});
@override @override
Widget build(BuildContext context, AssessmentViewModel viewModel) => Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildAssessmentScreens(viewModel); _buildAssessmentScreensWrapper(viewModel);
Widget _buildAssessmentScreens(AssessmentViewModel viewModel) =>
viewModel.isBusy || viewModel.assessments.isEmpty
? _buildPageLoadingIndicator(viewModel)
: _buildAssessmentScreensWrapper(viewModel);
Widget _buildPageLoadingIndicator(AssessmentViewModel viewModel) =>
AssessmentLoadingScreen(
isLoading: viewModel.isBusy,
isEmpty: viewModel.assessments.isEmpty,
onTap: () async => await viewModel.getAssessments(),
onPop: viewModel.assessments.isEmpty ? viewModel.pop : null,
);
Widget _buildAssessmentScreensWrapper(AssessmentViewModel viewModel) => Widget _buildAssessmentScreensWrapper(AssessmentViewModel viewModel) =>
PopScope( PopScope(
@ -52,7 +39,7 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
onClose: viewModel.abort, onClose: viewModel.abort,
showLanguageSelection: false, showLanguageSelection: false,
onPop: viewModel.previousQuestion, onPop: viewModel.previousQuestion,
showBackButton: viewModel.currentQuestion == 0 ? false : true, showBackButton: viewModel.currentQuestionIndex == 0 ? false : true,
); );
Widget _buildExpandedBody(AssessmentViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
@ -65,7 +52,7 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildAssessment(AssessmentViewModel viewModel) => PageView.builder( Widget _buildAssessment(AssessmentViewModel viewModel) => PageView.builder(
controller: viewModel.pageController, controller: viewModel.pageController,
itemCount: viewModel.assessments.length, itemCount: viewModel.assessmentQuestions.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (cotext, index) => itemBuilder: (cotext, index) =>
_buildBodyScroller(index: index, viewModel: viewModel), _buildBodyScroller(index: index, viewModel: viewModel),
@ -98,7 +85,7 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildTitle( Widget _buildTitle(
{required int index, required AssessmentViewModel viewModel}) => {required int index, required AssessmentViewModel viewModel}) =>
Text( Text(
'Q${index + 1}. ${viewModel.assessments[index].questionText} ', 'Q${index + 1}. ${viewModel.assessmentQuestions[index].questionText} ',
style: style16DG600, style: style16DG600,
); );
@ -107,15 +94,18 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
ListView.builder( ListView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: viewModel.assessments[index].options?.length, itemCount: viewModel.assessmentQuestions[index].options?.length,
itemBuilder: (context, inner) => _buildAnswer( itemBuilder: (context, inner) => _buildAnswer(
onTap: () => viewModel.setSelectedAnswer( onTap: () => viewModel.setSelectedAnswer(
question: index + 1, question: index + 1,
option: viewModel.assessments[index].options?[inner]), option: viewModel.assessmentQuestions[index].options?[inner]),
title: viewModel.assessments[index].options?[inner].optionText ?? '', title:
viewModel.assessmentQuestions[index].options?[inner].optionText ??
'',
selected: viewModel.isSelectedAnswer( selected: viewModel.isSelectedAnswer(
question: index + 1, question: index + 1,
answer: viewModel.assessments[index].options?[inner].optionText ?? answer: viewModel
.assessmentQuestions[index].options?[inner].optionText ??
''), ''),
), ),
); );
@ -150,8 +140,8 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
onTap: viewModel.selectedAnswers.containsKey(question.toString()) onTap: viewModel.selectedAnswers.containsKey(question.toString())
? () => viewModel.nextQuestion() ? () => viewModel.nextQuestion()
: null, : null,
text: viewModel.currentQuestion == viewModel.assessments.length - 1 text: viewModel.currentQuestionIndex == viewModel.assessmentQuestions.length - 1
? 'Finish' ? 'Finish Level'
: 'Continue', : 'Continue',
); );
} }

View File

@ -75,7 +75,7 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
]; ];
Widget _buildTitle(AssessmentViewModel viewModel) => Text( Widget _buildTitle(AssessmentViewModel viewModel) => Text(
'Youre likely a ${viewModel.proficiencyLevel.name.toUpperCase()} speaker!', 'Youre likely a ${viewModel.proficiencyLevel?.toUpperCase()} speaker!',
style: style25DG600, style: style25DG600,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
@ -87,12 +87,12 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
); );
Widget _buildIconWrapper(AssessmentViewModel viewModel) => Widget _buildIconWrapper(AssessmentViewModel viewModel) =>
viewModel.proficiencyLevel != ProficiencyLevels.none viewModel.proficiencyLevel != null
? _buildIcon(viewModel) ? _buildIcon(viewModel)
: Container(); : Container();
Widget _buildIcon(AssessmentViewModel viewModel) => SvgPicture.asset( Widget _buildIcon(AssessmentViewModel viewModel) => SvgPicture.asset(
'assets/icons/${viewModel.proficiencyLevel.name.substring(0, 1)}_${viewModel.proficiencyLevel.name.substring(1)}.svg'); 'assets/icons/${viewModel.proficiencyLevel?.substring(0, 1).toLowerCase()}_${viewModel.proficiencyLevel?.substring(1).toLowerCase()}.svg');
Widget _buildSecondarySubtitle() => Text( Widget _buildSecondarySubtitle() => Text(
'Let\'s start your practice', 'Let\'s start your practice',

View File

@ -14,9 +14,9 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
const StartLessonScreen({super.key}); const StartLessonScreen({super.key});
Future<void> _start(AssessmentViewModel viewModel) async { Future<void> _start(AssessmentViewModel viewModel) async {
if (viewModel.proficiencyLevel != ProficiencyLevels.none) { if (viewModel.proficiencyLevel != null) {
Map<String, dynamic> data = { Map<String, dynamic> data = {
'knowledge_level': viewModel.proficiencyLevel.name.toUpperCase() 'knowledge_level': viewModel.proficiencyLevel?.toUpperCase()
}; };
viewModel.addUserData(data); viewModel.addUserData(data);

View File

@ -49,7 +49,7 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
Widget _buildAppbar(CallSupportViewModel viewModel) => SmallAppBar( Widget _buildAppbar(CallSupportViewModel viewModel) => SmallAppBar(
showBackButton: true, showBackButton: true,
onTap: viewModel.pop, onPop: viewModel.pop,
title: 'Call Support', title: 'Call Support',
); );

View File

@ -56,7 +56,7 @@ class CourseView extends StackedView<CourseViewModel> {
); );
Widget _buildAppBar(CourseViewModel viewModel) => SmallAppBar( Widget _buildAppBar(CourseViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
); );

View File

@ -57,7 +57,7 @@ class CourseLessonView extends StackedView<CourseLessonViewModel> {
); );
Widget _buildAppBar(CourseLessonViewModel viewModel) => SmallAppBar( Widget _buildAppBar(CourseLessonViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
title: 'Course Detail', title: 'Course Detail',
); );

View File

@ -57,7 +57,7 @@ class CourseLessonDetailView extends StackedView<CourseLessonDetailViewModel> {
child: _buildAppBar(viewModel)); child: _buildAppBar(viewModel));
Widget _buildAppBar(CourseLessonDetailViewModel viewModel) => SmallAppBar( Widget _buildAppBar(CourseLessonDetailViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
title: lesson.title ?? '', title: lesson.title ?? '',
); );

View File

@ -50,7 +50,7 @@ class CoursePaymentView extends StackedView<CoursePaymentViewModel> {
); );
Widget _buildAppBar(CoursePaymentViewModel viewModel) => SmallAppBar( Widget _buildAppBar(CoursePaymentViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
); );

View File

@ -53,7 +53,7 @@ class CoursePracticeView extends StackedView<CoursePracticeViewModel> {
); );
Widget _buildAppBar(CoursePracticeViewModel viewModel) => SmallAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
); );

View File

@ -4,7 +4,7 @@ import 'package:stacked_services/stacked_services.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../models/option.dart'; import '../../../models/option.dart';
import '../../../models/question.dart'; import '../../../models/assessment_question.dart';
import '../../../services/api_service.dart'; import '../../../services/api_service.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
@ -38,13 +38,14 @@ class CoursePracticeQuestionViewModel extends FormViewModel {
bool get focusAnswer => _focusAnswer; bool get focusAnswer => _focusAnswer;
Question? _currentQuestion; AssessmentQuestion? _currentQuestion;
Question? get currentQuestion => _currentQuestion; AssessmentQuestion? get currentQuestion => _currentQuestion;
List<Question> _coursePracticeQuestions = []; List<AssessmentQuestion> _coursePracticeQuestions = [];
List<Question> get coursePracticeQuestions => _coursePracticeQuestions; List<AssessmentQuestion> get coursePracticeQuestions =>
_coursePracticeQuestions;
int _currentQuestionIndex = 0; int _currentQuestionIndex = 0;

View File

@ -59,7 +59,7 @@ class CourseSubcategoryView extends StackedView<CourseSubcategoryViewModel> {
); );
Widget _buildAppBar(CourseSubcategoryViewModel viewModel) => SmallAppBar( Widget _buildAppBar(CourseSubcategoryViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
); );

View File

@ -53,7 +53,7 @@ class DownloadsView extends StackedView<DownloadsViewModel> {
]; ];
Widget _buildAppbar(DownloadsViewModel viewModel) => SmallAppBar( Widget _buildAppbar(DownloadsViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
title: 'Offline Downloads', title: 'Offline Downloads',
); );

View File

@ -9,8 +9,10 @@ import 'failure_viewmodel.dart';
class FailureView extends StackedView<FailureViewModel> { class FailureView extends StackedView<FailureViewModel> {
final String label; final String label;
final GestureTapCallback onTap;
const FailureView({Key? key, required this.label}) : super(key: key); const FailureView({Key? key, required this.onTap, required this.label})
: super(key: key);
@override @override
FailureViewModel viewModelBuilder(BuildContext context) => FailureViewModel(); FailureViewModel viewModelBuilder(BuildContext context) => FailureViewModel();
@ -48,19 +50,38 @@ class FailureView extends StackedView<FailureViewModel> {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(), children: _buildColumnChildren(),
); );
List<Widget> _buildUpperColumnChildren() => List<Widget> _buildColumnChildren() =>
[_buildIconWrapper(), _buildSafeWrapper()]; [_buildIconWrapper(), _buildSafeWrapper()];
Widget _buildSafeWrapper() => SafeArea(child: _buildLoadingTextContainer()); Widget _buildIconWrapper() => Padding(
padding: const EdgeInsets.only(top: 100),
Widget _buildLoadingTextContainer() => Padding( child: _buildIcon(),
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
); );
Widget _buildIcon() => SvgPicture.asset('assets/icons/logo.svg', height: 50);
Widget _buildSafeWrapper() => SafeArea(child: _buildBottomSectionWrapper());
Widget _buildBottomSectionWrapper() => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildBottomSectionColumn(),
);
Widget _buildBottomSectionColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: _buildBottomSectionChildren(),
);
List<Widget> _buildBottomSectionChildren() => [
_buildLoadingTextWrapper(),
verticalSpaceSmall,
_buildRetryButtonWrapper()
];
Widget _buildLoadingTextWrapper() => Row( Widget _buildLoadingTextWrapper() => Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -85,10 +106,14 @@ class FailureView extends StackedView<FailureViewModel> {
Widget _buildIndicator() => Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite); const CustomCircularProgressIndicator(color: kcWhite);
Widget _buildIconWrapper() => Padding( Widget _buildRetryButtonWrapper() => GestureDetector(
padding: const EdgeInsets.only(top: 100), onTap: onTap,
child: _buildIcon(), child: _buildRetryButton(),
); );
Widget _buildIcon() => SvgPicture.asset('assets/icons/logo.svg', height: 50); Widget _buildRetryButton() => Text(
'Retry',
style: style16W600.copyWith(
fontStyle: FontStyle.italic, decoration: TextDecoration.underline),
);
} }

View File

@ -1,16 +1,21 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'; import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/profile/profile_view.dart'; import 'package:yimaru_app/ui/views/profile/profile_view.dart';
import 'package:yimaru_app/ui/views/startup/startup_view.dart';
import 'package:yimaru_app/ui/widgets/coming_soon.dart'; import 'package:yimaru_app/ui/widgets/coming_soon.dart';
import '../../common/enmus.dart';
import '../../widgets/page_loading_indicator.dart';
import 'home_viewmodel.dart'; import 'home_viewmodel.dart';
class HomeView extends StackedView<HomeViewModel> { class HomeView extends StackedView<HomeViewModel> {
const HomeView({Key? key}) : super(key: key); const HomeView({Key? key}) : super(key: key);
@override
void onViewModelReady(HomeViewModel viewModel) async {
await viewModel.inAppUpdate();
super.onViewModelReady(viewModel);
}
@override @override
HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel(); HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel();
@ -20,8 +25,6 @@ class HomeView extends StackedView<HomeViewModel> {
BuildContext context, HomeViewModel viewModel, Widget? child) => BuildContext context, HomeViewModel viewModel, Widget? child) =>
_buildScaffold(viewModel); _buildScaffold(viewModel);
Widget _buildScaffold(HomeViewModel viewModel) => Scaffold( Widget _buildScaffold(HomeViewModel viewModel) => Scaffold(
body: getViewForIndex(viewModel.currentPage), body: getViewForIndex(viewModel.currentPage),
bottomNavigationBar: _buildBottomNav(viewModel), bottomNavigationBar: _buildBottomNav(viewModel),
@ -56,22 +59,22 @@ class HomeView extends StackedView<HomeViewModel> {
label: 'Profile', label: 'Profile',
icon: _buildProfileIcon(), icon: _buildProfileIcon(),
); );
}
Widget _buildLearnIcon() => const Icon(Icons.school); Widget _buildLearnIcon() => const Icon(Icons.school);
Widget _buildCourseIcon() => const Icon(Icons.book); Widget _buildCourseIcon() => const Icon(Icons.book);
Widget _buildProfileIcon() => const Icon(Icons.person); Widget _buildProfileIcon() => const Icon(Icons.person);
Widget getViewForIndex(int index) { Widget getViewForIndex(int index) {
switch (index) { switch (index) {
case 0: case 0:
return const LearnProgramView(); return const LearnProgramView();
case 1: case 1:
return const ComingSoon(); return const ComingSoon();
default: default:
return const ProfileView(); return const ProfileView();
}
} }
} }

View File

@ -1,21 +1,19 @@
import 'package:yimaru_app/app/app.bottomsheets.dart'; import 'package:yimaru_app/app/app.bottomsheets.dart';
import 'package:yimaru_app/app/app.locator.dart'; import 'package:yimaru_app/app/app.locator.dart';
import 'package:yimaru_app/app/app.router.dart';
import 'package:yimaru_app/models/user.dart'; import 'package:yimaru_app/models/user.dart';
import 'package:yimaru_app/services/status_checker_service.dart'; import 'package:yimaru_app/services/status_checker_service.dart';
import 'package:yimaru_app/ui/common/app_strings.dart'; import 'package:yimaru_app/ui/common/app_strings.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import '../../../services/api_service.dart';
import '../../../services/authentication_service.dart'; import '../../../services/authentication_service.dart';
import '../../../services/image_downloader_service.dart'; import '../../../services/in_app_update_service.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
class HomeViewModel extends ReactiveViewModel { class HomeViewModel extends ReactiveViewModel {
// Dependency injection
final _statusChecker = locator<StatusCheckerService>();
final _bottomSheetService = locator<BottomSheetService>(); final _bottomSheetService = locator<BottomSheetService>();
final _inAppUpdateService = locator<InAppUpdateService>();
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
@override @override
@ -46,9 +44,12 @@ class HomeViewModel extends ReactiveViewModel {
rebuildUi(); rebuildUi();
} }
// Remote api calls
// In-app update
Future<void> inAppUpdate() async {
if (await _statusChecker.checkConnection()) {
await _inAppUpdateService.checkForUpdate();
}
}
} }

View File

@ -73,7 +73,7 @@ class LanguageView extends StackedView<LanguageViewModel> {
Widget _buildAppbar(LanguageViewModel viewModel) => SmallAppBar( Widget _buildAppbar(LanguageViewModel viewModel) => SmallAppBar(
showBackButton: true, showBackButton: true,
onTap: viewModel.pop, onPop: viewModel.pop,
title: 'Language Preference', title: 'Language Preference',
); );

View File

@ -55,7 +55,7 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
); );
Widget _buildAppBar(LearnCourseViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnCourseViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
); );

View File

@ -77,7 +77,7 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
); );
Widget _buildAppBar(LearnLessonViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnLessonViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
); );

View File

@ -17,7 +17,7 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
Future<void> _navigate(LearnLessonDetailViewModel viewModel) async { Future<void> _navigate(LearnLessonDetailViewModel viewModel) async {
await viewModel.pause(); await viewModel.pause();
await viewModel.navigateToLearnPractice(); await viewModel.navigateToLearnPractice(lesson.id ?? 0);
} }
@override @override
@ -60,7 +60,7 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
child: _buildAppBar(viewModel)); child: _buildAppBar(viewModel));
Widget _buildAppBar(LearnLessonDetailViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnLessonDetailViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
); );

View File

@ -4,6 +4,7 @@ import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../app/app.router.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
class LearnLessonDetailViewModel extends BaseViewModel { class LearnLessonDetailViewModel extends BaseViewModel {
@ -44,6 +45,6 @@ class LearnLessonDetailViewModel extends BaseViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnPractice() async {} Future<void> navigateToLearnPractice(int id) async => await _navigationService
// await _navigationService.navigateToLearnPracticeView(); .navigateToLearnPracticeView(id: id, practice: LearnPractices.lesson);
} }

View File

@ -19,7 +19,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
@override @override
void onViewModelReady(LearnModuleViewModel viewModel) async { void onViewModelReady(LearnModuleViewModel viewModel) async {
await viewModel.getLearnModules(1); await viewModel.getLearnModules(course.id ?? 0);
super.onViewModelReady(viewModel); super.onViewModelReady(viewModel);
} }
@ -58,7 +58,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
); );
Widget _buildAppBar(LearnModuleViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnModuleViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
); );
@ -116,10 +116,10 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile( itemBuilder: (context, index) => _buildTile(
module: viewModel.modules[index], module: viewModel.modules[index],
onModuleTap: () async =>
await viewModel.navigateToLearnLesson(viewModel.modules[index]),
onPracticeTap: () async => await viewModel onPracticeTap: () async => await viewModel
.navigateToLearnPractice(viewModel.modules[index].id ?? 0), .navigateToLearnPractice(viewModel.modules[index].id ?? 0),
onModuleTap: () async =>
await viewModel.navigateToLearnLesson(viewModel.modules[index]),
), ),
); );

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/finish_learn_practice_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/finish_learn_practice_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_loading_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_completion_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_completion_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_result_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_result_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_questions_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_questions_screen.dart';
@ -78,7 +79,20 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
Widget _buildBodyState(LearnPracticeViewModel viewModel) => Widget _buildBodyState(LearnPracticeViewModel viewModel) =>
viewModel.busy(StateObjects.learnPractices) viewModel.busy(StateObjects.learnPractices)
? const PageLoadingIndicator() ? const PageLoadingIndicator()
: _buildBody(viewModel); : viewModel.practices.isEmpty || viewModel.questions.isEmpty
? _buildPageLoadingIndicator(viewModel)
: _buildBody(viewModel);
Widget _buildPageLoadingIndicator(LearnPracticeViewModel viewModel) =>
LearnLoadingScreen(
isLoading: viewModel.busy(StateObjects.learnPractices),
onTap: () async =>
await viewModel.getLearnPractices(id: id, practice: practice),
onPop: viewModel.practices.isEmpty || viewModel.questions.isEmpty
? viewModel.pop
: null,
isEmpty: viewModel.practices.isEmpty || viewModel.questions.isEmpty,
);
Widget _buildBody(LearnPracticeViewModel viewModel) => IndexedStack( Widget _buildBody(LearnPracticeViewModel viewModel) => IndexedStack(
index: viewModel.currentPage, children: _buildScreens(viewModel)); index: viewModel.currentPage, children: _buildScreens(viewModel));

View File

@ -149,6 +149,10 @@ class LearnPracticeViewModel extends ReactiveViewModel {
await _audioPlayerService.playUrl(question.voicePrompt ?? ''); await _audioPlayerService.playUrl(question.voicePrompt ?? '');
} }
Future<void> replayVoicePrompt(LearnQuestion question) async {
await _audioPlayerService.playUrl(question.voicePrompt ?? '');
}
Future<void> playResult( Future<void> playResult(
{required Map<String, dynamic> answer, required Voice voice}) async { {required Map<String, dynamic> answer, required Voice voice}) async {
setBusyObject(playing: voice, object: answer['busy_object']); setBusyObject(playing: voice, object: answer['busy_object']);
@ -222,6 +226,7 @@ class LearnPracticeViewModel extends ReactiveViewModel {
_questionSetController.nextPage( _questionSetController.nextPage(
duration: const Duration(milliseconds: 350), duration: const Duration(milliseconds: 350),
curve: Curves.easeInOutCubic); curve: Curves.easeInOutCubic);
await playVoicePrompt(_questions[index]);
} else { } else {
goTo(3); goTo(3);
} }

View File

@ -50,7 +50,7 @@ class FinishLearnPracticeScreen
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
showBackButton: true, showBackButton: true,
onTap: viewModel.goBack, onPop: viewModel.goBack,
title: 'Practice Speaking', title: 'Practice Speaking',
); );

View File

@ -30,8 +30,8 @@ class InteractLearnPracticeScreen
viewModel.stopRecording(); viewModel.stopRecording();
} }
void _start(LearnPracticeViewModel viewModel) => void _reply(LearnPracticeViewModel viewModel) =>
viewModel.playVoicePrompt(question); viewModel.replayVoicePrompt(question);
Future<void> _stop(LearnPracticeViewModel viewModel) async => Future<void> _stop(LearnPracticeViewModel viewModel) async =>
await viewModel.nextQuestion(index: index, question: question); await viewModel.nextQuestion(index: index, question: question);
@ -116,7 +116,7 @@ class InteractLearnPracticeScreen
required LearnPracticeViewModel viewModel}) => required LearnPracticeViewModel viewModel}) =>
SmallAppBar( SmallAppBar(
showBackButton: true, showBackButton: true,
onTap: () async => onPop: () async =>
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
title: 'Practice Speaking ($index/${viewModel.questions.length})'); title: 'Practice Speaking ($index/${viewModel.questions.length})');
@ -263,7 +263,7 @@ class InteractLearnPracticeScreen
: kcLightGrey, : kcLightGrey,
onTap: viewModel.recordingState == VoiceRecordingState.pending && onTap: viewModel.recordingState == VoiceRecordingState.pending &&
viewModel.player.state != PlayerState.playing viewModel.player.state != PlayerState.playing
? () => _start(viewModel) ? () => _reply(viewModel)
: null, : null,
); );
@ -272,15 +272,15 @@ class InteractLearnPracticeScreen
Widget _buildMicButton(LearnPracticeViewModel viewModel) => ElevatedButton( Widget _buildMicButton(LearnPracticeViewModel viewModel) => ElevatedButton(
style: ButtonStyle( style: ButtonStyle(
shadowColor: const WidgetStatePropertyAll(kcWhite),
shape: const WidgetStatePropertyAll(CircleBorder()),
padding: const WidgetStatePropertyAll(EdgeInsets.all(15)),
backgroundColor: WidgetStatePropertyAll( backgroundColor: WidgetStatePropertyAll(
viewModel.player.state == PlayerState.playing || viewModel.player.state == PlayerState.playing ||
viewModel.busy(StateObjects.recordLearnPracticeAnswer) viewModel.busy(StateObjects.recordLearnPracticeAnswer)
? kcVeryLightGrey ? kcVeryLightGrey
: kcPrimaryColor, : kcPrimaryColor,
), ),
shadowColor: const WidgetStatePropertyAll(kcWhite),
shape: const WidgetStatePropertyAll(CircleBorder()),
padding: const WidgetStatePropertyAll(EdgeInsets.all(15)),
), ),
onPressed: () async => viewModel.player.state == PlayerState.playing || onPressed: () async => viewModel.player.state == PlayerState.playing ||
viewModel.busy(StateObjects.recordLearnPracticeAnswer) viewModel.busy(StateObjects.recordLearnPracticeAnswer)

View File

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/widgets/no_data_indicator.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import 'package:yimaru_app/ui/widgets/small_app_bar.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/large_app_bar.dart';
import '../../../widgets/refresh_button.dart';
class LearnLoadingScreen extends StatelessWidget {
final bool isEmpty;
final bool isLoading;
final GestureTapCallback? onPop;
final GestureTapCallback? onTap;
const LearnLoadingScreen(
{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() => SafeArea(child: _buildStack());
Widget _buildStack() => Stack(
children: [
_buildColumn(),
if (isEmpty) _buildRefreshButtonWrapper(),
if (isLoading) _buildPageIndicator()
],
);
Widget _buildColumn() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
);
List<Widget> _buildColumnChildren() =>
[verticalSpaceMedium, _buildAppBarWrapper(), _buildBody()];
Widget _buildAppBarWrapper() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBar(),
);
Widget _buildAppBar() => SmallAppBar(onPop: onPop, showBackButton: true);
Widget _buildBody() => Expanded(child: Container());
Widget _buildPageIndicator() => const PageLoadingIndicator();
Widget _buildRefreshButtonWrapper() => Align(
alignment: Alignment.center,
child:_buildRefreshButton());
Widget _buildRefreshButton()=> NoDataIndicator(title:'No practice available!' ,onTap: onTap,);
}

View File

@ -72,7 +72,7 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
SmallAppBar( SmallAppBar(
showBackButton: true, showBackButton: true,
title: 'Practice Speaking', title: 'Practice Speaking',
onTap: () async => onPop: () async =>
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
); );

View File

@ -40,7 +40,7 @@ class LearnPracticeQuestionsScreen
required LearnQuestion question, required LearnQuestion question,
}) => }) =>
[ [
_buildStartLearnPracticeScreen(index: index, question: question), if(index ==1) _buildStartLearnPracticeScreen(index: index, question: question),
_buildInteractLearnPracticeScreen(index: index, question: question) _buildInteractLearnPracticeScreen(index: index, question: question)
]; ];

View File

@ -86,7 +86,7 @@ class LearnPracticeResultScreen
SmallAppBar( SmallAppBar(
title: 'Result', title: 'Result',
showBackButton: true, showBackButton: true,
onTap: () async => onPop: () async =>
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
); );

View File

@ -97,7 +97,7 @@ class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
required LearnPracticeViewModel viewModel}) => required LearnPracticeViewModel viewModel}) =>
SmallAppBar( SmallAppBar(
showBackButton: true, showBackButton: true,
onTap: () async => onPop: () async =>
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
title: 'Practice Speaking ($index/${viewModel.questions.length})'); title: 'Practice Speaking ($index/${viewModel.questions.length})');

View File

@ -155,7 +155,6 @@ class LoginViewModel extends ReactiveViewModel
// Navigation // Navigation
Future<void> navigateToRegister() async => Future<void> navigateToRegister() async =>
await _navigationService.navigateToRegisterView(); await _navigationService.navigateToRegisterView();
@ -165,9 +164,6 @@ class LoginViewModel extends ReactiveViewModel
Future<void> replaceWithStartUp() async => Future<void> replaceWithStartUp() async =>
await _navigationService.clearStackAndShow(Routes.startupView); await _navigationService.clearStackAndShow(Routes.startupView);
// Remote api calls // Remote api calls
// Login with email // Login with email

View File

@ -36,6 +36,7 @@ class OnboardingView extends StackedView<OnboardingViewModel>
void _initClearData() { void _initClearData() {
topicController.clear(); topicController.clear();
regionController.clear();
fullNameController.clear(); fullNameController.clear();
challengeController.clear(); challengeController.clear();
occupationController.clear(); occupationController.clear();

View File

@ -191,7 +191,7 @@ class OnboardingViewModel extends ReactiveViewModel
final List<String> _topics = [ final List<String> _topics = [
'Food & Cooking', 'Food & Cooking',
' Hobbies, Sports, Music', 'Hobbies, Sports, Music',
'Tech, News, Business', 'Tech, News, Business',
'Travel, Places, Culture', 'Travel, Places, Culture',
'Other' 'Other'
@ -547,6 +547,7 @@ class OnboardingViewModel extends ReactiveViewModel
// Reset country region form screen // Reset country region form screen
void resetCountryRegionFormScreen() { void resetCountryRegionFormScreen() {
_focusRegion = false;
_selectedCountry = 'Ethiopia'; _selectedCountry = 'Ethiopia';
rebuildUi(); rebuildUi();
} }
@ -613,6 +614,4 @@ class OnboardingViewModel extends ReactiveViewModel
Future<void> navigateToAssessment() async => Future<void> navigateToAssessment() async =>
await _navigationService.navigateToAssessmentView(data: _userData); await _navigationService.navigateToAssessmentView(data: _userData);
} }

View File

@ -55,7 +55,7 @@ class PrivacyPolicyView extends StackedView<PrivacyPolicyViewModel> {
Widget _buildAppbar(PrivacyPolicyViewModel viewModel) => SmallAppBar( Widget _buildAppbar(PrivacyPolicyViewModel viewModel) => SmallAppBar(
showBackButton: true, showBackButton: true,
onTap: viewModel.pop, onPop: viewModel.pop,
title: 'Privacy Policy', title: 'Privacy Policy',
); );

View File

@ -3,6 +3,7 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import 'package:yimaru_app/ui/widgets/profile_card.dart'; import 'package:yimaru_app/ui/widgets/profile_card.dart';
import 'package:yimaru_app/ui/widgets/profile_image.dart'; import 'package:yimaru_app/ui/widgets/profile_image.dart';
import 'package:yimaru_app/ui/widgets/view_profile_button.dart'; import 'package:yimaru_app/ui/widgets/view_profile_button.dart';
@ -155,7 +156,7 @@ class ProfileView extends StackedView<ProfileViewModel> {
children: _buildSettingsChildren(viewModel)); children: _buildSettingsChildren(viewModel));
List<Widget> _buildSettingsChildren(ProfileViewModel viewModel) => [ List<Widget> _buildSettingsChildren(ProfileViewModel viewModel) => [
_buildDownloadsCard(viewModel), // _buildDownloadsCard(viewModel),
_buildProgressCard(viewModel), _buildProgressCard(viewModel),
_buildAccountCard(viewModel), _buildAccountCard(viewModel),
_buildSupportCard(viewModel) _buildSupportCard(viewModel)
@ -191,7 +192,7 @@ class ProfileView extends StackedView<ProfileViewModel> {
Widget _buildLogOutButton(ProfileViewModel viewModel) => CustomElevatedButton( Widget _buildLogOutButton(ProfileViewModel viewModel) => CustomElevatedButton(
height: 55, height: 55,
text: 'Log Out', text: 'Logout',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcRed, foregroundColor: kcRed,
backgroundColor: kcRed.withOpacity(0.25), backgroundColor: kcRed.withOpacity(0.25),

View File

@ -61,13 +61,6 @@ class ProfileViewModel extends ReactiveViewModel {
} }
} }
// Logout
Future<void> _logout() async {
await _googleAuthService.logout();
await _authenticationService.logout();
await _navigationService.replaceWithLoginView();
}
// Dialog // Dialog
Future<bool?> showAbortDialog() async { Future<bool?> showAbortDialog() async {
DialogResponse? response = await _dialogService.showDialog( DialogResponse? response = await _dialogService.showDialog(
@ -82,13 +75,6 @@ class ProfileViewModel extends ReactiveViewModel {
return response?.confirmed; return response?.confirmed;
} }
Future<void> logout() async {
bool? response = await showAbortDialog();
if (response != null && response) {
await _logout();
}
}
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
@ -107,6 +93,9 @@ class ProfileViewModel extends ReactiveViewModel {
Future<void> navigateToSupport() async => Future<void> navigateToSupport() async =>
await _navigationService.navigateToSupportView(); await _navigationService.navigateToSupportView();
Future<void> navigateToLogin() async =>
await _navigationService.clearStackAndShow(Routes.loginView);
// Remote api call // Remote api call
// Update profile // Update profile
@ -115,10 +104,21 @@ class ProfileViewModel extends ReactiveViewModel {
Future<void> _updateProfilePicture(String image) async { Future<void> _updateProfilePicture(String image) async {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {
Map<String, dynamic> data = { Map<String, dynamic> data = {'profile_picture_url': image};
'profile_picture_url': image,
};
await _apiService.updateProfileImage(data: data, userId: _user?.userId); await _apiService.updateProfileImage(data: data, userId: _user?.userId);
} }
} }
Future<void> logout() async {
bool? response = await showAbortDialog();
if (response != null && response) {
await _logout();
}
}
Future<void> _logout() async {
await _googleAuthService.logout();
await _authenticationService.logout();
await navigateToLogin();
}
} }

View File

@ -34,13 +34,13 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
Future<void> _update(ProfileDetailViewModel viewModel) async { Future<void> _update(ProfileDetailViewModel viewModel) async {
Map<String, dynamic> data = { Map<String, dynamic> data = {
'region':regionController.text, 'region': regionController.text,
'gender': viewModel.selectedGender, 'gender': viewModel.selectedGender,
'last_name': lastNameController.text, 'last_name': lastNameController.text,
'country': viewModel.selectedCountry, 'country': viewModel.selectedCountry,
'first_name': firstNameController.text, 'first_name': firstNameController.text,
'occupation': occupationController.text, 'occupation': occupationController.text,
'birth_day': DateFormat('d MMM, yyyy').format(DateTime.now()), 'birth_day': DateFormat('yyyy-MM-dd').format(DateTime.now()),
}; };
viewModel.addUserData(data); viewModel.addUserData(data);
@ -75,7 +75,6 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
viewModel.clearUserData(); viewModel.clearUserData();
viewModel.setGender(viewModel.user?.gender ?? ''); viewModel.setGender(viewModel.user?.gender ?? '');
viewModel.setSelectedCountry(viewModel.user?.country ?? 'Ethiopia'); viewModel.setSelectedCountry(viewModel.user?.country ?? 'Ethiopia');
} }
@override @override
@ -147,7 +146,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
]; ];
Widget _buildAppbar(ProfileDetailViewModel viewModel) => SmallAppBar( Widget _buildAppbar(ProfileDetailViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
title: 'Edit Profile', title: 'Edit Profile',
); );
@ -192,9 +191,8 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
verticalSpaceSmall, verticalSpaceSmall,
_buildCountryDropdown(viewModel), _buildCountryDropdown(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildRegionFormFieldWrapper(viewModel), _buildRegionFormFieldWrapper(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildOccupationDropdownWrapper(viewModel), _buildOccupationDropdownWrapper(viewModel),
verticalSpaceLarge, verticalSpaceLarge,
_buildLowerColumn(viewModel) _buildLowerColumn(viewModel)
@ -533,9 +531,10 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
); );
List<Widget> _buildRegionFormFieldChildren( List<Widget> _buildRegionFormFieldChildren(
ProfileDetailViewModel viewModel) => ProfileDetailViewModel viewModel) =>
[ [
_buildRegionFormFieldLabel(),verticalSpaceSmall, _buildRegionFormFieldLabel(),
verticalSpaceSmall,
_buildRegionFormField(viewModel), _buildRegionFormField(viewModel),
if (viewModel.hasRegionValidationMessage && viewModel.focusRegion) if (viewModel.hasRegionValidationMessage && viewModel.focusRegion)
verticalSpaceTiny, verticalSpaceTiny,

View File

@ -47,7 +47,6 @@ class ProfileDetailViewModel extends ReactiveViewModel
String? get selectedGender => _selectedGender; String? get selectedGender => _selectedGender;
// First name // First name
bool _focusPhoneNumber = false; bool _focusPhoneNumber = false;
@ -63,7 +62,6 @@ class ProfileDetailViewModel extends ReactiveViewModel
String get selectedCountry => _selectedCountry; String get selectedCountry => _selectedCountry;
// Occupation // Occupation
bool _focusOccupation = false; bool _focusOccupation = false;
@ -97,7 +95,6 @@ class ProfileDetailViewModel extends ReactiveViewModel
rebuildUi(); rebuildUi();
} }
// Phone number // Phone number
void setPhoneNumberFocus() { void setPhoneNumberFocus() {
_focusPhoneNumber = true; _focusPhoneNumber = true;
@ -113,167 +110,165 @@ class ProfileDetailViewModel extends ReactiveViewModel
// Country // Country
// Country // Country
List<String> getCountries() => [ List<String> getCountries() => [
"Afghanistan", "Afghanistan",
"Albania", "Albania",
"Algeria", "Algeria",
"Andorra", "Andorra",
"Angola", "Angola",
"Argentina", "Argentina",
"Armenia", "Armenia",
"Australia", "Australia",
"Austria", "Austria",
"Azerbaijan", "Azerbaijan",
"Bahrain", "Bahrain",
"Bangladesh", "Bangladesh",
"Belarus", "Belarus",
"Belgium", "Belgium",
"Belize", "Belize",
"Benin", "Benin",
"Bhutan", "Bhutan",
"Bolivia", "Bolivia",
"Bosnia and Herzegovina", "Bosnia and Herzegovina",
"Botswana", "Botswana",
"Brazil", "Brazil",
"Brunei", "Brunei",
"Bulgaria", "Bulgaria",
"Burkina Faso", "Burkina Faso",
"Burundi", "Burundi",
"Cambodia", "Cambodia",
"Cameroon", "Cameroon",
"Canada", "Canada",
"Chad", "Chad",
"Chile", "Chile",
"China", "China",
"Colombia", "Colombia",
"Comoros", "Comoros",
"Congo", "Congo",
"Costa Rica", "Costa Rica",
"Croatia", "Croatia",
"Cuba", "Cuba",
"Cyprus", "Cyprus",
"Czech Republic", "Czech Republic",
"Denmark", "Denmark",
"Djibouti", "Djibouti",
"Dominican Republic", "Dominican Republic",
"Ecuador", "Ecuador",
"Egypt", "Egypt",
"El Salvador", "El Salvador",
"Eritrea", "Eritrea",
"Estonia", "Estonia",
"Eswatini", "Eswatini",
"Ethiopia", "Ethiopia",
"Finland", "Finland",
"France", "France",
"Gabon", "Gabon",
"Gambia", "Gambia",
"Georgia", "Georgia",
"Germany", "Germany",
"Ghana", "Ghana",
"Greece", "Greece",
"Guatemala", "Guatemala",
"Guinea", "Guinea",
"Haiti", "Haiti",
"Honduras", "Honduras",
"Hungary", "Hungary",
"Iceland", "Iceland",
"India", "India",
"Indonesia", "Indonesia",
"Iran", "Iran",
"Iraq", "Iraq",
"Ireland", "Ireland",
"Israel", "Israel",
"Italy", "Italy",
"Jamaica", "Jamaica",
"Japan", "Japan",
"Jordan", "Jordan",
"Kazakhstan", "Kazakhstan",
"Kenya", "Kenya",
"Kuwait", "Kuwait",
"Kyrgyzstan", "Kyrgyzstan",
"Laos", "Laos",
"Latvia", "Latvia",
"Lebanon", "Lebanon",
"Liberia", "Liberia",
"Libya", "Libya",
"Lithuania", "Lithuania",
"Luxembourg", "Luxembourg",
"Madagascar", "Madagascar",
"Malawi", "Malawi",
"Malaysia", "Malaysia",
"Maldives", "Maldives",
"Mali", "Mali",
"Malta", "Malta",
"Mexico", "Mexico",
"Moldova", "Moldova",
"Monaco", "Monaco",
"Mongolia", "Mongolia",
"Morocco", "Morocco",
"Mozambique", "Mozambique",
"Myanmar", "Myanmar",
"Namibia", "Namibia",
"Nepal", "Nepal",
"Netherlands", "Netherlands",
"New Zealand", "New Zealand",
"Nicaragua", "Nicaragua",
"Niger", "Niger",
"Nigeria", "Nigeria",
"North Korea", "North Korea",
"Norway", "Norway",
"Oman", "Oman",
"Pakistan", "Pakistan",
"Panama", "Panama",
"Paraguay", "Paraguay",
"Peru", "Peru",
"Philippines", "Philippines",
"Poland", "Poland",
"Portugal", "Portugal",
"Qatar", "Qatar",
"Romania", "Romania",
"Russia", "Russia",
"Rwanda", "Rwanda",
"Saudi Arabia", "Saudi Arabia",
"Senegal", "Senegal",
"Serbia", "Serbia",
"Singapore", "Singapore",
"Slovakia", "Slovakia",
"Slovenia", "Slovenia",
"Somalia", "Somalia",
"South Africa", "South Africa",
"South Korea", "South Korea",
"Spain", "Spain",
"Sri Lanka", "Sri Lanka",
"Sudan", "Sudan",
"Sweden", "Sweden",
"Switzerland", "Switzerland",
"Syria", "Syria",
"Taiwan", "Taiwan",
"Tajikistan", "Tajikistan",
"Tanzania", "Tanzania",
"Thailand", "Thailand",
"Tunisia", "Tunisia",
"Turkey", "Turkey",
"Uganda", "Uganda",
"Ukraine", "Ukraine",
"United Arab Emirates", "United Arab Emirates",
"United Kingdom", "United Kingdom",
"United States", "United States",
"Uruguay", "Uruguay",
"Uzbekistan", "Uzbekistan",
"Venezuela", "Venezuela",
"Vietnam", "Vietnam",
"Yemen", "Yemen",
"Zambia", "Zambia",
"Zimbabwe" "Zimbabwe"
]; ];
void setSelectedCountry(String value) { void setSelectedCountry(String value) {
_selectedCountry = value; _selectedCountry = value;
rebuildUi(); rebuildUi();
} }
// Occupation // Occupation
void setOccupationFocus() { void setOccupationFocus() {
_focusOccupation = true; _focusOccupation = true;

View File

@ -56,7 +56,7 @@ class ProgressView extends StackedView<ProgressViewModel> {
Widget _buildAppbar(ProgressViewModel viewModel) => SmallAppBar( Widget _buildAppbar(ProgressViewModel viewModel) => SmallAppBar(
title: 'My Progress', title: 'My Progress',
showBackButton: true, showBackButton: true,
onTap: viewModel.pop, onPop: viewModel.pop,
); );
Widget _buildContentScrollViewWrapper(ProgressViewModel viewModel) => Widget _buildContentScrollViewWrapper(ProgressViewModel viewModel) =>

View File

@ -11,7 +11,6 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../models/user.dart'; import '../../../models/user.dart';
import '../../../services/google_auth_service.dart'; import '../../../services/google_auth_service.dart';
import '../../../services/smart_auth_service.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
class RegisterViewModel extends ReactiveViewModel class RegisterViewModel extends ReactiveViewModel

View File

@ -51,8 +51,8 @@ class StartupView extends StackedView<StartupViewModel> {
Widget _buildColumn() => Column( Widget _buildColumn() => Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildUpperColumnChildren(), children: _buildUpperColumnChildren(),
); );

View File

@ -1,7 +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/services/authentication_service.dart'; import 'package:yimaru_app/services/authentication_service.dart';
import 'package:yimaru_app/services/in_app_update_service.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../app/app.router.dart'; import '../../../app/app.router.dart';
@ -10,14 +9,12 @@ import '../../../services/api_service.dart';
import '../../../services/image_downloader_service.dart'; import '../../../services/image_downloader_service.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart'; import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
class StartupViewModel extends ReactiveViewModel { class StartupViewModel extends ReactiveViewModel {
// Dependency injection // Dependency injection
final _apiService = locator<ApiService>(); final _apiService = locator<ApiService>();
final _statusChecker = locator<StatusCheckerService>(); final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
final _inAppUpdateService = locator<InAppUpdateService>();
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
final _imageDownloaderService = locator<ImageDownloaderService>(); final _imageDownloaderService = locator<ImageDownloaderService>();
@ -30,15 +27,12 @@ class StartupViewModel extends ReactiveViewModel {
User? get user => _user; User? get user => _user;
// Main startup and navigation logic
// Place anything here that needs to happen before we get into the application
Future runStartupLogic() async { Future runStartupLogic() async {
final loggedIn = await _authenticationService.userLoggedIn(); final loggedIn = await _authenticationService.userLoggedIn();
final firstTimeInstall = await _authenticationService.isFirstTimeInstall(); final firstTimeInstall = await _authenticationService.isFirstTimeInstall();
await _inAppUpdate();
if (firstTimeInstall) { if (firstTimeInstall) {
await _navigationService.replaceWithWelcomeView(); await _navigationService.replaceWithWelcomeView();
} else { } else {
@ -52,63 +46,45 @@ class StartupViewModel extends ReactiveViewModel {
} }
} }
// Navigation // Navigation
Future<void> replaceWithFailure() async => await _navigationService Future<void> replaceWithFailure() async => await _navigationService.replaceWithFailureView(
.replaceWithFailureView(label: 'Check you internet connection'); label: 'Check you internet connection',
onTap: () async => await _getProfileStatus());
Future<void> replaceWithOnboarding() async => Future<void> replaceWithOnboarding() async =>
await _navigationService.replaceWithOnboardingView(); await _navigationService.replaceWithOnboardingView();
Future<void> replaceWithHome() async => Future<void> replaceWithHome() async =>
await _navigationService.replaceWithHomeView(); await _navigationService.replaceWithHomeView();
// Remote api calls // Remote api calls
// In-app update
Future<void> _inAppUpdate() async {
if (await _statusChecker.checkConnection()) {
await _inAppUpdateService.checkForUpdate();
}
}
// Get profile status // Get profile status
Future<void> _getProfileStatus() async { Future<void> _getProfileStatus() async {
final bool? profileCompleted = _user?.profileCompleted;
Map<String, dynamic> response = {}; Map<String, dynamic> response = {};
if (_user?.profileCompleted == null) { if (profileCompleted == null) {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {
print('PATH: 1');
response = await _apiService.getProfileStatus(_user); response = await _apiService.getProfileStatus(_user);
} else { } else {
print('PATH: 2');
await Future.delayed(kDuration);
await replaceWithFailure(); await replaceWithFailure();
} }
} else if (!(_user?.profileCompleted ?? false)) { } else if (!profileCompleted) {
print('PATH: 3');
response = {'data': false, 'status': ResponseStatus.success}; response = {'data': false, 'status': ResponseStatus.success};
} else { } else {
print('PATH: 4');
response = {'data': true, 'status': ResponseStatus.success}; response = {'data': true, 'status': ResponseStatus.success};
} }
if (response['status'] == ResponseStatus.success && !response['data']) { if (response['status'] == ResponseStatus.success && !response['data']) {
print('PATH: 5');
await replaceWithOnboarding(); await replaceWithOnboarding();
} else if (response['status'] == ResponseStatus.success && } else if (response['status'] == ResponseStatus.success &&
response['data']) { response['data']) {
print('PATH: 6');
await saveProfileStatus(response['data']); await saveProfileStatus(response['data']);
await _getProfileData(); await _getProfileData();
await replaceWithHome(); await replaceWithHome();
} else {
await replaceWithFailure();
} }
} }
@ -118,26 +94,24 @@ class StartupViewModel extends ReactiveViewModel {
// Get profile data // Get profile data
Future<void> _getProfileData() async { Future<void> _getProfileData() async {
if (!(_user?.userInfoLoaded ?? false)) { bool? infoLoaded = _user?.userInfoLoaded ?? false;
bool? profileCompleted = _user?.profileCompleted ?? false;
if (!infoLoaded) {
Map<String, dynamic> response = {}; Map<String, dynamic> response = {};
if (_user?.profileCompleted != null && if (profileCompleted) {
(_user?.profileCompleted ?? false)) { response = await _apiService.getProfileData(_user?.userId);
if (await _statusChecker.checkConnection()) {
response = await _apiService.getProfileData(_user?.userId);
if (response['status'] == ResponseStatus.success) { if (response['status'] == ResponseStatus.success) {
User user = response['data'] as User; User user = response['data'] as User;
await _authenticationService.saveUserData(user); await _authenticationService.saveUserData(user);
String image = String image =
await _imageDownloaderService.downloader(user.profilePicture); await _imageDownloaderService.downloader(user.profilePicture);
await _authenticationService.saveProfilePicture(image); await _authenticationService.saveProfilePicture(image);
}
} else {
await replaceWithFailure();
} }
} }
} }

View File

@ -53,7 +53,7 @@ class SupportView extends StackedView<SupportViewModel> {
Widget _buildAppbar(SupportViewModel viewModel) => SmallAppBar( Widget _buildAppbar(SupportViewModel viewModel) => SmallAppBar(
title: 'Need Help?', title: 'Need Help?',
showBackButton: true, showBackButton: true,
onTap: viewModel.pop, onPop: viewModel.pop,
); );
Widget _buildContentWrapper(SupportViewModel viewModel) => Widget _buildContentWrapper(SupportViewModel viewModel) =>

View File

@ -49,7 +49,7 @@ class TelegramSupportView extends StackedView<TelegramSupportViewModel> {
); );
Widget _buildAppbar(TelegramSupportViewModel viewModel) => SmallAppBar( Widget _buildAppbar(TelegramSupportViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
title: 'Telegram Support', title: 'Telegram Support',
); );

View File

@ -57,7 +57,7 @@ class TermsAndConditionsView extends StackedView<TermsAndConditionsViewModel> {
); );
Widget _buildAppbar(TermsAndConditionsViewModel viewModel) => SmallAppBar( Widget _buildAppbar(TermsAndConditionsViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
title: 'Terms and Conditions', title: 'Terms and Conditions',
); );

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart'; import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import 'package:yimaru_app/ui/widgets/refresh_button.dart';
import '../../../common/app_colors.dart'; import '../common/app_colors.dart';
import '../../../widgets/large_app_bar.dart'; import 'large_app_bar.dart';
import '../../../widgets/refresh_button.dart';
class AssessmentLoadingScreen extends StatelessWidget { class AssessmentLoadingScreen extends StatelessWidget {
final bool isEmpty; final bool isEmpty;

View File

@ -55,7 +55,7 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
expandedCrossAxisAlignment: CrossAxisAlignment.start, expandedCrossAxisAlignment: CrossAxisAlignment.start,
collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1), collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1),
childrenPadding: const EdgeInsets.fromLTRB(15, 0, 15, 15), childrenPadding: const EdgeInsets.fromLTRB(15, 0, 15, 15),
// enabled: status != ProgressStatuses.pending, // enabled: (lesson.access?.isAccessible ?? false),
// backgroundColor: ProgressStatuses.pending == status // backgroundColor: ProgressStatuses.pending == status
// ? kcPrimaryColor.withOpacity(0.1) // ? kcPrimaryColor.withOpacity(0.1)
// : kcGreen.withOpacity(0.1), // : kcGreen.withOpacity(0.1),

View File

@ -56,9 +56,7 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
{required BuildContext context, {required BuildContext context,
required LearnModuleViewModel viewModel}) => required LearnModuleViewModel viewModel}) =>
ExpansionTile( ExpansionTile(
enabled: true,
textColor: kcDarkGrey, textColor: kcDarkGrey,
showTrailingIcon: true,
initiallyExpanded: true, initiallyExpanded: true,
subtitle: _buildContent(), subtitle: _buildContent(),
title: _buildTitleWrapper(), title: _buildTitleWrapper(),
@ -69,13 +67,12 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
shape: Border.all(color: kcTransparent), shape: Border.all(color: kcTransparent),
expandedAlignment: Alignment.centerLeft, expandedAlignment: Alignment.centerLeft,
collapsedBackgroundColor: kcBackgroundColor, collapsedBackgroundColor: kcBackgroundColor,
enabled: (module.access?.isAccessible ?? false),
controlAffinity: ListTileControlAffinity.trailing, controlAffinity: ListTileControlAffinity.trailing,
expandedCrossAxisAlignment: CrossAxisAlignment.start, expandedCrossAxisAlignment: CrossAxisAlignment.start,
tilePadding: const EdgeInsets.symmetric(horizontal: 15), tilePadding: const EdgeInsets.symmetric(horizontal: 15),
childrenPadding: const EdgeInsets.fromLTRB(70, 0, 15, 15), childrenPadding: const EdgeInsets.fromLTRB(70, 0, 15, 15),
// enabled:(module.access?.isAccessible ?? false) , showTrailingIcon: (module.access?.isAccessible ?? false) ? true : false,
// showTrailingIcon: status != ProgressStatuses.pending ? true : false,
//initiallyExpanded: status == ProgressStatuses.started ? true : false,
children: children:
_buildExpansionTileChildren(context: context, viewModel: viewModel), _buildExpansionTileChildren(context: context, viewModel: viewModel),
); );
@ -209,9 +206,9 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
onTap: viewModel.pop, onTap: viewModel.pop,
); );
// Widget _buildContainerShaderState() => status == ProgressStatuses.pending Widget _buildContainerShaderState() => !(module.access?.isAccessible ?? false)
// ? _buildContainerShaderWrapper() ? _buildContainerShaderWrapper()
// : Container(); : Container();
Widget _buildContainerShaderWrapper() => Positioned.fill( Widget _buildContainerShaderWrapper() => Positioned.fill(
child: _buildContainerShader(), child: _buildContainerShader(),

View File

@ -4,7 +4,6 @@ import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
class LearnPracticeTipSection extends ViewModelWidget<LearnPracticeViewModel> { class LearnPracticeTipSection extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeTipSection({super.key}); const LearnPracticeTipSection({super.key});

View File

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import '../common/ui_helpers.dart';
class NoDataIndicator extends StatelessWidget {
final String title;
final GestureTapCallback? onTap;
const NoDataIndicator({super.key, this.onTap, required this.title});
@override
Widget build(BuildContext context) => _buildColumn();
Widget _buildColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: _buildColumnChildren(),
);
List<Widget> _buildColumnChildren() => [
_buildIconWrapper(),
verticalSpaceMedium,
_buildTitle(),
];
Widget _buildTitle() => Text(
title,
style: style16P600,
);
Widget _buildIconWrapper() => GestureDetector(
onTap: onTap,
child: _buildIcon(),
);
Widget _buildIcon() => const Icon(
Icons.replay,
size: 75,
color: kcPrimaryColor,
);
}

View File

@ -5,10 +5,10 @@ import 'package:yimaru_app/ui/widgets/custom_back_button.dart';
class SmallAppBar extends StatelessWidget { class SmallAppBar extends StatelessWidget {
final String? title; final String? title;
final bool showBackButton; final bool showBackButton;
final GestureTapCallback? onTap; final GestureTapCallback? onPop;
const SmallAppBar( const SmallAppBar(
{super.key, this.onTap, this.title, required this.showBackButton}); {super.key, this.onPop, this.title, required this.showBackButton});
@override @override
Widget build(BuildContext context) => _buildAppBar(); Widget build(BuildContext context) => _buildAppBar();
@ -28,7 +28,7 @@ class SmallAppBar extends StatelessWidget {
child: _buildBackButton(), child: _buildBackButton(),
); );
Widget _buildBackButton() => CustomBackButton(onTap: onTap); Widget _buildBackButton() => CustomBackButton(onTap: onPop);
Widget _buildTitleWrapper() => Align( Widget _buildTitleWrapper() => Align(
alignment: Alignment.center, alignment: Alignment.center,

View File

@ -1,5 +1,5 @@
name: yimaru_app name: yimaru_app
version: 0.1.8+10 version: 0.1.9+11
publish_to: 'none' publish_to: 'none'
description: A new Flutter project. description: A new Flutter project.

View File

@ -8,48 +8,49 @@ import 'dart:ui' as _i10;
import 'package:audioplayers/audioplayers.dart' as _i4; import 'package:audioplayers/audioplayers.dart' as _i4;
import 'package:dio/dio.dart' as _i2; import 'package:dio/dio.dart' as _i2;
import 'package:firebase_messaging/firebase_messaging.dart' as _i39; import 'package:firebase_messaging/firebase_messaging.dart' as _i40;
import 'package:flutter/material.dart' as _i8; import 'package:flutter/material.dart' as _i8;
import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/mockito.dart' as _i1;
import 'package:mockito/src/dummies.dart' as _i7; import 'package:mockito/src/dummies.dart' as _i7;
import 'package:permission_handler/permission_handler.dart' as _i34; import 'package:permission_handler/permission_handler.dart' as _i35;
import 'package:stacked_services/stacked_services.dart' as _i6; import 'package:stacked_services/stacked_services.dart' as _i6;
import 'package:waveform_recorder/waveform_recorder.dart' as _i5; import 'package:waveform_recorder/waveform_recorder.dart' as _i5;
import 'package:yimaru_app/models/category.dart' as _i21; import 'package:yimaru_app/models/assessment.dart' as _i14;
import 'package:yimaru_app/models/course.dart' as _i26; import 'package:yimaru_app/models/assessment_question.dart' as _i15;
import 'package:yimaru_app/models/course_detail.dart' as _i42; import 'package:yimaru_app/models/category.dart' as _i22;
import 'package:yimaru_app/models/course_lesson.dart' as _i24; import 'package:yimaru_app/models/course.dart' as _i27;
import 'package:yimaru_app/models/course_progress.dart' as _i23; import 'package:yimaru_app/models/course_detail.dart' as _i43;
import 'package:yimaru_app/models/learn_course.dart' as _i16; import 'package:yimaru_app/models/course_lesson.dart' as _i25;
import 'package:yimaru_app/models/learn_lesson.dart' as _i19; import 'package:yimaru_app/models/course_progress.dart' as _i24;
import 'package:yimaru_app/models/learn_module.dart' as _i18; import 'package:yimaru_app/models/learn_course.dart' as _i17;
import 'package:yimaru_app/models/learn_practice.dart' as _i17; import 'package:yimaru_app/models/learn_lesson.dart' as _i20;
import 'package:yimaru_app/models/learn_program.dart' as _i15; import 'package:yimaru_app/models/learn_module.dart' as _i19;
import 'package:yimaru_app/models/learn_question.dart' as _i20; import 'package:yimaru_app/models/learn_practice.dart' as _i18;
import 'package:yimaru_app/models/lesson.dart' as _i30; import 'package:yimaru_app/models/learn_program.dart' as _i16;
import 'package:yimaru_app/models/level.dart' as _i27; import 'package:yimaru_app/models/learn_question.dart' as _i21;
import 'package:yimaru_app/models/module.dart' as _i28; import 'package:yimaru_app/models/lesson.dart' as _i31;
import 'package:yimaru_app/models/practice.dart' as _i25; import 'package:yimaru_app/models/level.dart' as _i28;
import 'package:yimaru_app/models/question.dart' as _i14; import 'package:yimaru_app/models/module.dart' as _i29;
import 'package:yimaru_app/models/subcategory.dart' as _i22; import 'package:yimaru_app/models/practice.dart' as _i26;
import 'package:yimaru_app/models/submodule.dart' as _i29; import 'package:yimaru_app/models/subcategory.dart' as _i23;
import 'package:yimaru_app/models/submodule.dart' as _i30;
import 'package:yimaru_app/models/user.dart' as _i12; import 'package:yimaru_app/models/user.dart' as _i12;
import 'package:yimaru_app/services/api_service.dart' as _i13; import 'package:yimaru_app/services/api_service.dart' as _i13;
import 'package:yimaru_app/services/audio_player_service.dart' as _i43; import 'package:yimaru_app/services/audio_player_service.dart' as _i44;
import 'package:yimaru_app/services/authentication_service.dart' as _i11; import 'package:yimaru_app/services/authentication_service.dart' as _i11;
import 'package:yimaru_app/services/course_service.dart' as _i41; import 'package:yimaru_app/services/course_service.dart' as _i42;
import 'package:yimaru_app/services/dio_service.dart' as _i31; import 'package:yimaru_app/services/dio_service.dart' as _i32;
import 'package:yimaru_app/services/google_auth_service.dart' as _i36; import 'package:yimaru_app/services/google_auth_service.dart' as _i37;
import 'package:yimaru_app/services/image_downloader_service.dart' as _i37; import 'package:yimaru_app/services/image_downloader_service.dart' as _i38;
import 'package:yimaru_app/services/image_picker_service.dart' as _i35; import 'package:yimaru_app/services/image_picker_service.dart' as _i36;
import 'package:yimaru_app/services/in_app_update_service.dart' as _i46; import 'package:yimaru_app/services/in_app_update_service.dart' as _i47;
import 'package:yimaru_app/services/notification_service.dart' as _i38; import 'package:yimaru_app/services/notification_service.dart' as _i39;
import 'package:yimaru_app/services/permission_handler_service.dart' as _i33; import 'package:yimaru_app/services/permission_handler_service.dart' as _i34;
import 'package:yimaru_app/services/secure_storage_service.dart' as _i3; import 'package:yimaru_app/services/secure_storage_service.dart' as _i3;
import 'package:yimaru_app/services/smart_auth_service.dart' as _i40; import 'package:yimaru_app/services/smart_auth_service.dart' as _i41;
import 'package:yimaru_app/services/status_checker_service.dart' as _i32; import 'package:yimaru_app/services/status_checker_service.dart' as _i33;
import 'package:yimaru_app/services/voice_recorder_service.dart' as _i44; import 'package:yimaru_app/services/voice_recorder_service.dart' as _i45;
import 'package:yimaru_app/ui/common/enmus.dart' as _i45; import 'package:yimaru_app/ui/common/enmus.dart' as _i46;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_redundant_argument_values
@ -1125,168 +1126,183 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
) as _i9.Future<Map<String, dynamic>>); ) as _i9.Future<Map<String, dynamic>>);
@override @override
_i9.Future<List<_i14.Question>> getAssessments() => (super.noSuchMethod( _i9.Future<List<_i14.Assessment>> getAssessments() => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getAssessments, #getAssessments,
[], [],
), ),
returnValue: _i9.Future<List<_i14.Question>>.value(<_i14.Question>[]), returnValue:
_i9.Future<List<_i14.Assessment>>.value(<_i14.Assessment>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i14.Question>>.value(<_i14.Question>[]), _i9.Future<List<_i14.Assessment>>.value(<_i14.Assessment>[]),
) as _i9.Future<List<_i14.Question>>); ) as _i9.Future<List<_i14.Assessment>>);
@override @override
_i9.Future<List<_i15.LearnProgram>> getLearnPrograms() => (super.noSuchMethod( _i9.Future<List<_i15.AssessmentQuestion>> getAssessmentQuestions(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getAssessmentQuestions,
[id],
),
returnValue: _i9.Future<List<_i15.AssessmentQuestion>>.value(
<_i15.AssessmentQuestion>[]),
returnValueForMissingStub:
_i9.Future<List<_i15.AssessmentQuestion>>.value(
<_i15.AssessmentQuestion>[]),
) as _i9.Future<List<_i15.AssessmentQuestion>>);
@override
_i9.Future<List<_i16.LearnProgram>> getLearnPrograms() => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getLearnPrograms, #getLearnPrograms,
[], [],
), ),
returnValue: returnValue:
_i9.Future<List<_i15.LearnProgram>>.value(<_i15.LearnProgram>[]), _i9.Future<List<_i16.LearnProgram>>.value(<_i16.LearnProgram>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i15.LearnProgram>>.value(<_i15.LearnProgram>[]), _i9.Future<List<_i16.LearnProgram>>.value(<_i16.LearnProgram>[]),
) as _i9.Future<List<_i15.LearnProgram>>); ) as _i9.Future<List<_i16.LearnProgram>>);
@override @override
_i9.Future<List<_i16.LearnCourse>> getLearnCourse(int? id) => _i9.Future<List<_i17.LearnCourse>> getLearnCourse(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getLearnCourse, #getLearnCourse,
[id], [id],
), ),
returnValue: returnValue:
_i9.Future<List<_i16.LearnCourse>>.value(<_i16.LearnCourse>[]), _i9.Future<List<_i17.LearnCourse>>.value(<_i17.LearnCourse>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i16.LearnCourse>>.value(<_i16.LearnCourse>[]), _i9.Future<List<_i17.LearnCourse>>.value(<_i17.LearnCourse>[]),
) as _i9.Future<List<_i16.LearnCourse>>); ) as _i9.Future<List<_i17.LearnCourse>>);
@override @override
_i9.Future<List<_i17.LearnPractice>> getLearnCoursePractices(int? id) => _i9.Future<List<_i18.LearnPractice>> getLearnCoursePractices(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getLearnCoursePractices, #getLearnCoursePractices,
[id], [id],
), ),
returnValue: returnValue:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]), _i9.Future<List<_i18.LearnPractice>>.value(<_i18.LearnPractice>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]), _i9.Future<List<_i18.LearnPractice>>.value(<_i18.LearnPractice>[]),
) as _i9.Future<List<_i17.LearnPractice>>); ) as _i9.Future<List<_i18.LearnPractice>>);
@override @override
_i9.Future<List<_i18.LearnModule>> getLearnModules(int? id) => _i9.Future<List<_i19.LearnModule>> getLearnModules(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getLearnModules, #getLearnModules,
[id], [id],
), ),
returnValue: returnValue:
_i9.Future<List<_i18.LearnModule>>.value(<_i18.LearnModule>[]), _i9.Future<List<_i19.LearnModule>>.value(<_i19.LearnModule>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i18.LearnModule>>.value(<_i18.LearnModule>[]), _i9.Future<List<_i19.LearnModule>>.value(<_i19.LearnModule>[]),
) as _i9.Future<List<_i18.LearnModule>>); ) as _i9.Future<List<_i19.LearnModule>>);
@override @override
_i9.Future<List<_i17.LearnPractice>> getLearnModulePractices(int? id) => _i9.Future<List<_i18.LearnPractice>> getLearnModulePractices(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getLearnModulePractices, #getLearnModulePractices,
[id], [id],
), ),
returnValue: returnValue:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]), _i9.Future<List<_i18.LearnPractice>>.value(<_i18.LearnPractice>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]), _i9.Future<List<_i18.LearnPractice>>.value(<_i18.LearnPractice>[]),
) as _i9.Future<List<_i17.LearnPractice>>); ) as _i9.Future<List<_i18.LearnPractice>>);
@override @override
_i9.Future<List<_i19.LearnLesson>> getLearnLessons(int? id) => _i9.Future<List<_i20.LearnLesson>> getLearnLessons(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getLearnLessons, #getLearnLessons,
[id], [id],
), ),
returnValue: returnValue:
_i9.Future<List<_i19.LearnLesson>>.value(<_i19.LearnLesson>[]), _i9.Future<List<_i20.LearnLesson>>.value(<_i20.LearnLesson>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i19.LearnLesson>>.value(<_i19.LearnLesson>[]), _i9.Future<List<_i20.LearnLesson>>.value(<_i20.LearnLesson>[]),
) as _i9.Future<List<_i19.LearnLesson>>); ) as _i9.Future<List<_i20.LearnLesson>>);
@override @override
_i9.Future<List<_i17.LearnPractice>> getLearnLessonPractices(int? id) => _i9.Future<List<_i18.LearnPractice>> getLearnLessonPractices(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getLearnLessonPractices, #getLearnLessonPractices,
[id], [id],
), ),
returnValue: returnValue:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]), _i9.Future<List<_i18.LearnPractice>>.value(<_i18.LearnPractice>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]), _i9.Future<List<_i18.LearnPractice>>.value(<_i18.LearnPractice>[]),
) as _i9.Future<List<_i17.LearnPractice>>); ) as _i9.Future<List<_i18.LearnPractice>>);
@override @override
_i9.Future<List<_i20.LearnQuestion>> getLearnQuestions(int? id) => _i9.Future<List<_i21.LearnQuestion>> getLearnQuestions(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getLearnQuestions, #getLearnQuestions,
[id], [id],
), ),
returnValue: returnValue:
_i9.Future<List<_i20.LearnQuestion>>.value(<_i20.LearnQuestion>[]), _i9.Future<List<_i21.LearnQuestion>>.value(<_i21.LearnQuestion>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i20.LearnQuestion>>.value(<_i20.LearnQuestion>[]), _i9.Future<List<_i21.LearnQuestion>>.value(<_i21.LearnQuestion>[]),
) as _i9.Future<List<_i20.LearnQuestion>>); ) as _i9.Future<List<_i21.LearnQuestion>>);
@override @override
_i9.Future<List<_i21.Category>> getCategories() => (super.noSuchMethod( _i9.Future<List<_i22.Category>> getCategories() => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getCategories, #getCategories,
[], [],
), ),
returnValue: _i9.Future<List<_i21.Category>>.value(<_i21.Category>[]), returnValue: _i9.Future<List<_i22.Category>>.value(<_i22.Category>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i21.Category>>.value(<_i21.Category>[]), _i9.Future<List<_i22.Category>>.value(<_i22.Category>[]),
) as _i9.Future<List<_i21.Category>>); ) as _i9.Future<List<_i22.Category>>);
@override @override
_i9.Future<List<_i22.Subcategory>> getSubcategories(int? id) => _i9.Future<List<_i23.Subcategory>> getSubcategories(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getSubcategories, #getSubcategories,
[id], [id],
), ),
returnValue: returnValue:
_i9.Future<List<_i22.Subcategory>>.value(<_i22.Subcategory>[]), _i9.Future<List<_i23.Subcategory>>.value(<_i23.Subcategory>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i22.Subcategory>>.value(<_i22.Subcategory>[]), _i9.Future<List<_i23.Subcategory>>.value(<_i23.Subcategory>[]),
) as _i9.Future<List<_i22.Subcategory>>); ) as _i9.Future<List<_i23.Subcategory>>);
@override @override
_i9.Future<List<_i23.CourseProgress>> getCourseProgress(int? id) => _i9.Future<List<_i24.CourseProgress>> getCourseProgress(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getCourseProgress, #getCourseProgress,
[id], [id],
), ),
returnValue: _i9.Future<List<_i23.CourseProgress>>.value( returnValue: _i9.Future<List<_i24.CourseProgress>>.value(
<_i23.CourseProgress>[]), <_i24.CourseProgress>[]),
returnValueForMissingStub: _i9.Future<List<_i23.CourseProgress>>.value( returnValueForMissingStub: _i9.Future<List<_i24.CourseProgress>>.value(
<_i23.CourseProgress>[]), <_i24.CourseProgress>[]),
) as _i9.Future<List<_i23.CourseProgress>>); ) as _i9.Future<List<_i24.CourseProgress>>);
@override @override
_i9.Future<List<_i24.CourseLesson>> getCourseLessons(int? id) => _i9.Future<List<_i25.CourseLesson>> getCourseLessons(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getCourseLessons, #getCourseLessons,
[id], [id],
), ),
returnValue: returnValue:
_i9.Future<List<_i24.CourseLesson>>.value(<_i24.CourseLesson>[]), _i9.Future<List<_i25.CourseLesson>>.value(<_i25.CourseLesson>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i24.CourseLesson>>.value(<_i24.CourseLesson>[]), _i9.Future<List<_i25.CourseLesson>>.value(<_i25.CourseLesson>[]),
) as _i9.Future<List<_i24.CourseLesson>>); ) as _i9.Future<List<_i25.CourseLesson>>);
@override @override
_i9.Future<Map<String, dynamic>> completeLesson(int? id) => _i9.Future<Map<String, dynamic>> completeLesson(int? id) =>
@ -1302,130 +1318,136 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
) as _i9.Future<Map<String, dynamic>>); ) as _i9.Future<Map<String, dynamic>>);
@override @override
_i9.Future<List<_i25.Practice>> getCoursePractices(int? id) => _i9.Future<List<_i26.Practice>> getCoursePractices(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getCoursePractices, #getCoursePractices,
[id], [id],
), ),
returnValue: _i9.Future<List<_i25.Practice>>.value(<_i25.Practice>[]), returnValue: _i9.Future<List<_i26.Practice>>.value(<_i26.Practice>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i25.Practice>>.value(<_i25.Practice>[]), _i9.Future<List<_i26.Practice>>.value(<_i26.Practice>[]),
) as _i9.Future<List<_i25.Practice>>); ) as _i9.Future<List<_i26.Practice>>);
@override @override
_i9.Future<List<_i14.Question>> getCoursePracticeQuestions(int? id) => _i9.Future<List<_i15.AssessmentQuestion>> getCoursePracticeQuestions(
int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getCoursePracticeQuestions, #getCoursePracticeQuestions,
[id], [id],
), ),
returnValue: _i9.Future<List<_i14.Question>>.value(<_i14.Question>[]), returnValue: _i9.Future<List<_i15.AssessmentQuestion>>.value(
<_i15.AssessmentQuestion>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i14.Question>>.value(<_i14.Question>[]), _i9.Future<List<_i15.AssessmentQuestion>>.value(
) as _i9.Future<List<_i14.Question>>); <_i15.AssessmentQuestion>[]),
) as _i9.Future<List<_i15.AssessmentQuestion>>);
@override @override
_i9.Future<_i14.Question?> getCoursePracticeQuestion(int? id) => _i9.Future<_i15.AssessmentQuestion?> getCoursePracticeQuestion(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getCoursePracticeQuestion, #getCoursePracticeQuestion,
[id], [id],
), ),
returnValue: _i9.Future<_i14.Question?>.value(), returnValue: _i9.Future<_i15.AssessmentQuestion?>.value(),
returnValueForMissingStub: _i9.Future<_i14.Question?>.value(), returnValueForMissingStub: _i9.Future<_i15.AssessmentQuestion?>.value(),
) as _i9.Future<_i14.Question?>); ) as _i9.Future<_i15.AssessmentQuestion?>);
@override @override
_i9.Future<List<_i22.Subcategory>> getLearnSubcategories() => _i9.Future<List<_i23.Subcategory>> getLearnSubcategories() =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getLearnSubcategories, #getLearnSubcategories,
[], [],
), ),
returnValue: returnValue:
_i9.Future<List<_i22.Subcategory>>.value(<_i22.Subcategory>[]), _i9.Future<List<_i23.Subcategory>>.value(<_i23.Subcategory>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i22.Subcategory>>.value(<_i22.Subcategory>[]), _i9.Future<List<_i23.Subcategory>>.value(<_i23.Subcategory>[]),
) as _i9.Future<List<_i22.Subcategory>>); ) as _i9.Future<List<_i23.Subcategory>>);
@override @override
_i9.Future<List<_i26.Course>> getCourses(int? id) => (super.noSuchMethod( _i9.Future<List<_i27.Course>> getCourses(int? id) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getCourses, #getCourses,
[id], [id],
), ),
returnValue: _i9.Future<List<_i26.Course>>.value(<_i26.Course>[]), returnValue: _i9.Future<List<_i27.Course>>.value(<_i27.Course>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i26.Course>>.value(<_i26.Course>[]), _i9.Future<List<_i27.Course>>.value(<_i27.Course>[]),
) as _i9.Future<List<_i26.Course>>); ) as _i9.Future<List<_i27.Course>>);
@override @override
_i9.Future<List<_i27.Level>> getLevels(int? id) => (super.noSuchMethod( _i9.Future<List<_i28.Level>> getLevels(int? id) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getLevels, #getLevels,
[id], [id],
), ),
returnValue: _i9.Future<List<_i27.Level>>.value(<_i27.Level>[]), returnValue: _i9.Future<List<_i28.Level>>.value(<_i28.Level>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i27.Level>>.value(<_i27.Level>[]), _i9.Future<List<_i28.Level>>.value(<_i28.Level>[]),
) as _i9.Future<List<_i27.Level>>); ) as _i9.Future<List<_i28.Level>>);
@override @override
_i9.Future<List<_i28.Module>> getModules(int? id) => (super.noSuchMethod( _i9.Future<List<_i29.Module>> getModules(int? id) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getModules, #getModules,
[id], [id],
), ),
returnValue: _i9.Future<List<_i28.Module>>.value(<_i28.Module>[]), returnValue: _i9.Future<List<_i29.Module>>.value(<_i29.Module>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i28.Module>>.value(<_i28.Module>[]), _i9.Future<List<_i29.Module>>.value(<_i29.Module>[]),
) as _i9.Future<List<_i28.Module>>); ) as _i9.Future<List<_i29.Module>>);
@override @override
_i9.Future<List<_i29.Submodule>> getSubmodules(int? id) => _i9.Future<List<_i30.Submodule>> getSubmodules(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getSubmodules, #getSubmodules,
[id], [id],
), ),
returnValue: _i9.Future<List<_i29.Submodule>>.value(<_i29.Submodule>[]), returnValue: _i9.Future<List<_i30.Submodule>>.value(<_i30.Submodule>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i29.Submodule>>.value(<_i29.Submodule>[]), _i9.Future<List<_i30.Submodule>>.value(<_i30.Submodule>[]),
) as _i9.Future<List<_i29.Submodule>>); ) as _i9.Future<List<_i30.Submodule>>);
@override @override
_i9.Future<List<_i30.Lesson>> getLessons(int? id) => (super.noSuchMethod( _i9.Future<List<_i31.Lesson>> getLessons(int? id) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getLessons, #getLessons,
[id], [id],
), ),
returnValue: _i9.Future<List<_i30.Lesson>>.value(<_i30.Lesson>[]), returnValue: _i9.Future<List<_i31.Lesson>>.value(<_i31.Lesson>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i30.Lesson>>.value(<_i30.Lesson>[]), _i9.Future<List<_i31.Lesson>>.value(<_i31.Lesson>[]),
) as _i9.Future<List<_i30.Lesson>>); ) as _i9.Future<List<_i31.Lesson>>);
@override @override
_i9.Future<List<_i25.Practice>> getPractices(int? id) => (super.noSuchMethod( _i9.Future<List<_i26.Practice>> getPractices(int? id) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getPractices, #getPractices,
[id], [id],
), ),
returnValue: _i9.Future<List<_i25.Practice>>.value(<_i25.Practice>[]), returnValue: _i9.Future<List<_i26.Practice>>.value(<_i26.Practice>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i25.Practice>>.value(<_i25.Practice>[]), _i9.Future<List<_i26.Practice>>.value(<_i26.Practice>[]),
) as _i9.Future<List<_i25.Practice>>); ) as _i9.Future<List<_i26.Practice>>);
@override @override
_i9.Future<List<_i14.Question>> getQuestions(int? id) => (super.noSuchMethod( _i9.Future<List<_i15.AssessmentQuestion>> getQuestions(int? id) =>
(super.noSuchMethod(
Invocation.method( Invocation.method(
#getQuestions, #getQuestions,
[id], [id],
), ),
returnValue: _i9.Future<List<_i14.Question>>.value(<_i14.Question>[]), returnValue: _i9.Future<List<_i15.AssessmentQuestion>>.value(
<_i15.AssessmentQuestion>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i14.Question>>.value(<_i14.Question>[]), _i9.Future<List<_i15.AssessmentQuestion>>.value(
) as _i9.Future<List<_i14.Question>>); <_i15.AssessmentQuestion>[]),
) as _i9.Future<List<_i15.AssessmentQuestion>>);
} }
/// A class which mocks [SecureStorageService]. /// A class which mocks [SecureStorageService].
@ -1528,7 +1550,7 @@ class MockSecureStorageService extends _i1.Mock
/// A class which mocks [DioService]. /// A class which mocks [DioService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockDioService extends _i1.Mock implements _i31.DioService { class MockDioService extends _i1.Mock implements _i32.DioService {
@override @override
_i2.Dio get dio => (super.noSuchMethod( _i2.Dio get dio => (super.noSuchMethod(
Invocation.getter(#dio), Invocation.getter(#dio),
@ -1547,7 +1569,7 @@ class MockDioService extends _i1.Mock implements _i31.DioService {
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockStatusCheckerService extends _i1.Mock class MockStatusCheckerService extends _i1.Mock
implements _i32.StatusCheckerService { implements _i33.StatusCheckerService {
@override @override
_i3.SecureStorageService get storage => (super.noSuchMethod( _i3.SecureStorageService get storage => (super.noSuchMethod(
Invocation.getter(#storage), Invocation.getter(#storage),
@ -1583,40 +1605,40 @@ class MockStatusCheckerService extends _i1.Mock
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockPermissionHandlerService extends _i1.Mock class MockPermissionHandlerService extends _i1.Mock
implements _i33.PermissionHandlerService { implements _i34.PermissionHandlerService {
@override @override
_i9.Future<_i34.PermissionStatus> requestPermission( _i9.Future<_i35.PermissionStatus> requestPermission(
_i34.Permission? requestedPermission) => _i35.Permission? requestedPermission) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#requestPermission, #requestPermission,
[requestedPermission], [requestedPermission],
), ),
returnValue: _i9.Future<_i34.PermissionStatus>.value( returnValue: _i9.Future<_i35.PermissionStatus>.value(
_i34.PermissionStatus.denied), _i35.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i34.PermissionStatus>.value( returnValueForMissingStub: _i9.Future<_i35.PermissionStatus>.value(
_i34.PermissionStatus.denied), _i35.PermissionStatus.denied),
) as _i9.Future<_i34.PermissionStatus>); ) as _i9.Future<_i35.PermissionStatus>);
@override @override
_i9.Future<_i34.PermissionStatus> request(_i34.Permission? permission) => _i9.Future<_i35.PermissionStatus> request(_i35.Permission? permission) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#request, #request,
[permission], [permission],
), ),
returnValue: _i9.Future<_i34.PermissionStatus>.value( returnValue: _i9.Future<_i35.PermissionStatus>.value(
_i34.PermissionStatus.denied), _i35.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i34.PermissionStatus>.value( returnValueForMissingStub: _i9.Future<_i35.PermissionStatus>.value(
_i34.PermissionStatus.denied), _i35.PermissionStatus.denied),
) as _i9.Future<_i34.PermissionStatus>); ) as _i9.Future<_i35.PermissionStatus>);
} }
/// A class which mocks [ImagePickerService]. /// A class which mocks [ImagePickerService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockImagePickerService extends _i1.Mock class MockImagePickerService extends _i1.Mock
implements _i35.ImagePickerService { implements _i36.ImagePickerService {
@override @override
_i9.Future<String?> gallery() => (super.noSuchMethod( _i9.Future<String?> gallery() => (super.noSuchMethod(
Invocation.method( Invocation.method(
@ -1641,7 +1663,7 @@ class MockImagePickerService extends _i1.Mock
/// A class which mocks [GoogleAuthService]. /// A class which mocks [GoogleAuthService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockGoogleAuthService extends _i1.Mock implements _i36.GoogleAuthService { class MockGoogleAuthService extends _i1.Mock implements _i37.GoogleAuthService {
@override @override
int get listenersCount => (super.noSuchMethod( int get listenersCount => (super.noSuchMethod(
Invocation.getter(#listenersCount), Invocation.getter(#listenersCount),
@ -1711,7 +1733,7 @@ class MockGoogleAuthService extends _i1.Mock implements _i36.GoogleAuthService {
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockImageDownloaderService extends _i1.Mock class MockImageDownloaderService extends _i1.Mock
implements _i37.ImageDownloaderService { implements _i38.ImageDownloaderService {
@override @override
_i9.Future<String> downloader(String? networkImage) => (super.noSuchMethod( _i9.Future<String> downloader(String? networkImage) => (super.noSuchMethod(
Invocation.method( Invocation.method(
@ -1740,7 +1762,7 @@ class MockImageDownloaderService extends _i1.Mock
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockNotificationService extends _i1.Mock class MockNotificationService extends _i1.Mock
implements _i38.NotificationService { implements _i39.NotificationService {
@override @override
_i9.Future<void> initialize() => (super.noSuchMethod( _i9.Future<void> initialize() => (super.noSuchMethod(
Invocation.method( Invocation.method(
@ -1762,7 +1784,7 @@ class MockNotificationService extends _i1.Mock
) as _i9.Future<void>); ) as _i9.Future<void>);
@override @override
_i9.Future<void> showNotification(_i39.RemoteMessage? message) => _i9.Future<void> showNotification(_i40.RemoteMessage? message) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#showNotification, #showNotification,
@ -1796,7 +1818,7 @@ class MockNotificationService extends _i1.Mock
/// A class which mocks [SmartAuthService]. /// A class which mocks [SmartAuthService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockSmartAuthService extends _i1.Mock implements _i40.SmartAuthService { class MockSmartAuthService extends _i1.Mock implements _i41.SmartAuthService {
@override @override
bool get listenForMultipleSms => (super.noSuchMethod( bool get listenForMultipleSms => (super.noSuchMethod(
Invocation.getter(#listenForMultipleSms), Invocation.getter(#listenForMultipleSms),
@ -1828,26 +1850,26 @@ class MockSmartAuthService extends _i1.Mock implements _i40.SmartAuthService {
/// A class which mocks [CourseService]. /// A class which mocks [CourseService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockCourseService extends _i1.Mock implements _i41.CourseService { class MockCourseService extends _i1.Mock implements _i42.CourseService {
@override @override
_i9.Future<List<_i42.CourseDetail>> getCoursesDetail(int? id) => _i9.Future<List<_i43.CourseDetail>> getCoursesDetail(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getCoursesDetail, #getCoursesDetail,
[id], [id],
), ),
returnValue: returnValue:
_i9.Future<List<_i42.CourseDetail>>.value(<_i42.CourseDetail>[]), _i9.Future<List<_i43.CourseDetail>>.value(<_i43.CourseDetail>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i42.CourseDetail>>.value(<_i42.CourseDetail>[]), _i9.Future<List<_i43.CourseDetail>>.value(<_i43.CourseDetail>[]),
) as _i9.Future<List<_i42.CourseDetail>>); ) as _i9.Future<List<_i43.CourseDetail>>);
} }
/// A class which mocks [AudioPlayerService]. /// A class which mocks [AudioPlayerService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockAudioPlayerService extends _i1.Mock class MockAudioPlayerService extends _i1.Mock
implements _i43.AudioPlayerService { implements _i44.AudioPlayerService {
@override @override
_i4.AudioPlayer get player => (super.noSuchMethod( _i4.AudioPlayer get player => (super.noSuchMethod(
Invocation.getter(#player), Invocation.getter(#player),
@ -1971,13 +1993,13 @@ class MockAudioPlayerService extends _i1.Mock
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockVoiceRecorderService extends _i1.Mock class MockVoiceRecorderService extends _i1.Mock
implements _i44.VoiceRecorderService { implements _i45.VoiceRecorderService {
@override @override
_i45.VoiceRecordingState get recordingState => (super.noSuchMethod( _i46.VoiceRecordingState get recordingState => (super.noSuchMethod(
Invocation.getter(#recordingState), Invocation.getter(#recordingState),
returnValue: _i45.VoiceRecordingState.pending, returnValue: _i46.VoiceRecordingState.pending,
returnValueForMissingStub: _i45.VoiceRecordingState.pending, returnValueForMissingStub: _i46.VoiceRecordingState.pending,
) as _i45.VoiceRecordingState); ) as _i46.VoiceRecordingState);
@override @override
_i5.WaveformRecorderController get waveController => (super.noSuchMethod( _i5.WaveformRecorderController get waveController => (super.noSuchMethod(
@ -2078,7 +2100,7 @@ class MockVoiceRecorderService extends _i1.Mock
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockInAppUpdateService extends _i1.Mock class MockInAppUpdateService extends _i1.Mock
implements _i46.InAppUpdateService { implements _i47.InAppUpdateService {
@override @override
_i9.Future<int> getBatteryLevel() => (super.noSuchMethod( _i9.Future<int> getBatteryLevel() => (super.noSuchMethod(
Invocation.method( Invocation.method(

View File

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