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