Merge branch 'release/0.1.9'

-fix(onboarding): Change onboarding assessment implementation
This commit is contained in:
BisratHailu 2026-04-30 10:58:36 +03:00
commit 6b4f87476e
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/status_checker_service.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/services/permission_handler_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/ui/views/learn_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart';
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart';
// @stacked-import
@StackedApp(
@ -74,7 +74,6 @@ import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart';
MaterialRoute(page: LoginView),
MaterialRoute(page: LearnModuleView),
MaterialRoute(page: WelcomeView),
MaterialRoute(page: AssessmentView),
MaterialRoute(page: LearnLessonView),
MaterialRoute(page: ForgetPasswordView),
MaterialRoute(page: LearnLessonDetailView),
@ -91,6 +90,7 @@ import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart';
MaterialRoute(page: CoursePracticeQuestionView),
MaterialRoute(page: LearnProgramView),
MaterialRoute(page: LearnCourseView),
MaterialRoute(page: AssessmentView),
// @stacked-route
],
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/views/account_privacy/account_privacy_view.dart'
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'
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'
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;
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;
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'
as _i34;
as _i33;
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/duolingo/duolingo_view.dart' as _i31;
import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i28;
import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart' as _i30;
import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i27;
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/language/language_view.dart' as _i13;
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'
as _i21;
as _i20;
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'
as _i18;
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'
as _i35;
as _i34;
import 'package:yimaru_app/ui/views/login/login_view.dart' as _i17;
import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i3;
import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart'
@ -111,8 +111,6 @@ class Routes {
static const welcomeView = '/welcome-view';
static const assessmentView = '/assessment-view';
static const learnLessonView = '/learn-lesson-view';
static const forgetPasswordView = '/forget-password-view';
@ -145,6 +143,8 @@ class Routes {
static const learnCourseView = '/learn-course-view';
static const assessmentView = '/assessment-view';
static const all = <String>{
homeView,
onboardingView,
@ -164,7 +164,6 @@ class Routes {
loginView,
learnModuleView,
welcomeView,
assessmentView,
learnLessonView,
forgetPasswordView,
learnLessonDetailView,
@ -181,6 +180,7 @@ class Routes {
coursePracticeQuestionView,
learnProgramView,
learnCourseView,
assessmentView,
};
}
@ -258,73 +258,73 @@ class StackedRouter extends _i1.RouterBase {
Routes.welcomeView,
page: _i19.WelcomeView,
),
_i1.RouteDef(
Routes.assessmentView,
page: _i20.AssessmentView,
),
_i1.RouteDef(
Routes.learnLessonView,
page: _i21.LearnLessonView,
page: _i20.LearnLessonView,
),
_i1.RouteDef(
Routes.forgetPasswordView,
page: _i22.ForgetPasswordView,
page: _i21.ForgetPasswordView,
),
_i1.RouteDef(
Routes.learnLessonDetailView,
page: _i23.LearnLessonDetailView,
page: _i22.LearnLessonDetailView,
),
_i1.RouteDef(
Routes.learnPracticeView,
page: _i24.LearnPracticeView,
page: _i23.LearnPracticeView,
),
_i1.RouteDef(
Routes.coursePracticeView,
page: _i25.CoursePracticeView,
page: _i24.CoursePracticeView,
),
_i1.RouteDef(
Routes.coursePaymentView,
page: _i26.CoursePaymentView,
page: _i25.CoursePaymentView,
),
_i1.RouteDef(
Routes.courseCategoryView,
page: _i27.CourseCategoryView,
page: _i26.CourseCategoryView,
),
_i1.RouteDef(
Routes.failureView,
page: _i28.FailureView,
page: _i27.FailureView,
),
_i1.RouteDef(
Routes.courseLessonView,
page: _i29.CourseLessonView,
page: _i28.CourseLessonView,
),
_i1.RouteDef(
Routes.courseLessonDetailView,
page: _i30.CourseLessonDetailView,
page: _i29.CourseLessonDetailView,
),
_i1.RouteDef(
Routes.duolingoView,
page: _i31.DuolingoView,
page: _i30.DuolingoView,
),
_i1.RouteDef(
Routes.courseSubcategoryView,
page: _i32.CourseSubcategoryView,
page: _i31.CourseSubcategoryView,
),
_i1.RouteDef(
Routes.courseView,
page: _i33.CourseView,
page: _i32.CourseView,
),
_i1.RouteDef(
Routes.coursePracticeQuestionView,
page: _i34.CoursePracticeQuestionView,
page: _i33.CoursePracticeQuestionView,
),
_i1.RouteDef(
Routes.learnProgramView,
page: _i35.LearnProgramView,
page: _i34.LearnProgramView,
),
_i1.RouteDef(
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,
);
},
_i20.AssessmentView: (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) {
_i20.LearnLessonView: (data) {
final args = data.getArgs<LearnLessonViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i21.LearnLessonView(key: args.key, module: args.module),
_i20.LearnLessonView(key: args.key, module: args.module),
settings: data,
);
},
_i22.ForgetPasswordView: (data) {
_i21.ForgetPasswordView: (data) {
final args = data.getArgs<ForgetPasswordViewArguments>(
orElse: () => const ForgetPasswordViewArguments(),
);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i22.ForgetPasswordView(key: args.key),
builder: (context) => _i21.ForgetPasswordView(key: args.key),
settings: data,
);
},
_i23.LearnLessonDetailView: (data) {
_i22.LearnLessonDetailView: (data) {
final args = data.getArgs<LearnLessonDetailViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i23.LearnLessonDetailView(key: args.key, lesson: args.lesson),
_i22.LearnLessonDetailView(key: args.key, lesson: args.lesson),
settings: data,
);
},
_i24.LearnPracticeView: (data) {
_i23.LearnPracticeView: (data) {
final args = data.getArgs<LearnPracticeViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i24.LearnPracticeView(
builder: (context) => _i23.LearnPracticeView(
key: args.key, id: args.id, practice: args.practice),
settings: data,
);
},
_i25.CoursePracticeView: (data) {
_i24.CoursePracticeView: (data) {
final args = data.getArgs<CoursePracticeViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i25.CoursePracticeView(key: args.key, id: args.id),
_i24.CoursePracticeView(key: args.key, id: args.id),
settings: data,
);
},
_i26.CoursePaymentView: (data) {
_i25.CoursePaymentView: (data) {
final args = data.getArgs<CoursePaymentViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i26.CoursePaymentView(key: args.key, course: args.course),
_i25.CoursePaymentView(key: args.key, course: args.course),
settings: data,
);
},
_i27.CourseCategoryView: (data) {
_i26.CourseCategoryView: (data) {
final args = data.getArgs<CourseCategoryViewArguments>(
orElse: () => const CourseCategoryViewArguments(),
);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i27.CourseCategoryView(key: args.key),
builder: (context) => _i26.CourseCategoryView(key: args.key),
settings: data,
);
},
_i28.FailureView: (data) {
_i27.FailureView: (data) {
final args = data.getArgs<FailureViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i28.FailureView(key: args.key, label: args.label),
builder: (context) => _i27.FailureView(
key: args.key, onTap: args.onTap, label: args.label),
settings: data,
);
},
_i29.CourseLessonView: (data) {
_i28.CourseLessonView: (data) {
final args = data.getArgs<CourseLessonViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i29.CourseLessonView(key: args.key, course: args.course),
_i28.CourseLessonView(key: args.key, course: args.course),
settings: data,
);
},
_i30.CourseLessonDetailView: (data) {
_i29.CourseLessonDetailView: (data) {
final args = data.getArgs<CourseLessonDetailViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i30.CourseLessonDetailView(key: args.key, lesson: args.lesson),
_i29.CourseLessonDetailView(key: args.key, lesson: args.lesson),
settings: data,
);
},
_i31.DuolingoView: (data) {
_i30.DuolingoView: (data) {
final args = data.getArgs<DuolingoViewArguments>(
orElse: () => const DuolingoViewArguments(),
);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i31.DuolingoView(key: args.key),
builder: (context) => _i30.DuolingoView(key: args.key),
settings: data,
);
},
_i32.CourseSubcategoryView: (data) {
_i31.CourseSubcategoryView: (data) {
final args = data.getArgs<CourseSubcategoryViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i32.CourseSubcategoryView(key: args.key, category: args.category),
_i31.CourseSubcategoryView(key: args.key, category: args.category),
settings: data,
);
},
_i33.CourseView: (data) {
_i32.CourseView: (data) {
final args = data.getArgs<CourseViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i33.CourseView(key: args.key, subcategory: args.subcategory),
_i32.CourseView(key: args.key, subcategory: args.subcategory),
settings: data,
);
},
_i34.CoursePracticeQuestionView: (data) {
_i33.CoursePracticeQuestionView: (data) {
final args =
data.getArgs<CoursePracticeQuestionViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i34.CoursePracticeQuestionView(key: args.key, id: args.id),
_i33.CoursePracticeQuestionView(key: args.key, id: args.id),
settings: data,
);
},
_i35.LearnProgramView: (data) {
_i34.LearnProgramView: (data) {
final args = data.getArgs<LearnProgramViewArguments>(
orElse: () => const LearnProgramViewArguments(),
);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i35.LearnProgramView(key: args.key),
builder: (context) => _i34.LearnProgramView(key: args.key),
settings: data,
);
},
_i36.LearnCourseView: (data) {
_i35.LearnCourseView: (data) {
final args = data.getArgs<LearnCourseViewArguments>(nullOk: false);
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,
);
},
@ -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 {
const LearnLessonViewArguments({
this.key,
@ -1257,27 +1230,30 @@ class CourseCategoryViewArguments {
class FailureViewArguments {
const FailureViewArguments({
this.key,
required this.onTap,
required this.label,
});
final _i37.Key? key;
final void Function() onTap;
final String label;
@override
String toString() {
return '{"key": "$key", "label": "$label"}';
return '{"key": "$key", "onTap": "$onTap", "label": "$label"}';
}
@override
bool operator ==(covariant FailureViewArguments other) {
if (identical(this, other)) return true;
return other.key == key && other.label == label;
return other.key == key && other.onTap == onTap && other.label == label;
}
@override
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 {
Future<dynamic> navigateToHomeView({
_i37.Key? key,
@ -1778,23 +1781,6 @@ extension NavigatorStateExtension on _i46.NavigationService {
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({
_i37.Key? key,
required _i39.LearnModule module,
@ -1916,6 +1902,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> navigateToFailureView({
_i37.Key? key,
required void Function() onTap,
required String label,
int? routerId,
bool preventDuplicates = true,
@ -1924,7 +1911,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition,
}) async {
return navigateTo<dynamic>(Routes.failureView,
arguments: FailureViewArguments(key: key, label: label),
arguments: FailureViewArguments(key: key, onTap: onTap, label: label),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
@ -2065,6 +2052,23 @@ extension NavigatorStateExtension on _i46.NavigationService {
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({
_i37.Key? key,
int? routerId,
@ -2355,23 +2359,6 @@ extension NavigatorStateExtension on _i46.NavigationService {
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({
_i37.Key? key,
required _i39.LearnModule module,
@ -2493,6 +2480,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> replaceWithFailureView({
_i37.Key? key,
required void Function() onTap,
required String label,
int? routerId,
bool preventDuplicates = true,
@ -2501,7 +2489,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition,
}) async {
return replaceWith<dynamic>(Routes.failureView,
arguments: FailureViewArguments(key: key, label: label),
arguments: FailureViewArguments(key: key, onTap: onTap, label: label),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
@ -2641,4 +2629,21 @@ extension NavigatorStateExtension on _i46.NavigationService {
parameters: parameters,
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:yimaru_app/models/option.dart';
part 'question.g.dart';
part 'assessment_question.g.dart';
@JsonSerializable()
class Question {
class AssessmentQuestion {
final int? id;
final int? points;
@ -18,21 +18,17 @@ class Question {
@JsonKey(name: 'question_text')
final String? questionText;
@JsonKey(name: 'difficulty_level')
final String? difficultyLevel;
const Question({
const AssessmentQuestion({
this.id,
this.points,
this.status,
this.options,
this.questionText,
this.questionType,
this.difficultyLevel,
});
factory Question.fromJson(Map<String, dynamic> json) =>
_$QuestionFromJson(json);
factory AssessmentQuestion.fromJson(Map<String, dynamic> 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
part of 'question.dart';
part of 'assessment_question.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Question _$QuestionFromJson(Map<String, dynamic> json) => Question(
AssessmentQuestion _$AssessmentQuestionFromJson(Map<String, dynamic> json) =>
AssessmentQuestion(
id: (json['id'] as num?)?.toInt(),
points: (json['points'] as num?)?.toInt(),
status: json['status'] as String?,
@ -15,15 +16,14 @@ Question _$QuestionFromJson(Map<String, dynamic> json) => Question(
.toList(),
questionText: json['question_text'] 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,
'points': instance.points,
'status': instance.status,
'options': instance.options,
'question_type': instance.questionType,
'question_text': instance.questionText,
'difficulty_level': instance.difficultyLevel,
};

View File

@ -11,7 +11,10 @@ class Option {
@JsonKey(name: 'option_text')
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);

View File

@ -10,10 +10,12 @@ Option _$OptionFromJson(Map<String, dynamic> json) => Option(
id: (json['id'] as num?)?.toInt(),
optionText: json['option_text'] as String?,
isCorrect: json['is_correct'] as bool?,
optionOrder: (json['option_order'] as num?)?.toInt(),
);
Map<String, dynamic> _$OptionToJson(Option instance) => <String, dynamic>{
'id': instance.id,
'is_correct': instance.isCorrect,
'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_program.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/category.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/lesson.dart';
import '../models/module.dart';
import '../models/assessment.dart';
import '../models/submodule.dart';
import '../ui/common/enmus.dart';
@ -354,23 +355,47 @@ class ApiService {
}
}
// Get assessments
Future<List<Question>> getAssessments() async {
// Get assessment question sets
Future<List<Assessment>> getAssessments() async {
try {
List<Question> assessments = [];
List<Assessment> assessments = [];
final Response response =
await _service.dio.get('$kBaseUrl/$kAssessmentsUrl');
final Response response = await _service.dio.get(
'$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) {
var data = response.data;
var decodedData = data['data'] as List;
assessments = decodedData.map(
questions = decodedData.map(
(e) {
return Question.fromJson(e);
return AssessmentQuestion.fromJson(e);
},
).toList();
return assessments;
return questions;
}
return [];
} catch (e) {
@ -741,9 +766,9 @@ class ApiService {
}
// Get course practic questions
Future<List<Question>> getCoursePracticeQuestions(int id) async {
Future<List<AssessmentQuestion>> getCoursePracticeQuestions(int id) async {
try {
List<Question> coursePracticeQuestions = [];
List<AssessmentQuestion> coursePracticeQuestions = [];
final Response response = await _service.dio
.get('$kBaseUrl/$kPracticeBaseUrl/$id/$kCoursePracticeQuestions');
@ -753,7 +778,7 @@ class ApiService {
var decodedData = data['data'] as List;
coursePracticeQuestions = decodedData.map(
(e) {
return Question.fromJson(e);
return AssessmentQuestion.fromJson(e);
},
).toList();
return coursePracticeQuestions;
@ -765,13 +790,14 @@ class ApiService {
}
// Get course practice question
Future<Question?> getCoursePracticeQuestion(int id) async {
Future<AssessmentQuestion?> getCoursePracticeQuestion(int id) async {
try {
final Response response =
await _service.dio.get('$kBaseUrl/$kCoursePracticeQuestion/$id');
if (response.statusCode == 200) {
Question question = Question.fromJson(response.data['data']);
AssessmentQuestion question =
AssessmentQuestion.fromJson(response.data['data']);
return question;
}
@ -950,9 +976,9 @@ class ApiService {
}
// Questions
Future<List<Question>> getQuestions(int id) async {
Future<List<AssessmentQuestion>> getQuestions(int id) async {
try {
List<Question> questions = [];
List<AssessmentQuestion> questions = [];
final Response response = await _service.dio.get(
'$kBaseUrl/api/$kApiVersionUrl/$kQuestionSetsUrl/$id/$kQuestionsUrl');
@ -962,7 +988,7 @@ class ApiService {
var decodedData = data['data'] as List;
questions = decodedData.map(
(e) {
return Question.fromJson(e);
return AssessmentQuestion.fromJson(e);
},
).toList();
return questions;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,12 @@
import 'package:flutter/material.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_intro_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 '../../widgets/assessment_loading_screen.dart';
import 'assessment_viewmodel.dart';
class AssessmentView extends StackedView<AssessmentViewModel> {
@ -39,16 +41,29 @@ class AssessmentView extends StackedView<AssessmentViewModel> {
Widget _buildAssessmentScreens(AssessmentViewModel viewModel) => IndexedStack(
index: viewModel.currentPage,
children: _buildScreens(),
children: _buildScreens(viewModel),
);
List<Widget> _buildScreens() => [
_buildAssessmentIntro(),
List<Widget> _buildScreens(AssessmentViewModel viewModel) => [
_buildAssessmentIntroWrapper(viewModel),
_buildAssessment(),
_buildAssessmentResult(),
_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 _buildAssessment() => const AssessmentQuestionsScreen();

View File

@ -1,13 +1,14 @@
import 'package:flutter/cupertino.dart';
import 'package:stacked/stacked.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/services/status_checker_service.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart';
import '../../../app/app.router.dart';
import '../../../models/question.dart';
import '../../../models/assessment.dart';
import '../../../services/api_service.dart';
import '../../common/app_colors.dart';
import '../../common/ui_helpers.dart';
@ -33,82 +34,78 @@ class AssessmentViewModel extends BaseViewModel {
PageController get pageController => _pageController;
// 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 = {};
Map<String, dynamic> get selectedAnswers => _selectedAnswers;
List<AssessmentQuestion> _assessmentQuestions = [];
List<AssessmentQuestion> get assessmentQuestions => _assessmentQuestions;
// User data
final Map<String, dynamic> _userData = {};
Map<String, dynamic> get userData => _userData;
// 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() {
if (_currentQuestion == 5) {
// A1
final correctCount = countCorrectAnswersUntil(5);
if (correctCount > 3) {
return {'continue': true, 'level': ProficiencyLevels.a1};
bool levelPassed = canPassLevel();
if (levelPassed) {
return {'passed': true, 'level': _currentAssessment?.description};
} 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 {
return {'continue': true, 'level': ProficiencyLevels.none};
return {'passed': false, 'level': _currentAssessment?.description};
}
}
int countCorrectAnswersUntil(int untilQuestion) {
bool canPassLevel() {
int count = 0;
for (int i = 1; i <= untilQuestion; i++) {
for (int i = 1; i <= _assessmentQuestions.length; i++) {
final answer = _selectedAnswers[i.toString()];
if (answer is Map<String, dynamic> && answer['correct'] == true) {
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}) {
@ -125,7 +122,7 @@ class AssessmentViewModel extends BaseViewModel {
question.toString(): {
'correct': correct,
'option': option?.optionText,
'answer': _assessments[question - 1]
'answer': _assessmentQuestions[question - 1]
.options
?.firstWhere((e) => e.isCorrect ?? false)
.optionText
@ -152,32 +149,41 @@ class AssessmentViewModel extends BaseViewModel {
}
// Question navigation
void nextQuestion() {
_currentQuestion++;
Future<void> nextQuestion() async {
_currentQuestionIndex++;
Map<String, dynamic> response = evaluateAssessment();
if (_currentQuestion == _assessments.length) {
print('LEVEL: $response');
print('LENGTH: ${_assessmentQuestions.length}');
print('INDEX: $_currentQuestionIndex');
if (_currentQuestionIndex == _assessmentQuestions.length) {
_currentAssessmentIndex = _currentAssessmentIndex + 1;
if (_currentAssessmentIndex == _assessments.length) {
_proficiencyLevel = response['level'];
next();
} else {
if (response['level'] == ProficiencyLevels.none) {
_pageController.jumpToPage(_currentQuestion);
} else {
if (response['continue']) {
_pageController.jumpToPage(_currentQuestion);
if (response['passed']) {
_selectedAnswers.clear();
_currentQuestionIndex = 0;
_proficiencyLevel = response['level'];
_currentAssessment = assessments[currentAssessmentIndex];
await getAssessmentQuestions(
_currentAssessment?.id ?? 0);
_pageController.jumpToPage(_currentQuestionIndex);
} else {
_proficiencyLevel = response['level'];
next();
}
}
} else {
_pageController.jumpToPage(_currentQuestionIndex);
}
rebuildUi();
}
void previousQuestion() {
if (_currentQuestion != 0) {
_currentQuestion--;
if (_currentQuestionIndex != 0) {
_currentQuestionIndex--;
_pageController.previousPage(
duration: const Duration(microseconds: 100), curve: Curves.linear);
rebuildUi();
@ -193,7 +199,7 @@ class AssessmentViewModel extends BaseViewModel {
_currentPage = 0;
rebuildUi();
} else if (_currentPage == 3) {
if (_proficiencyLevel != ProficiencyLevels.none) {
if (_proficiencyLevel != null) {
_currentPage--;
} else {
_currentPage = 0;
@ -240,12 +246,12 @@ class AssessmentViewModel extends BaseViewModel {
// Navigation
void pop() => _navigationService.back();
Future<void> replaceWithStartUp() async =>
await _navigationService.clearStackAndShow(Routes.startupView);
Future<void> navigateToLanguage() async =>
await _navigationService.navigateToLanguageView();
Future<void> replaceWittStartUp() async =>
await _navigationService.clearStackAndShow(Routes.startupView);
// Remote api call
// Complete profile
@ -259,7 +265,7 @@ class AssessmentViewModel extends BaseViewModel {
await _apiService.completeProfile(_userData);
if (response['status'] == ResponseStatus.success) {
clearUserData();
await replaceWithStartUp();
await replaceWittStartUp();
showSuccessToast(response['message']);
} else {
showErrorToast(response['message']);
@ -268,11 +274,23 @@ class AssessmentViewModel extends BaseViewModel {
}
// Assessments
Future<void> getAssessments() async =>
await runBusyFuture(_getAssessments(),
busyObject: StateObjects.assessments);
Future<void> _getAssessments() async {
if (await _statusChecker.checkConnection()) {
_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> {
const AssessmentIntroScreen({super.key});
Future<void> _next(AssessmentViewModel viewModel) async =>
viewModel.setFirstAssessment();
@override
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
@ -95,8 +98,8 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
onTap: () => viewModel.next(),
backgroundColor: kcPrimaryColor,
onTap: () async => await _next(viewModel),
);
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/large_app_bar.dart';
import '../../../widgets/assessment_loading_screen.dart';
import '../assessment_viewmodel.dart';
import 'assessment_loading_screen.dart';
class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
const AssessmentQuestionsScreen({super.key});
@override
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildAssessmentScreens(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,
);
_buildAssessmentScreensWrapper(viewModel);
Widget _buildAssessmentScreensWrapper(AssessmentViewModel viewModel) =>
PopScope(
@ -52,7 +39,7 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
onClose: viewModel.abort,
showLanguageSelection: false,
onPop: viewModel.previousQuestion,
showBackButton: viewModel.currentQuestion == 0 ? false : true,
showBackButton: viewModel.currentQuestionIndex == 0 ? false : true,
);
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
@ -65,7 +52,7 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildAssessment(AssessmentViewModel viewModel) => PageView.builder(
controller: viewModel.pageController,
itemCount: viewModel.assessments.length,
itemCount: viewModel.assessmentQuestions.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (cotext, index) =>
_buildBodyScroller(index: index, viewModel: viewModel),
@ -98,7 +85,7 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildTitle(
{required int index, required AssessmentViewModel viewModel}) =>
Text(
'Q${index + 1}. ${viewModel.assessments[index].questionText} ',
'Q${index + 1}. ${viewModel.assessmentQuestions[index].questionText} ',
style: style16DG600,
);
@ -107,15 +94,18 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: viewModel.assessments[index].options?.length,
itemCount: viewModel.assessmentQuestions[index].options?.length,
itemBuilder: (context, inner) => _buildAnswer(
onTap: () => viewModel.setSelectedAnswer(
question: index + 1,
option: viewModel.assessments[index].options?[inner]),
title: viewModel.assessments[index].options?[inner].optionText ?? '',
option: viewModel.assessmentQuestions[index].options?[inner]),
title:
viewModel.assessmentQuestions[index].options?[inner].optionText ??
'',
selected: viewModel.isSelectedAnswer(
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())
? () => viewModel.nextQuestion()
: null,
text: viewModel.currentQuestion == viewModel.assessments.length - 1
? 'Finish'
text: viewModel.currentQuestionIndex == viewModel.assessmentQuestions.length - 1
? 'Finish Level'
: 'Continue',
);
}

View File

@ -75,7 +75,7 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
];
Widget _buildTitle(AssessmentViewModel viewModel) => Text(
'Youre likely a ${viewModel.proficiencyLevel.name.toUpperCase()} speaker!',
'Youre likely a ${viewModel.proficiencyLevel?.toUpperCase()} speaker!',
style: style25DG600,
textAlign: TextAlign.center,
);
@ -87,12 +87,12 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
);
Widget _buildIconWrapper(AssessmentViewModel viewModel) =>
viewModel.proficiencyLevel != ProficiencyLevels.none
viewModel.proficiencyLevel != null
? _buildIcon(viewModel)
: Container();
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(
'Let\'s start your practice',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import 'package:stacked_services/stacked_services.dart';
import '../../../app/app.locator.dart';
import '../../../models/option.dart';
import '../../../models/question.dart';
import '../../../models/assessment_question.dart';
import '../../../services/api_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/app_colors.dart';
@ -38,13 +38,14 @@ class CoursePracticeQuestionViewModel extends FormViewModel {
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;

View File

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

View File

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

View File

@ -9,8 +9,10 @@ import 'failure_viewmodel.dart';
class FailureView extends StackedView<FailureViewModel> {
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
FailureViewModel viewModelBuilder(BuildContext context) => FailureViewModel();
@ -48,19 +50,38 @@ class FailureView extends StackedView<FailureViewModel> {
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(),
children: _buildColumnChildren(),
);
List<Widget> _buildUpperColumnChildren() =>
List<Widget> _buildColumnChildren() =>
[_buildIconWrapper(), _buildSafeWrapper()];
Widget _buildSafeWrapper() => SafeArea(child: _buildLoadingTextContainer());
Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
Widget _buildIconWrapper() => Padding(
padding: const EdgeInsets.only(top: 100),
child: _buildIcon(),
);
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(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
@ -85,10 +106,14 @@ class FailureView extends StackedView<FailureViewModel> {
Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite);
Widget _buildIconWrapper() => Padding(
padding: const EdgeInsets.only(top: 100),
child: _buildIcon(),
Widget _buildRetryButtonWrapper() => GestureDetector(
onTap: onTap,
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:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/learn_program/learn_program_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 '../../common/enmus.dart';
import '../../widgets/page_loading_indicator.dart';
import 'home_viewmodel.dart';
class HomeView extends StackedView<HomeViewModel> {
const HomeView({Key? key}) : super(key: key);
@override
void onViewModelReady(HomeViewModel viewModel) async {
await viewModel.inAppUpdate();
super.onViewModelReady(viewModel);
}
@override
HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel();
@ -20,8 +25,6 @@ class HomeView extends StackedView<HomeViewModel> {
BuildContext context, HomeViewModel viewModel, Widget? child) =>
_buildScaffold(viewModel);
Widget _buildScaffold(HomeViewModel viewModel) => Scaffold(
body: getViewForIndex(viewModel.currentPage),
bottomNavigationBar: _buildBottomNav(viewModel),
@ -56,7 +59,6 @@ class HomeView extends StackedView<HomeViewModel> {
label: 'Profile',
icon: _buildProfileIcon(),
);
}
Widget _buildLearnIcon() => const Icon(Icons.school);
@ -75,3 +77,4 @@ Widget getViewForIndex(int index) {
return const ProfileView();
}
}
}

View File

@ -1,21 +1,19 @@
import 'package:yimaru_app/app/app.bottomsheets.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/services/status_checker_service.dart';
import 'package:yimaru_app/ui/common/app_strings.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import '../../../services/api_service.dart';
import '../../../services/authentication_service.dart';
import '../../../services/image_downloader_service.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
import '../../../services/in_app_update_service.dart';
class HomeViewModel extends ReactiveViewModel {
// Dependency injection
final _statusChecker = locator<StatusCheckerService>();
final _bottomSheetService = locator<BottomSheetService>();
final _inAppUpdateService = locator<InAppUpdateService>();
final _authenticationService = locator<AuthenticationService>();
@override
@ -46,9 +44,12 @@ class HomeViewModel extends ReactiveViewModel {
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(
showBackButton: true,
onTap: viewModel.pop,
onPop: viewModel.pop,
title: 'Language Preference',
);

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
@override
void onViewModelReady(LearnModuleViewModel viewModel) async {
await viewModel.getLearnModules(1);
await viewModel.getLearnModules(course.id ?? 0);
super.onViewModelReady(viewModel);
}
@ -58,7 +58,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
);
Widget _buildAppBar(LearnModuleViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop,
onPop: viewModel.pop,
showBackButton: true,
);
@ -116,10 +116,10 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile(
module: viewModel.modules[index],
onModuleTap: () async =>
await viewModel.navigateToLearnLesson(viewModel.modules[index]),
onPracticeTap: () async => await viewModel
.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: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/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_result_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_questions_screen.dart';
@ -78,8 +79,21 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
Widget _buildBodyState(LearnPracticeViewModel viewModel) =>
viewModel.busy(StateObjects.learnPractices)
? const PageLoadingIndicator()
: 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(
index: viewModel.currentPage, children: _buildScreens(viewModel));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -547,6 +547,7 @@ class OnboardingViewModel extends ReactiveViewModel
// Reset country region form screen
void resetCountryRegionFormScreen() {
_focusRegion = false;
_selectedCountry = 'Ethiopia';
rebuildUi();
}
@ -613,6 +614,4 @@ class OnboardingViewModel extends ReactiveViewModel
Future<void> navigateToAssessment() async =>
await _navigationService.navigateToAssessmentView(data: _userData);
}

View File

@ -55,7 +55,7 @@ class PrivacyPolicyView extends StackedView<PrivacyPolicyViewModel> {
Widget _buildAppbar(PrivacyPolicyViewModel viewModel) => SmallAppBar(
showBackButton: true,
onTap: viewModel.pop,
onPop: viewModel.pop,
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/enmus.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_image.dart';
import 'package:yimaru_app/ui/widgets/view_profile_button.dart';
@ -155,7 +156,7 @@ class ProfileView extends StackedView<ProfileViewModel> {
children: _buildSettingsChildren(viewModel));
List<Widget> _buildSettingsChildren(ProfileViewModel viewModel) => [
_buildDownloadsCard(viewModel),
// _buildDownloadsCard(viewModel),
_buildProgressCard(viewModel),
_buildAccountCard(viewModel),
_buildSupportCard(viewModel)
@ -191,7 +192,7 @@ class ProfileView extends StackedView<ProfileViewModel> {
Widget _buildLogOutButton(ProfileViewModel viewModel) => CustomElevatedButton(
height: 55,
text: 'Log Out',
text: 'Logout',
borderRadius: 12,
foregroundColor: kcRed,
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
Future<bool?> showAbortDialog() async {
DialogResponse? response = await _dialogService.showDialog(
@ -82,13 +75,6 @@ class ProfileViewModel extends ReactiveViewModel {
return response?.confirmed;
}
Future<void> logout() async {
bool? response = await showAbortDialog();
if (response != null && response) {
await _logout();
}
}
// Navigation
void pop() => _navigationService.back();
@ -107,6 +93,9 @@ class ProfileViewModel extends ReactiveViewModel {
Future<void> navigateToSupport() async =>
await _navigationService.navigateToSupportView();
Future<void> navigateToLogin() async =>
await _navigationService.clearStackAndShow(Routes.loginView);
// Remote api call
// Update profile
@ -115,10 +104,21 @@ class ProfileViewModel extends ReactiveViewModel {
Future<void> _updateProfilePicture(String image) async {
if (await _statusChecker.checkConnection()) {
Map<String, dynamic> data = {
'profile_picture_url': image,
};
Map<String, dynamic> data = {'profile_picture_url': image};
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

@ -40,7 +40,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
'country': viewModel.selectedCountry,
'first_name': firstNameController.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);
@ -75,7 +75,6 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
viewModel.clearUserData();
viewModel.setGender(viewModel.user?.gender ?? '');
viewModel.setSelectedCountry(viewModel.user?.country ?? 'Ethiopia');
}
@override
@ -147,7 +146,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
];
Widget _buildAppbar(ProfileDetailViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop,
onPop: viewModel.pop,
showBackButton: true,
title: 'Edit Profile',
);
@ -194,7 +193,6 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
verticalSpaceMedium,
_buildRegionFormFieldWrapper(viewModel),
verticalSpaceMedium,
_buildOccupationDropdownWrapper(viewModel),
verticalSpaceLarge,
_buildLowerColumn(viewModel)
@ -535,7 +533,8 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
List<Widget> _buildRegionFormFieldChildren(
ProfileDetailViewModel viewModel) =>
[
_buildRegionFormFieldLabel(),verticalSpaceSmall,
_buildRegionFormFieldLabel(),
verticalSpaceSmall,
_buildRegionFormField(viewModel),
if (viewModel.hasRegionValidationMessage && viewModel.focusRegion)
verticalSpaceTiny,

View File

@ -47,7 +47,6 @@ class ProfileDetailViewModel extends ReactiveViewModel
String? get selectedGender => _selectedGender;
// First name
bool _focusPhoneNumber = false;
@ -63,7 +62,6 @@ class ProfileDetailViewModel extends ReactiveViewModel
String get selectedCountry => _selectedCountry;
// Occupation
bool _focusOccupation = false;
@ -97,7 +95,6 @@ class ProfileDetailViewModel extends ReactiveViewModel
rebuildUi();
}
// Phone number
void setPhoneNumberFocus() {
_focusPhoneNumber = true;
@ -269,11 +266,9 @@ class ProfileDetailViewModel extends ReactiveViewModel
void setSelectedCountry(String value) {
_selectedCountry = value;
rebuildUi();
}
// Occupation
void setOccupationFocus() {
_focusOccupation = true;

View File

@ -56,7 +56,7 @@ class ProgressView extends StackedView<ProgressViewModel> {
Widget _buildAppbar(ProgressViewModel viewModel) => SmallAppBar(
title: 'My Progress',
showBackButton: true,
onTap: viewModel.pop,
onPop: viewModel.pop,
);
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 '../../../models/user.dart';
import '../../../services/google_auth_service.dart';
import '../../../services/smart_auth_service.dart';
import '../../../services/status_checker_service.dart';
class RegisterViewModel extends ReactiveViewModel

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.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 '../../../widgets/large_app_bar.dart';
import '../../../widgets/refresh_button.dart';
import '../common/app_colors.dart';
import 'large_app_bar.dart';
class AssessmentLoadingScreen extends StatelessWidget {
final bool isEmpty;

View File

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

View File

@ -56,9 +56,7 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
ExpansionTile(
enabled: true,
textColor: kcDarkGrey,
showTrailingIcon: true,
initiallyExpanded: true,
subtitle: _buildContent(),
title: _buildTitleWrapper(),
@ -69,13 +67,12 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
shape: Border.all(color: kcTransparent),
expandedAlignment: Alignment.centerLeft,
collapsedBackgroundColor: kcBackgroundColor,
enabled: (module.access?.isAccessible ?? false),
controlAffinity: ListTileControlAffinity.trailing,
expandedCrossAxisAlignment: CrossAxisAlignment.start,
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
childrenPadding: const EdgeInsets.fromLTRB(70, 0, 15, 15),
// enabled:(module.access?.isAccessible ?? false) ,
// showTrailingIcon: status != ProgressStatuses.pending ? true : false,
//initiallyExpanded: status == ProgressStatuses.started ? true : false,
showTrailingIcon: (module.access?.isAccessible ?? false) ? true : false,
children:
_buildExpansionTileChildren(context: context, viewModel: viewModel),
);
@ -209,9 +206,9 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
onTap: viewModel.pop,
);
// Widget _buildContainerShaderState() => status == ProgressStatuses.pending
// ? _buildContainerShaderWrapper()
// : Container();
Widget _buildContainerShaderState() => !(module.access?.isAccessible ?? false)
? _buildContainerShaderWrapper()
: Container();
Widget _buildContainerShaderWrapper() => Positioned.fill(
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/views/learn_practice/learn_practice_viewmodel.dart';
class LearnPracticeTipSection extends ViewModelWidget<LearnPracticeViewModel> {
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 {
final String? title;
final bool showBackButton;
final GestureTapCallback? onTap;
final GestureTapCallback? onPop;
const SmallAppBar(
{super.key, this.onTap, this.title, required this.showBackButton});
{super.key, this.onPop, this.title, required this.showBackButton});
@override
Widget build(BuildContext context) => _buildAppBar();
@ -28,7 +28,7 @@ class SmallAppBar extends StatelessWidget {
child: _buildBackButton(),
);
Widget _buildBackButton() => CustomBackButton(onTap: onTap);
Widget _buildBackButton() => CustomBackButton(onTap: onPop);
Widget _buildTitleWrapper() => Align(
alignment: Alignment.center,

View File

@ -1,5 +1,5 @@
name: yimaru_app
version: 0.1.8+10
version: 0.1.9+11
publish_to: 'none'
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: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:mockito/mockito.dart' as _i1;
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:waveform_recorder/waveform_recorder.dart' as _i5;
import 'package:yimaru_app/models/category.dart' as _i21;
import 'package:yimaru_app/models/course.dart' as _i26;
import 'package:yimaru_app/models/course_detail.dart' as _i42;
import 'package:yimaru_app/models/course_lesson.dart' as _i24;
import 'package:yimaru_app/models/course_progress.dart' as _i23;
import 'package:yimaru_app/models/learn_course.dart' as _i16;
import 'package:yimaru_app/models/learn_lesson.dart' as _i19;
import 'package:yimaru_app/models/learn_module.dart' as _i18;
import 'package:yimaru_app/models/learn_practice.dart' as _i17;
import 'package:yimaru_app/models/learn_program.dart' as _i15;
import 'package:yimaru_app/models/learn_question.dart' as _i20;
import 'package:yimaru_app/models/lesson.dart' as _i30;
import 'package:yimaru_app/models/level.dart' as _i27;
import 'package:yimaru_app/models/module.dart' as _i28;
import 'package:yimaru_app/models/practice.dart' as _i25;
import 'package:yimaru_app/models/question.dart' as _i14;
import 'package:yimaru_app/models/subcategory.dart' as _i22;
import 'package:yimaru_app/models/submodule.dart' as _i29;
import 'package:yimaru_app/models/assessment.dart' as _i14;
import 'package:yimaru_app/models/assessment_question.dart' as _i15;
import 'package:yimaru_app/models/category.dart' as _i22;
import 'package:yimaru_app/models/course.dart' as _i27;
import 'package:yimaru_app/models/course_detail.dart' as _i43;
import 'package:yimaru_app/models/course_lesson.dart' as _i25;
import 'package:yimaru_app/models/course_progress.dart' as _i24;
import 'package:yimaru_app/models/learn_course.dart' as _i17;
import 'package:yimaru_app/models/learn_lesson.dart' as _i20;
import 'package:yimaru_app/models/learn_module.dart' as _i19;
import 'package:yimaru_app/models/learn_practice.dart' as _i18;
import 'package:yimaru_app/models/learn_program.dart' as _i16;
import 'package:yimaru_app/models/learn_question.dart' as _i21;
import 'package:yimaru_app/models/lesson.dart' as _i31;
import 'package:yimaru_app/models/level.dart' as _i28;
import 'package:yimaru_app/models/module.dart' as _i29;
import 'package:yimaru_app/models/practice.dart' as _i26;
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/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/course_service.dart' as _i41;
import 'package:yimaru_app/services/dio_service.dart' as _i31;
import 'package:yimaru_app/services/google_auth_service.dart' as _i36;
import 'package:yimaru_app/services/image_downloader_service.dart' as _i37;
import 'package:yimaru_app/services/image_picker_service.dart' as _i35;
import 'package:yimaru_app/services/in_app_update_service.dart' as _i46;
import 'package:yimaru_app/services/notification_service.dart' as _i38;
import 'package:yimaru_app/services/permission_handler_service.dart' as _i33;
import 'package:yimaru_app/services/course_service.dart' as _i42;
import 'package:yimaru_app/services/dio_service.dart' as _i32;
import 'package:yimaru_app/services/google_auth_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 _i36;
import 'package:yimaru_app/services/in_app_update_service.dart' as _i47;
import 'package:yimaru_app/services/notification_service.dart' as _i39;
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/smart_auth_service.dart' as _i40;
import 'package:yimaru_app/services/status_checker_service.dart' as _i32;
import 'package:yimaru_app/services/voice_recorder_service.dart' as _i44;
import 'package:yimaru_app/ui/common/enmus.dart' as _i45;
import 'package:yimaru_app/services/smart_auth_service.dart' as _i41;
import 'package:yimaru_app/services/status_checker_service.dart' as _i33;
import 'package:yimaru_app/services/voice_recorder_service.dart' as _i45;
import 'package:yimaru_app/ui/common/enmus.dart' as _i46;
// ignore_for_file: type=lint
// 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>>);
@override
_i9.Future<List<_i14.Question>> getAssessments() => (super.noSuchMethod(
_i9.Future<List<_i14.Assessment>> getAssessments() => (super.noSuchMethod(
Invocation.method(
#getAssessments,
[],
),
returnValue: _i9.Future<List<_i14.Question>>.value(<_i14.Question>[]),
returnValue:
_i9.Future<List<_i14.Assessment>>.value(<_i14.Assessment>[]),
returnValueForMissingStub:
_i9.Future<List<_i14.Question>>.value(<_i14.Question>[]),
) as _i9.Future<List<_i14.Question>>);
_i9.Future<List<_i14.Assessment>>.value(<_i14.Assessment>[]),
) as _i9.Future<List<_i14.Assessment>>);
@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(
#getLearnPrograms,
[],
),
returnValue:
_i9.Future<List<_i15.LearnProgram>>.value(<_i15.LearnProgram>[]),
_i9.Future<List<_i16.LearnProgram>>.value(<_i16.LearnProgram>[]),
returnValueForMissingStub:
_i9.Future<List<_i15.LearnProgram>>.value(<_i15.LearnProgram>[]),
) as _i9.Future<List<_i15.LearnProgram>>);
_i9.Future<List<_i16.LearnProgram>>.value(<_i16.LearnProgram>[]),
) as _i9.Future<List<_i16.LearnProgram>>);
@override
_i9.Future<List<_i16.LearnCourse>> getLearnCourse(int? id) =>
_i9.Future<List<_i17.LearnCourse>> getLearnCourse(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnCourse,
[id],
),
returnValue:
_i9.Future<List<_i16.LearnCourse>>.value(<_i16.LearnCourse>[]),
_i9.Future<List<_i17.LearnCourse>>.value(<_i17.LearnCourse>[]),
returnValueForMissingStub:
_i9.Future<List<_i16.LearnCourse>>.value(<_i16.LearnCourse>[]),
) as _i9.Future<List<_i16.LearnCourse>>);
_i9.Future<List<_i17.LearnCourse>>.value(<_i17.LearnCourse>[]),
) as _i9.Future<List<_i17.LearnCourse>>);
@override
_i9.Future<List<_i17.LearnPractice>> getLearnCoursePractices(int? id) =>
_i9.Future<List<_i18.LearnPractice>> getLearnCoursePractices(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnCoursePractices,
[id],
),
returnValue:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]),
_i9.Future<List<_i18.LearnPractice>>.value(<_i18.LearnPractice>[]),
returnValueForMissingStub:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]),
) as _i9.Future<List<_i17.LearnPractice>>);
_i9.Future<List<_i18.LearnPractice>>.value(<_i18.LearnPractice>[]),
) as _i9.Future<List<_i18.LearnPractice>>);
@override
_i9.Future<List<_i18.LearnModule>> getLearnModules(int? id) =>
_i9.Future<List<_i19.LearnModule>> getLearnModules(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnModules,
[id],
),
returnValue:
_i9.Future<List<_i18.LearnModule>>.value(<_i18.LearnModule>[]),
_i9.Future<List<_i19.LearnModule>>.value(<_i19.LearnModule>[]),
returnValueForMissingStub:
_i9.Future<List<_i18.LearnModule>>.value(<_i18.LearnModule>[]),
) as _i9.Future<List<_i18.LearnModule>>);
_i9.Future<List<_i19.LearnModule>>.value(<_i19.LearnModule>[]),
) as _i9.Future<List<_i19.LearnModule>>);
@override
_i9.Future<List<_i17.LearnPractice>> getLearnModulePractices(int? id) =>
_i9.Future<List<_i18.LearnPractice>> getLearnModulePractices(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnModulePractices,
[id],
),
returnValue:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]),
_i9.Future<List<_i18.LearnPractice>>.value(<_i18.LearnPractice>[]),
returnValueForMissingStub:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]),
) as _i9.Future<List<_i17.LearnPractice>>);
_i9.Future<List<_i18.LearnPractice>>.value(<_i18.LearnPractice>[]),
) as _i9.Future<List<_i18.LearnPractice>>);
@override
_i9.Future<List<_i19.LearnLesson>> getLearnLessons(int? id) =>
_i9.Future<List<_i20.LearnLesson>> getLearnLessons(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnLessons,
[id],
),
returnValue:
_i9.Future<List<_i19.LearnLesson>>.value(<_i19.LearnLesson>[]),
_i9.Future<List<_i20.LearnLesson>>.value(<_i20.LearnLesson>[]),
returnValueForMissingStub:
_i9.Future<List<_i19.LearnLesson>>.value(<_i19.LearnLesson>[]),
) as _i9.Future<List<_i19.LearnLesson>>);
_i9.Future<List<_i20.LearnLesson>>.value(<_i20.LearnLesson>[]),
) as _i9.Future<List<_i20.LearnLesson>>);
@override
_i9.Future<List<_i17.LearnPractice>> getLearnLessonPractices(int? id) =>
_i9.Future<List<_i18.LearnPractice>> getLearnLessonPractices(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnLessonPractices,
[id],
),
returnValue:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]),
_i9.Future<List<_i18.LearnPractice>>.value(<_i18.LearnPractice>[]),
returnValueForMissingStub:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]),
) as _i9.Future<List<_i17.LearnPractice>>);
_i9.Future<List<_i18.LearnPractice>>.value(<_i18.LearnPractice>[]),
) as _i9.Future<List<_i18.LearnPractice>>);
@override
_i9.Future<List<_i20.LearnQuestion>> getLearnQuestions(int? id) =>
_i9.Future<List<_i21.LearnQuestion>> getLearnQuestions(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnQuestions,
[id],
),
returnValue:
_i9.Future<List<_i20.LearnQuestion>>.value(<_i20.LearnQuestion>[]),
_i9.Future<List<_i21.LearnQuestion>>.value(<_i21.LearnQuestion>[]),
returnValueForMissingStub:
_i9.Future<List<_i20.LearnQuestion>>.value(<_i20.LearnQuestion>[]),
) as _i9.Future<List<_i20.LearnQuestion>>);
_i9.Future<List<_i21.LearnQuestion>>.value(<_i21.LearnQuestion>[]),
) as _i9.Future<List<_i21.LearnQuestion>>);
@override
_i9.Future<List<_i21.Category>> getCategories() => (super.noSuchMethod(
_i9.Future<List<_i22.Category>> getCategories() => (super.noSuchMethod(
Invocation.method(
#getCategories,
[],
),
returnValue: _i9.Future<List<_i21.Category>>.value(<_i21.Category>[]),
returnValue: _i9.Future<List<_i22.Category>>.value(<_i22.Category>[]),
returnValueForMissingStub:
_i9.Future<List<_i21.Category>>.value(<_i21.Category>[]),
) as _i9.Future<List<_i21.Category>>);
_i9.Future<List<_i22.Category>>.value(<_i22.Category>[]),
) as _i9.Future<List<_i22.Category>>);
@override
_i9.Future<List<_i22.Subcategory>> getSubcategories(int? id) =>
_i9.Future<List<_i23.Subcategory>> getSubcategories(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getSubcategories,
[id],
),
returnValue:
_i9.Future<List<_i22.Subcategory>>.value(<_i22.Subcategory>[]),
_i9.Future<List<_i23.Subcategory>>.value(<_i23.Subcategory>[]),
returnValueForMissingStub:
_i9.Future<List<_i22.Subcategory>>.value(<_i22.Subcategory>[]),
) as _i9.Future<List<_i22.Subcategory>>);
_i9.Future<List<_i23.Subcategory>>.value(<_i23.Subcategory>[]),
) as _i9.Future<List<_i23.Subcategory>>);
@override
_i9.Future<List<_i23.CourseProgress>> getCourseProgress(int? id) =>
_i9.Future<List<_i24.CourseProgress>> getCourseProgress(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getCourseProgress,
[id],
),
returnValue: _i9.Future<List<_i23.CourseProgress>>.value(
<_i23.CourseProgress>[]),
returnValueForMissingStub: _i9.Future<List<_i23.CourseProgress>>.value(
<_i23.CourseProgress>[]),
) as _i9.Future<List<_i23.CourseProgress>>);
returnValue: _i9.Future<List<_i24.CourseProgress>>.value(
<_i24.CourseProgress>[]),
returnValueForMissingStub: _i9.Future<List<_i24.CourseProgress>>.value(
<_i24.CourseProgress>[]),
) as _i9.Future<List<_i24.CourseProgress>>);
@override
_i9.Future<List<_i24.CourseLesson>> getCourseLessons(int? id) =>
_i9.Future<List<_i25.CourseLesson>> getCourseLessons(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getCourseLessons,
[id],
),
returnValue:
_i9.Future<List<_i24.CourseLesson>>.value(<_i24.CourseLesson>[]),
_i9.Future<List<_i25.CourseLesson>>.value(<_i25.CourseLesson>[]),
returnValueForMissingStub:
_i9.Future<List<_i24.CourseLesson>>.value(<_i24.CourseLesson>[]),
) as _i9.Future<List<_i24.CourseLesson>>);
_i9.Future<List<_i25.CourseLesson>>.value(<_i25.CourseLesson>[]),
) as _i9.Future<List<_i25.CourseLesson>>);
@override
_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>>);
@override
_i9.Future<List<_i25.Practice>> getCoursePractices(int? id) =>
_i9.Future<List<_i26.Practice>> getCoursePractices(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getCoursePractices,
[id],
),
returnValue: _i9.Future<List<_i25.Practice>>.value(<_i25.Practice>[]),
returnValue: _i9.Future<List<_i26.Practice>>.value(<_i26.Practice>[]),
returnValueForMissingStub:
_i9.Future<List<_i25.Practice>>.value(<_i25.Practice>[]),
) as _i9.Future<List<_i25.Practice>>);
_i9.Future<List<_i26.Practice>>.value(<_i26.Practice>[]),
) as _i9.Future<List<_i26.Practice>>);
@override
_i9.Future<List<_i14.Question>> getCoursePracticeQuestions(int? id) =>
_i9.Future<List<_i15.AssessmentQuestion>> getCoursePracticeQuestions(
int? id) =>
(super.noSuchMethod(
Invocation.method(
#getCoursePracticeQuestions,
[id],
),
returnValue: _i9.Future<List<_i14.Question>>.value(<_i14.Question>[]),
returnValue: _i9.Future<List<_i15.AssessmentQuestion>>.value(
<_i15.AssessmentQuestion>[]),
returnValueForMissingStub:
_i9.Future<List<_i14.Question>>.value(<_i14.Question>[]),
) as _i9.Future<List<_i14.Question>>);
_i9.Future<List<_i15.AssessmentQuestion>>.value(
<_i15.AssessmentQuestion>[]),
) as _i9.Future<List<_i15.AssessmentQuestion>>);
@override
_i9.Future<_i14.Question?> getCoursePracticeQuestion(int? id) =>
_i9.Future<_i15.AssessmentQuestion?> getCoursePracticeQuestion(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getCoursePracticeQuestion,
[id],
),
returnValue: _i9.Future<_i14.Question?>.value(),
returnValueForMissingStub: _i9.Future<_i14.Question?>.value(),
) as _i9.Future<_i14.Question?>);
returnValue: _i9.Future<_i15.AssessmentQuestion?>.value(),
returnValueForMissingStub: _i9.Future<_i15.AssessmentQuestion?>.value(),
) as _i9.Future<_i15.AssessmentQuestion?>);
@override
_i9.Future<List<_i22.Subcategory>> getLearnSubcategories() =>
_i9.Future<List<_i23.Subcategory>> getLearnSubcategories() =>
(super.noSuchMethod(
Invocation.method(
#getLearnSubcategories,
[],
),
returnValue:
_i9.Future<List<_i22.Subcategory>>.value(<_i22.Subcategory>[]),
_i9.Future<List<_i23.Subcategory>>.value(<_i23.Subcategory>[]),
returnValueForMissingStub:
_i9.Future<List<_i22.Subcategory>>.value(<_i22.Subcategory>[]),
) as _i9.Future<List<_i22.Subcategory>>);
_i9.Future<List<_i23.Subcategory>>.value(<_i23.Subcategory>[]),
) as _i9.Future<List<_i23.Subcategory>>);
@override
_i9.Future<List<_i26.Course>> getCourses(int? id) => (super.noSuchMethod(
_i9.Future<List<_i27.Course>> getCourses(int? id) => (super.noSuchMethod(
Invocation.method(
#getCourses,
[id],
),
returnValue: _i9.Future<List<_i26.Course>>.value(<_i26.Course>[]),
returnValue: _i9.Future<List<_i27.Course>>.value(<_i27.Course>[]),
returnValueForMissingStub:
_i9.Future<List<_i26.Course>>.value(<_i26.Course>[]),
) as _i9.Future<List<_i26.Course>>);
_i9.Future<List<_i27.Course>>.value(<_i27.Course>[]),
) as _i9.Future<List<_i27.Course>>);
@override
_i9.Future<List<_i27.Level>> getLevels(int? id) => (super.noSuchMethod(
_i9.Future<List<_i28.Level>> getLevels(int? id) => (super.noSuchMethod(
Invocation.method(
#getLevels,
[id],
),
returnValue: _i9.Future<List<_i27.Level>>.value(<_i27.Level>[]),
returnValue: _i9.Future<List<_i28.Level>>.value(<_i28.Level>[]),
returnValueForMissingStub:
_i9.Future<List<_i27.Level>>.value(<_i27.Level>[]),
) as _i9.Future<List<_i27.Level>>);
_i9.Future<List<_i28.Level>>.value(<_i28.Level>[]),
) as _i9.Future<List<_i28.Level>>);
@override
_i9.Future<List<_i28.Module>> getModules(int? id) => (super.noSuchMethod(
_i9.Future<List<_i29.Module>> getModules(int? id) => (super.noSuchMethod(
Invocation.method(
#getModules,
[id],
),
returnValue: _i9.Future<List<_i28.Module>>.value(<_i28.Module>[]),
returnValue: _i9.Future<List<_i29.Module>>.value(<_i29.Module>[]),
returnValueForMissingStub:
_i9.Future<List<_i28.Module>>.value(<_i28.Module>[]),
) as _i9.Future<List<_i28.Module>>);
_i9.Future<List<_i29.Module>>.value(<_i29.Module>[]),
) as _i9.Future<List<_i29.Module>>);
@override
_i9.Future<List<_i29.Submodule>> getSubmodules(int? id) =>
_i9.Future<List<_i30.Submodule>> getSubmodules(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getSubmodules,
[id],
),
returnValue: _i9.Future<List<_i29.Submodule>>.value(<_i29.Submodule>[]),
returnValue: _i9.Future<List<_i30.Submodule>>.value(<_i30.Submodule>[]),
returnValueForMissingStub:
_i9.Future<List<_i29.Submodule>>.value(<_i29.Submodule>[]),
) as _i9.Future<List<_i29.Submodule>>);
_i9.Future<List<_i30.Submodule>>.value(<_i30.Submodule>[]),
) as _i9.Future<List<_i30.Submodule>>);
@override
_i9.Future<List<_i30.Lesson>> getLessons(int? id) => (super.noSuchMethod(
_i9.Future<List<_i31.Lesson>> getLessons(int? id) => (super.noSuchMethod(
Invocation.method(
#getLessons,
[id],
),
returnValue: _i9.Future<List<_i30.Lesson>>.value(<_i30.Lesson>[]),
returnValue: _i9.Future<List<_i31.Lesson>>.value(<_i31.Lesson>[]),
returnValueForMissingStub:
_i9.Future<List<_i30.Lesson>>.value(<_i30.Lesson>[]),
) as _i9.Future<List<_i30.Lesson>>);
_i9.Future<List<_i31.Lesson>>.value(<_i31.Lesson>[]),
) as _i9.Future<List<_i31.Lesson>>);
@override
_i9.Future<List<_i25.Practice>> getPractices(int? id) => (super.noSuchMethod(
_i9.Future<List<_i26.Practice>> getPractices(int? id) => (super.noSuchMethod(
Invocation.method(
#getPractices,
[id],
),
returnValue: _i9.Future<List<_i25.Practice>>.value(<_i25.Practice>[]),
returnValue: _i9.Future<List<_i26.Practice>>.value(<_i26.Practice>[]),
returnValueForMissingStub:
_i9.Future<List<_i25.Practice>>.value(<_i25.Practice>[]),
) as _i9.Future<List<_i25.Practice>>);
_i9.Future<List<_i26.Practice>>.value(<_i26.Practice>[]),
) as _i9.Future<List<_i26.Practice>>);
@override
_i9.Future<List<_i14.Question>> getQuestions(int? id) => (super.noSuchMethod(
_i9.Future<List<_i15.AssessmentQuestion>> getQuestions(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getQuestions,
[id],
),
returnValue: _i9.Future<List<_i14.Question>>.value(<_i14.Question>[]),
returnValue: _i9.Future<List<_i15.AssessmentQuestion>>.value(
<_i15.AssessmentQuestion>[]),
returnValueForMissingStub:
_i9.Future<List<_i14.Question>>.value(<_i14.Question>[]),
) as _i9.Future<List<_i14.Question>>);
_i9.Future<List<_i15.AssessmentQuestion>>.value(
<_i15.AssessmentQuestion>[]),
) as _i9.Future<List<_i15.AssessmentQuestion>>);
}
/// A class which mocks [SecureStorageService].
@ -1528,7 +1550,7 @@ class MockSecureStorageService extends _i1.Mock
/// A class which mocks [DioService].
///
/// 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
_i2.Dio get dio => (super.noSuchMethod(
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.
class MockStatusCheckerService extends _i1.Mock
implements _i32.StatusCheckerService {
implements _i33.StatusCheckerService {
@override
_i3.SecureStorageService get storage => (super.noSuchMethod(
Invocation.getter(#storage),
@ -1583,40 +1605,40 @@ class MockStatusCheckerService extends _i1.Mock
///
/// See the documentation for Mockito's code generation for more information.
class MockPermissionHandlerService extends _i1.Mock
implements _i33.PermissionHandlerService {
implements _i34.PermissionHandlerService {
@override
_i9.Future<_i34.PermissionStatus> requestPermission(
_i34.Permission? requestedPermission) =>
_i9.Future<_i35.PermissionStatus> requestPermission(
_i35.Permission? requestedPermission) =>
(super.noSuchMethod(
Invocation.method(
#requestPermission,
[requestedPermission],
),
returnValue: _i9.Future<_i34.PermissionStatus>.value(
_i34.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i34.PermissionStatus>.value(
_i34.PermissionStatus.denied),
) as _i9.Future<_i34.PermissionStatus>);
returnValue: _i9.Future<_i35.PermissionStatus>.value(
_i35.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i35.PermissionStatus>.value(
_i35.PermissionStatus.denied),
) as _i9.Future<_i35.PermissionStatus>);
@override
_i9.Future<_i34.PermissionStatus> request(_i34.Permission? permission) =>
_i9.Future<_i35.PermissionStatus> request(_i35.Permission? permission) =>
(super.noSuchMethod(
Invocation.method(
#request,
[permission],
),
returnValue: _i9.Future<_i34.PermissionStatus>.value(
_i34.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i34.PermissionStatus>.value(
_i34.PermissionStatus.denied),
) as _i9.Future<_i34.PermissionStatus>);
returnValue: _i9.Future<_i35.PermissionStatus>.value(
_i35.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i35.PermissionStatus>.value(
_i35.PermissionStatus.denied),
) as _i9.Future<_i35.PermissionStatus>);
}
/// A class which mocks [ImagePickerService].
///
/// See the documentation for Mockito's code generation for more information.
class MockImagePickerService extends _i1.Mock
implements _i35.ImagePickerService {
implements _i36.ImagePickerService {
@override
_i9.Future<String?> gallery() => (super.noSuchMethod(
Invocation.method(
@ -1641,7 +1663,7 @@ class MockImagePickerService extends _i1.Mock
/// A class which mocks [GoogleAuthService].
///
/// 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
int get listenersCount => (super.noSuchMethod(
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.
class MockImageDownloaderService extends _i1.Mock
implements _i37.ImageDownloaderService {
implements _i38.ImageDownloaderService {
@override
_i9.Future<String> downloader(String? networkImage) => (super.noSuchMethod(
Invocation.method(
@ -1740,7 +1762,7 @@ class MockImageDownloaderService extends _i1.Mock
///
/// See the documentation for Mockito's code generation for more information.
class MockNotificationService extends _i1.Mock
implements _i38.NotificationService {
implements _i39.NotificationService {
@override
_i9.Future<void> initialize() => (super.noSuchMethod(
Invocation.method(
@ -1762,7 +1784,7 @@ class MockNotificationService extends _i1.Mock
) as _i9.Future<void>);
@override
_i9.Future<void> showNotification(_i39.RemoteMessage? message) =>
_i9.Future<void> showNotification(_i40.RemoteMessage? message) =>
(super.noSuchMethod(
Invocation.method(
#showNotification,
@ -1796,7 +1818,7 @@ class MockNotificationService extends _i1.Mock
/// A class which mocks [SmartAuthService].
///
/// 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
bool get listenForMultipleSms => (super.noSuchMethod(
Invocation.getter(#listenForMultipleSms),
@ -1828,26 +1850,26 @@ class MockSmartAuthService extends _i1.Mock implements _i40.SmartAuthService {
/// A class which mocks [CourseService].
///
/// 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
_i9.Future<List<_i42.CourseDetail>> getCoursesDetail(int? id) =>
_i9.Future<List<_i43.CourseDetail>> getCoursesDetail(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getCoursesDetail,
[id],
),
returnValue:
_i9.Future<List<_i42.CourseDetail>>.value(<_i42.CourseDetail>[]),
_i9.Future<List<_i43.CourseDetail>>.value(<_i43.CourseDetail>[]),
returnValueForMissingStub:
_i9.Future<List<_i42.CourseDetail>>.value(<_i42.CourseDetail>[]),
) as _i9.Future<List<_i42.CourseDetail>>);
_i9.Future<List<_i43.CourseDetail>>.value(<_i43.CourseDetail>[]),
) as _i9.Future<List<_i43.CourseDetail>>);
}
/// A class which mocks [AudioPlayerService].
///
/// See the documentation for Mockito's code generation for more information.
class MockAudioPlayerService extends _i1.Mock
implements _i43.AudioPlayerService {
implements _i44.AudioPlayerService {
@override
_i4.AudioPlayer get player => (super.noSuchMethod(
Invocation.getter(#player),
@ -1971,13 +1993,13 @@ class MockAudioPlayerService extends _i1.Mock
///
/// See the documentation for Mockito's code generation for more information.
class MockVoiceRecorderService extends _i1.Mock
implements _i44.VoiceRecorderService {
implements _i45.VoiceRecorderService {
@override
_i45.VoiceRecordingState get recordingState => (super.noSuchMethod(
_i46.VoiceRecordingState get recordingState => (super.noSuchMethod(
Invocation.getter(#recordingState),
returnValue: _i45.VoiceRecordingState.pending,
returnValueForMissingStub: _i45.VoiceRecordingState.pending,
) as _i45.VoiceRecordingState);
returnValue: _i46.VoiceRecordingState.pending,
returnValueForMissingStub: _i46.VoiceRecordingState.pending,
) as _i46.VoiceRecordingState);
@override
_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.
class MockInAppUpdateService extends _i1.Mock
implements _i46.InAppUpdateService {
implements _i47.InAppUpdateService {
@override
_i9.Future<int> getBatteryLevel() => (super.noSuchMethod(
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());
});
}