feat(course): Polish course ui
This commit is contained in:
parent
d75ed8c7c7
commit
4eb6e9d6c3
|
|
@ -38,6 +38,10 @@ import 'package:yimaru_app/services/image_downloader_service.dart';
|
|||
import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart';
|
||||
import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart';
|
||||
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart';
|
||||
import 'package:yimaru_app/ui/views/course/course_view.dart';
|
||||
import 'package:yimaru_app/ui/views/course_module/course_module_view.dart';
|
||||
import 'package:yimaru_app/ui/views/course_practice/course_practice_view.dart';
|
||||
import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart';
|
||||
// @stacked-import
|
||||
|
||||
@StackedApp(
|
||||
|
|
@ -69,6 +73,10 @@ import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart';
|
|||
MaterialRoute(page: ForgetPasswordView),
|
||||
MaterialRoute(page: LearnLessonDetailView),
|
||||
MaterialRoute(page: LearnPracticeView),
|
||||
MaterialRoute(page: CourseView),
|
||||
MaterialRoute(page: CourseModuleView),
|
||||
MaterialRoute(page: CoursePracticeView),
|
||||
MaterialRoute(page: CoursePaymentView),
|
||||
// @stacked-route
|
||||
],
|
||||
dependencies: [
|
||||
|
|
|
|||
|
|
@ -5,15 +5,22 @@
|
|||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:flutter/material.dart' as _i29;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' as _i33;
|
||||
import 'package:stacked/stacked.dart' as _i1;
|
||||
import 'package:stacked_services/stacked_services.dart' as _i30;
|
||||
import 'package:stacked_services/stacked_services.dart' as _i34;
|
||||
import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart'
|
||||
as _i10;
|
||||
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i23;
|
||||
import 'package:yimaru_app/ui/views/call_support/call_support_view.dart'
|
||||
as _i13;
|
||||
import 'package:yimaru_app/ui/views/course/course_view.dart' as _i29;
|
||||
import 'package:yimaru_app/ui/views/course_module/course_module_view.dart'
|
||||
as _i30;
|
||||
import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart'
|
||||
as _i32;
|
||||
import 'package:yimaru_app/ui/views/course_practice/course_practice_view.dart'
|
||||
as _i31;
|
||||
import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7;
|
||||
import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i25;
|
||||
import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart'
|
||||
|
|
@ -104,6 +111,14 @@ class Routes {
|
|||
|
||||
static const learnPracticeView = '/learn-practice-view';
|
||||
|
||||
static const courseView = '/course-view';
|
||||
|
||||
static const courseModuleView = '/course-module-view';
|
||||
|
||||
static const coursePracticeView = '/course-practice-view';
|
||||
|
||||
static const coursePaymentView = '/course-payment-view';
|
||||
|
||||
static const all = <String>{
|
||||
homeView,
|
||||
onboardingView,
|
||||
|
|
@ -132,6 +147,10 @@ class Routes {
|
|||
forgetPasswordView,
|
||||
learnLessonDetailView,
|
||||
learnPracticeView,
|
||||
courseView,
|
||||
courseModuleView,
|
||||
coursePracticeView,
|
||||
coursePaymentView,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -245,17 +264,33 @@ class StackedRouter extends _i1.RouterBase {
|
|||
Routes.learnPracticeView,
|
||||
page: _i28.LearnPracticeView,
|
||||
),
|
||||
_i1.RouteDef(
|
||||
Routes.courseView,
|
||||
page: _i29.CourseView,
|
||||
),
|
||||
_i1.RouteDef(
|
||||
Routes.courseModuleView,
|
||||
page: _i30.CourseModuleView,
|
||||
),
|
||||
_i1.RouteDef(
|
||||
Routes.coursePracticeView,
|
||||
page: _i31.CoursePracticeView,
|
||||
),
|
||||
_i1.RouteDef(
|
||||
Routes.coursePaymentView,
|
||||
page: _i32.CoursePaymentView,
|
||||
),
|
||||
];
|
||||
|
||||
final _pagesMap = <Type, _i1.StackedRouteFactory>{
|
||||
_i2.HomeView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i2.HomeView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i3.OnboardingView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i3.OnboardingView(),
|
||||
settings: data,
|
||||
);
|
||||
|
|
@ -264,156 +299,156 @@ class StackedRouter extends _i1.RouterBase {
|
|||
final args = data.getArgs<StartupViewArguments>(
|
||||
orElse: () => const StartupViewArguments(),
|
||||
);
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => _i4.StartupView(key: args.key, label: args.label),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i5.ProfileView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i5.ProfileView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i6.ProfileDetailView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i6.ProfileDetailView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i7.DownloadsView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i7.DownloadsView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i8.ProgressView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i8.ProgressView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i9.OngoingProgressView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i9.OngoingProgressView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i10.AccountPrivacyView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i10.AccountPrivacyView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i11.SupportView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i11.SupportView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i12.TelegramSupportView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i12.TelegramSupportView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i13.CallSupportView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i13.CallSupportView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i14.LanguageView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i14.LanguageView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i15.PrivacyPolicyView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i15.PrivacyPolicyView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i16.TermsAndConditionsView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i16.TermsAndConditionsView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i17.RegisterView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i17.RegisterView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i18.LoginView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i18.LoginView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i19.LearnView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i19.LearnView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i20.LearnLevelView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i20.LearnLevelView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i21.LearnModuleView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i21.LearnModuleView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i22.WelcomeView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i22.WelcomeView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i23.AssessmentView: (data) {
|
||||
final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) =>
|
||||
_i23.AssessmentView(key: args.key, data: args.data),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i24.LearnLessonView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i24.LearnLessonView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i25.FailureView: (data) {
|
||||
final args = data.getArgs<FailureViewArguments>(nullOk: false);
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) =>
|
||||
_i25.FailureView(key: args.key, label: args.label),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i26.ForgetPasswordView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i26.ForgetPasswordView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i27.LearnLessonDetailView: (data) {
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i27.LearnLessonDetailView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i28.LearnPracticeView: (data) {
|
||||
final args = data.getArgs<LearnPracticeViewArguments>(nullOk: false);
|
||||
return _i29.MaterialPageRoute<dynamic>(
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => _i28.LearnPracticeView(
|
||||
key: args.key,
|
||||
title: args.title,
|
||||
|
|
@ -422,6 +457,30 @@ class StackedRouter extends _i1.RouterBase {
|
|||
settings: data,
|
||||
);
|
||||
},
|
||||
_i29.CourseView: (data) {
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i29.CourseView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i30.CourseModuleView: (data) {
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i30.CourseModuleView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i31.CoursePracticeView: (data) {
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i31.CoursePracticeView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i32.CoursePaymentView: (data) {
|
||||
return _i33.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i32.CoursePaymentView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@override
|
||||
|
|
@ -437,7 +496,7 @@ class StartupViewArguments {
|
|||
this.label = 'Loading',
|
||||
});
|
||||
|
||||
final _i29.Key? key;
|
||||
final _i33.Key? key;
|
||||
|
||||
final String label;
|
||||
|
||||
|
|
@ -464,7 +523,7 @@ class AssessmentViewArguments {
|
|||
required this.data,
|
||||
});
|
||||
|
||||
final _i29.Key? key;
|
||||
final _i33.Key? key;
|
||||
|
||||
final Map<String, dynamic> data;
|
||||
|
||||
|
|
@ -491,7 +550,7 @@ class FailureViewArguments {
|
|||
required this.label,
|
||||
});
|
||||
|
||||
final _i29.Key? key;
|
||||
final _i33.Key? key;
|
||||
|
||||
final String label;
|
||||
|
||||
|
|
@ -520,7 +579,7 @@ class LearnPracticeViewArguments {
|
|||
required this.buttonLabel,
|
||||
});
|
||||
|
||||
final _i29.Key? key;
|
||||
final _i33.Key? key;
|
||||
|
||||
final String title;
|
||||
|
||||
|
|
@ -551,7 +610,7 @@ class LearnPracticeViewArguments {
|
|||
}
|
||||
}
|
||||
|
||||
extension NavigatorStateExtension on _i30.NavigationService {
|
||||
extension NavigatorStateExtension on _i34.NavigationService {
|
||||
Future<dynamic> navigateToHomeView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -581,7 +640,7 @@ extension NavigatorStateExtension on _i30.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> navigateToStartupView({
|
||||
_i29.Key? key,
|
||||
_i33.Key? key,
|
||||
String label = 'Loading',
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -850,7 +909,7 @@ extension NavigatorStateExtension on _i30.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> navigateToAssessmentView({
|
||||
_i29.Key? key,
|
||||
_i33.Key? key,
|
||||
required Map<String, dynamic> data,
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -881,7 +940,7 @@ extension NavigatorStateExtension on _i30.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> navigateToFailureView({
|
||||
_i29.Key? key,
|
||||
_i33.Key? key,
|
||||
required String label,
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -926,7 +985,7 @@ extension NavigatorStateExtension on _i30.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> navigateToLearnPracticeView({
|
||||
_i29.Key? key,
|
||||
_i33.Key? key,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required String buttonLabel,
|
||||
|
|
@ -948,6 +1007,62 @@ extension NavigatorStateExtension on _i30.NavigationService {
|
|||
transition: transition);
|
||||
}
|
||||
|
||||
Future<dynamic> navigateToCourseView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||
transition,
|
||||
]) async {
|
||||
return navigateTo<dynamic>(Routes.courseView,
|
||||
id: routerId,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
transition: transition);
|
||||
}
|
||||
|
||||
Future<dynamic> navigateToCourseModuleView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||
transition,
|
||||
]) async {
|
||||
return navigateTo<dynamic>(Routes.courseModuleView,
|
||||
id: routerId,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
transition: transition);
|
||||
}
|
||||
|
||||
Future<dynamic> navigateToCoursePracticeView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||
transition,
|
||||
]) async {
|
||||
return navigateTo<dynamic>(Routes.coursePracticeView,
|
||||
id: routerId,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
transition: transition);
|
||||
}
|
||||
|
||||
Future<dynamic> navigateToCoursePaymentView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||
transition,
|
||||
]) async {
|
||||
return navigateTo<dynamic>(Routes.coursePaymentView,
|
||||
id: routerId,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
transition: transition);
|
||||
}
|
||||
|
||||
Future<dynamic> replaceWithHomeView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -977,7 +1092,7 @@ extension NavigatorStateExtension on _i30.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> replaceWithStartupView({
|
||||
_i29.Key? key,
|
||||
_i33.Key? key,
|
||||
String label = 'Loading',
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -1246,7 +1361,7 @@ extension NavigatorStateExtension on _i30.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> replaceWithAssessmentView({
|
||||
_i29.Key? key,
|
||||
_i33.Key? key,
|
||||
required Map<String, dynamic> data,
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -1277,7 +1392,7 @@ extension NavigatorStateExtension on _i30.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> replaceWithFailureView({
|
||||
_i29.Key? key,
|
||||
_i33.Key? key,
|
||||
required String label,
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -1322,7 +1437,7 @@ extension NavigatorStateExtension on _i30.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> replaceWithLearnPracticeView({
|
||||
_i29.Key? key,
|
||||
_i33.Key? key,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required String buttonLabel,
|
||||
|
|
@ -1343,4 +1458,60 @@ extension NavigatorStateExtension on _i30.NavigationService {
|
|||
parameters: parameters,
|
||||
transition: transition);
|
||||
}
|
||||
|
||||
Future<dynamic> replaceWithCourseView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||
transition,
|
||||
]) async {
|
||||
return replaceWith<dynamic>(Routes.courseView,
|
||||
id: routerId,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
transition: transition);
|
||||
}
|
||||
|
||||
Future<dynamic> replaceWithCourseModuleView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||
transition,
|
||||
]) async {
|
||||
return replaceWith<dynamic>(Routes.courseModuleView,
|
||||
id: routerId,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
transition: transition);
|
||||
}
|
||||
|
||||
Future<dynamic> replaceWithCoursePracticeView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||
transition,
|
||||
]) async {
|
||||
return replaceWith<dynamic>(Routes.coursePracticeView,
|
||||
id: routerId,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
transition: transition);
|
||||
}
|
||||
|
||||
Future<dynamic> replaceWithCoursePaymentView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||
transition,
|
||||
]) async {
|
||||
return replaceWith<dynamic>(Routes.coursePaymentView,
|
||||
id: routerId,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
transition: transition);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ class Assessment {
|
|||
|
||||
final String? status;
|
||||
|
||||
final List<Option>? options;
|
||||
|
||||
|
||||
@JsonKey(name: 'question_type')
|
||||
final String? questionType;
|
||||
|
||||
|
|
@ -19,7 +22,6 @@ class Assessment {
|
|||
@JsonKey(name: 'difficulty_level')
|
||||
final String? difficultyLevel;
|
||||
|
||||
final List<Option>? options;
|
||||
|
||||
const Assessment({
|
||||
this.id,
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ Map<String, dynamic> _$AssessmentToJson(Assessment instance) =>
|
|||
'id': instance.id,
|
||||
'points': instance.points,
|
||||
'status': instance.status,
|
||||
'options': instance.options,
|
||||
'question_type': instance.questionType,
|
||||
'question_text': instance.questionText,
|
||||
'difficulty_level': instance.difficultyLevel,
|
||||
'options': instance.options,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -57,6 +57,37 @@ class UserModel {
|
|||
this.profileCompleted,
|
||||
});
|
||||
|
||||
UserModel copyWith(
|
||||
{int? userId,
|
||||
String? email,
|
||||
String? gender,
|
||||
String? region,
|
||||
String? country,
|
||||
String? lastName,
|
||||
String? birthday,
|
||||
String? firstName,
|
||||
String? occupation,
|
||||
String? accessToken,
|
||||
String? refreshToken,
|
||||
bool? userInfoLoaded,
|
||||
bool? profileCompleted,
|
||||
String? profilePicture}) =>
|
||||
UserModel(
|
||||
email: email ?? this.email,
|
||||
userId: userId ?? this.userId,
|
||||
gender: gender ?? this.gender,
|
||||
region: region ?? this.region,
|
||||
country: country ?? this.country,
|
||||
lastName: lastName ?? this.lastName,
|
||||
birthday: birthday ?? this.birthday,
|
||||
firstName: firstName ?? this.firstName,
|
||||
occupation: occupation ?? this.occupation,
|
||||
accessToken: accessToken ?? this.accessToken,
|
||||
refreshToken: refreshToken ?? this.refreshToken,
|
||||
userInfoLoaded: userInfoLoaded ?? this.userInfoLoaded,
|
||||
profilePicture: profilePicture ?? this.profilePicture,
|
||||
profileCompleted: profileCompleted ?? this.profileCompleted);
|
||||
|
||||
factory UserModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserModelFromJson(json);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import '../app/app.locator.dart';
|
|||
import '../ui/common/enmus.dart';
|
||||
|
||||
class ApiService {
|
||||
// Dependency injection
|
||||
final _service = locator<DioService>();
|
||||
|
||||
// Register
|
||||
|
|
@ -38,7 +39,7 @@ class ApiService {
|
|||
}
|
||||
}
|
||||
|
||||
// Email Login
|
||||
// Email login
|
||||
Future<Map<String, dynamic>> emailLogin(Map<String, dynamic> data) async {
|
||||
try {
|
||||
Response response = await _service.dio.post(
|
||||
|
|
|
|||
|
|
@ -4,16 +4,20 @@ import 'package:yimaru_app/models/user_model.dart';
|
|||
import 'package:yimaru_app/services/secure_storage_service.dart';
|
||||
|
||||
class AuthenticationService with ListenableServiceMixin {
|
||||
// Dependency injection
|
||||
final _secureService = locator<SecureStorageService>();
|
||||
|
||||
AuthenticationService() {
|
||||
listenToReactiveValues([_user]);
|
||||
}
|
||||
|
||||
// User data
|
||||
UserModel? _user;
|
||||
|
||||
UserModel? get user => _user;
|
||||
|
||||
// Initialization
|
||||
AuthenticationService() {
|
||||
listenToReactiveValues([_user]);
|
||||
}
|
||||
|
||||
// Check user logged in
|
||||
Future<bool> userLoggedIn() async {
|
||||
if (await _secureService.getString('userId') != null) {
|
||||
return true;
|
||||
|
|
@ -21,14 +25,18 @@ class AuthenticationService with ListenableServiceMixin {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Get access token
|
||||
Future<String?> getAccessToken() async =>
|
||||
await _secureService.getString('accessToken');
|
||||
|
||||
// Get refresh token
|
||||
Future<String?> getRefreshToken() async =>
|
||||
await _secureService.getString('refreshToken');
|
||||
|
||||
// Get user id
|
||||
Future<int?> getUserId() async => await _secureService.getInt('userId');
|
||||
|
||||
// Save tokens
|
||||
Future<void> saveTokens({
|
||||
required String access,
|
||||
required String refresh,
|
||||
|
|
@ -37,6 +45,7 @@ class AuthenticationService with ListenableServiceMixin {
|
|||
await _secureService.setString('refreshToken', refresh);
|
||||
}
|
||||
|
||||
// Save user credential
|
||||
Future<void> saveUserCredential(Map<String, dynamic> data) async {
|
||||
await _secureService.setInt('userId', data['userId']);
|
||||
await _secureService.setString('accessToken', data['accessToken']);
|
||||
|
|
@ -49,10 +58,16 @@ class AuthenticationService with ListenableServiceMixin {
|
|||
);
|
||||
}
|
||||
|
||||
// Save profile status
|
||||
Future<void> saveProfileStatus(bool value) async {
|
||||
await _secureService.setBool('profileCompleted', value);
|
||||
|
||||
_user = UserModel(
|
||||
_user = _user?.copyWith(
|
||||
userInfoLoaded: _user?.userInfoLoaded ?? false,
|
||||
profileCompleted: await _secureService.getBool('profileCompleted'),
|
||||
);
|
||||
|
||||
/* UserModel(
|
||||
email: _user?.email,
|
||||
gender: _user?.gender,
|
||||
region: _user?.region,
|
||||
|
|
@ -67,12 +82,18 @@ class AuthenticationService with ListenableServiceMixin {
|
|||
profilePicture: _user?.profilePicture,
|
||||
userInfoLoaded: _user?.userInfoLoaded ?? false,
|
||||
profileCompleted: await _secureService.getBool('profileCompleted'));
|
||||
*/
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> saveProfilePicture(String image) async {
|
||||
await _secureService.setString('profilePicture', image);
|
||||
_user = UserModel(
|
||||
_user = _user?.copyWith(
|
||||
userInfoLoaded: _user?.userInfoLoaded ?? false,
|
||||
profilePicture: await _secureService.getString('profilePicture'),
|
||||
);
|
||||
|
||||
/*UserModel(
|
||||
email: _user?.email,
|
||||
gender: _user?.gender,
|
||||
region: _user?.region,
|
||||
|
|
@ -88,7 +109,7 @@ class AuthenticationService with ListenableServiceMixin {
|
|||
userInfoLoaded: _user?.userInfoLoaded ?? false,
|
||||
profilePicture: await _secureService.getString('profilePicture'),
|
||||
);
|
||||
|
||||
*/
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +157,17 @@ class AuthenticationService with ListenableServiceMixin {
|
|||
await _secureService.setString('firstName', data['first_name']);
|
||||
await _secureService.setString('occupation', data['occupation']);
|
||||
|
||||
_user = UserModel(
|
||||
_user = _user?.copyWith(
|
||||
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'),
|
||||
);
|
||||
|
||||
/*UserModel(
|
||||
email: _user?.email,
|
||||
userId: _user?.userId,
|
||||
accessToken: _user?.accessToken,
|
||||
|
|
@ -150,7 +181,7 @@ class AuthenticationService with ListenableServiceMixin {
|
|||
birthday: await _secureService.getString('birthday'),
|
||||
firstName: await _secureService.getString('firstName'),
|
||||
occupation: await _secureService.getString('occupation'),
|
||||
);
|
||||
);*/
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,15 +9,21 @@ import '../app/app.locator.dart';
|
|||
import '../ui/common/app_constants.dart';
|
||||
|
||||
class DioService {
|
||||
// Dependency injection
|
||||
final _navigationService = locator<NavigationService>();
|
||||
final _authenticationService = locator<AuthenticationService>();
|
||||
|
||||
// Initialization
|
||||
final Dio _dio = Dio();
|
||||
|
||||
Dio get dio => _dio;
|
||||
|
||||
final Dio _refreshDio = Dio(); // separate instance
|
||||
|
||||
bool _isRefreshing = false;
|
||||
final List<void Function()> _retryQueue = [];
|
||||
|
||||
// Initialization
|
||||
DioService() {
|
||||
_dio.options
|
||||
..baseUrl = kBaseUrl
|
||||
|
|
@ -33,6 +39,7 @@ class DioService {
|
|||
);
|
||||
}
|
||||
|
||||
// Response logger
|
||||
void _onResponse(
|
||||
Response response,
|
||||
ResponseInterceptorHandler handler,
|
||||
|
|
@ -69,6 +76,7 @@ class DioService {
|
|||
handler.next(options);
|
||||
}
|
||||
|
||||
// Error logger
|
||||
Future<void> _onError(
|
||||
DioException error,
|
||||
ErrorInterceptorHandler handler,
|
||||
|
|
@ -125,6 +133,7 @@ class DioService {
|
|||
}
|
||||
}
|
||||
|
||||
// Refresh token
|
||||
Future<bool> _refreshToken() async {
|
||||
final UserModel? user = await _authenticationService.getUser();
|
||||
|
||||
|
|
@ -155,9 +164,8 @@ class DioService {
|
|||
}
|
||||
}
|
||||
|
||||
// Check request if immediately after token refreshed
|
||||
bool _isRefreshRequest(RequestOptions options) {
|
||||
return options.path.contains(kRefreshTokenUrl);
|
||||
}
|
||||
|
||||
Dio get dio => _dio;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import 'package:google_sign_in/google_sign_in.dart';
|
|||
import 'package:yimaru_app/ui/common/app_constants.dart';
|
||||
|
||||
class GoogleAuthService {
|
||||
// Initialization
|
||||
final GoogleSignIn signIn = GoogleSignIn.instance;
|
||||
|
||||
// Google authentication
|
||||
Future<GoogleSignInAccount?> googleAuth() async {
|
||||
try {
|
||||
GoogleSignInAccount? googleUser;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ import '../ui/common/app_constants.dart';
|
|||
import 'dio_service.dart';
|
||||
|
||||
class ImageDownloaderService {
|
||||
// Dependency injection
|
||||
final _service = locator<DioService>();
|
||||
|
||||
// Image downloader
|
||||
Future<String> downloader(String? networkImage) async {
|
||||
late File image;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,13 @@ import '../app/app.locator.dart';
|
|||
import '../ui/common/ui_helpers.dart';
|
||||
|
||||
class ImagePickerService {
|
||||
// Dependency injection
|
||||
final _permissionHandler = locator<PermissionHandlerService>();
|
||||
|
||||
// Initialization
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
|
||||
// Pick image from gallery
|
||||
Future<String?> gallery() async {
|
||||
try {
|
||||
PermissionStatus status =
|
||||
|
|
@ -32,6 +35,7 @@ class ImagePickerService {
|
|||
}
|
||||
}
|
||||
|
||||
// Pick image from camera
|
||||
Future<String?> camera() async {
|
||||
try {
|
||||
PermissionStatus status =
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import 'package:permission_handler/permission_handler.dart';
|
|||
import '../ui/common/ui_helpers.dart';
|
||||
|
||||
class PermissionHandlerService {
|
||||
|
||||
// Check permission category
|
||||
Future<PermissionStatus> requestPermission(
|
||||
Permission requestedPermission) async {
|
||||
if (requestedPermission == Permission.camera) {
|
||||
|
|
@ -17,6 +19,7 @@ class PermissionHandlerService {
|
|||
return PermissionStatus.denied;
|
||||
}
|
||||
|
||||
// Request permission
|
||||
Future<PermissionStatus> request(Permission permission) async {
|
||||
if (await permission.isDenied) {
|
||||
final PermissionStatus status = await permission.request();
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ extension BoolParsing on String {
|
|||
}
|
||||
|
||||
class SecureStorageService {
|
||||
// Create storage
|
||||
|
||||
// Initialization
|
||||
late final FlutterSecureStorage _storage;
|
||||
|
||||
SecureStorageService() {
|
||||
|
|
@ -31,33 +30,40 @@ class SecureStorageService {
|
|||
);
|
||||
}
|
||||
|
||||
// Clear storage data
|
||||
Future<void> clear() async {
|
||||
_storage.deleteAll();
|
||||
}
|
||||
|
||||
// Get boolean data from storage
|
||||
Future<bool?> getBool(String key) async {
|
||||
String? result = await _storage.read(key: key);
|
||||
return result?.parseBool();
|
||||
}
|
||||
|
||||
// Get string data from storage
|
||||
Future<String?> getString(String key) async {
|
||||
return await _storage.read(key: key);
|
||||
}
|
||||
|
||||
// Get integer data from storage
|
||||
Future<int?> getInt(String key) async {
|
||||
return await _storage.read(key: key) == null
|
||||
? null
|
||||
: int.parse(await _storage.read(key: key) ?? '0');
|
||||
}
|
||||
|
||||
// Save string data to storage
|
||||
Future<void> setString(String key, String value) async {
|
||||
await _storage.write(key: key, value: value);
|
||||
}
|
||||
|
||||
// Save integer data to storage
|
||||
Future<void> setInt(String key, int value) async {
|
||||
await _storage.write(key: key, value: value.toString());
|
||||
}
|
||||
|
||||
// Save boolean data to storage
|
||||
Future<void> setBool(String key, bool value) async {
|
||||
await _storage.write(key: key, value: value.toString());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,34 +8,28 @@ import 'package:yimaru_app/services/secure_storage_service.dart';
|
|||
import '../app/app.locator.dart';
|
||||
|
||||
class StatusCheckerService {
|
||||
// Dependency injection
|
||||
final storage = locator<SecureStorageService>();
|
||||
|
||||
// Initialization
|
||||
bool _previousConnection = true;
|
||||
|
||||
bool get previousConnection => _previousConnection;
|
||||
|
||||
// Get phone battery level
|
||||
Future<int> getBatteryLevel() async {
|
||||
final battery = Battery();
|
||||
final batteryLevel = await battery.batteryLevel;
|
||||
return batteryLevel;
|
||||
}
|
||||
|
||||
Future<bool> userAuthenticated() async {
|
||||
await checkAndUpdate();
|
||||
|
||||
if (await storage.getString('authenticated') != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check internet connection
|
||||
Future<bool> checkConnection() async {
|
||||
if (await InternetConnection().hasInternetAccess) {
|
||||
_previousConnection = true;
|
||||
return true;
|
||||
} else {
|
||||
if (_previousConnection) {
|
||||
// showErrorToast('Check your internet connection');
|
||||
_previousConnection = false;
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +37,7 @@ class StatusCheckerService {
|
|||
}
|
||||
}
|
||||
|
||||
// Check phone available storage
|
||||
Future<int> getAvailableStorage() async {
|
||||
try {
|
||||
final availableStorage =
|
||||
|
|
@ -53,6 +48,7 @@ class StatusCheckerService {
|
|||
}
|
||||
}
|
||||
|
||||
// Check for latest update
|
||||
Future<void> checkAndUpdate() async {
|
||||
const requiredStorage = 500 * 1024 * 1024;
|
||||
|
||||
|
|
@ -62,16 +58,12 @@ class StatusCheckerService {
|
|||
await getAvailableStorage(); // Implement getAvailableStorage
|
||||
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
|
||||
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
|
||||
// KewedeConst().showErrorToast(
|
||||
// 'Unable to update app, please charge your phone & free up space.');
|
||||
} else if (batteryLevel < 20) {
|
||||
// KewedeConst()
|
||||
// .showErrorToast('Unable to update app, please charge your phone.');
|
||||
} else if (storageAvailable < requiredStorage) {
|
||||
// KewedeConst()
|
||||
// .showErrorToast('Unable to update app, please free up space.');
|
||||
}
|
||||
// Show user-friendly message explaining why update failed and suggesting solutions (e.g., charge device, free up space)
|
||||
return; // Prevent update from starting
|
||||
}
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@ String kGoogleAuthUrl = 'api/v1/auth/google/android';
|
|||
|
||||
String kAssessmentsUrl = 'api/v1/assessment/questions';
|
||||
|
||||
String kServerClientId =
|
||||
'574860813475-n5o17gpprdqmhcml99tiqhafb17rob0r.apps.googleusercontent.com';
|
||||
|
||||
String kSampleVideoUrl =
|
||||
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4';
|
||||
|
||||
String kServerClientId =
|
||||
'574860813475-n5o17gpprdqmhcml99tiqhafb17rob0r.apps.googleusercontent.com';
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
const String ksHomeBottomSheetTitle = 'Build Great Apps!';
|
||||
|
||||
const String ksSuggestion =
|
||||
"15 minutes a day can make you 3x more fluent in 3 month";
|
||||
const String ksHomeBottomSheetTitle = 'Build Great Apps!';
|
||||
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 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 ksTerms = """
|
||||
<p style="color:#9C2C91;font-size:13px;">
|
||||
Last updated: October 26, 2025
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
// Registration type
|
||||
enum RegistrationType { phone, email }
|
||||
|
||||
// Report status
|
||||
// Response status
|
||||
enum ResponseStatus { success, failure }
|
||||
|
||||
enum ProgressStatuses { pending, started, completed }
|
||||
|
||||
// Levels
|
||||
enum ProficiencyLevels { a1, a2, b1, b2, none }
|
||||
|
||||
// Progress status
|
||||
enum ProgressStatuses { pending, started, completed }
|
||||
|
||||
// State object
|
||||
enum StateObjects {
|
||||
homeView,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
|
||||
// Split full name
|
||||
Map<String, String> splitFullName(String fullName) {
|
||||
final parts = fullName.trim().split(RegExp(r'\s+'));
|
||||
|
||||
|
|
|
|||
|
|
@ -202,7 +202,6 @@ TextStyle style12RP600 = const TextStyle(
|
|||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
|
||||
|
||||
TextStyle style12R700 = const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.red,
|
||||
|
|
@ -239,6 +238,17 @@ TextStyle style25DG600 = const TextStyle(
|
|||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
|
||||
TextStyle style16P600 = const TextStyle(
|
||||
fontSize: 16,
|
||||
color: kcPrimaryColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
|
||||
TextStyle style16DG500 = const TextStyle(
|
||||
fontSize: 16,
|
||||
color: kcDarkGrey,
|
||||
);
|
||||
|
||||
TextStyle style16DG600 = const TextStyle(
|
||||
fontSize: 16,
|
||||
color: kcDarkGrey,
|
||||
|
|
@ -257,10 +267,16 @@ TextStyle style18DG500 = const TextStyle(
|
|||
fontWeight: FontWeight.w500,
|
||||
);
|
||||
|
||||
TextStyle style18DG600 = const TextStyle(
|
||||
TextStyle style18DG700 = const TextStyle(
|
||||
fontSize: 18,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w700,
|
||||
);
|
||||
|
||||
TextStyle style20DG700 = const TextStyle(
|
||||
fontSize: 20,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w700,
|
||||
);
|
||||
|
||||
TextStyle style16DG400 = const TextStyle(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:email_validator/email_validator.dart';
|
||||
|
||||
class FormValidator {
|
||||
// Form validator
|
||||
static String? validateForm(String? value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
|
|
@ -12,6 +13,37 @@ class FormValidator {
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Email validator
|
||||
static String? validateEmail(String? value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.isEmpty) {
|
||||
return 'The field is required';
|
||||
}
|
||||
|
||||
if (!EmailValidator.validate(value)) {
|
||||
return 'Invalid email format';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Password validator
|
||||
static String? validatePassword(String? value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.isEmpty) {
|
||||
return 'The field is required';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Phone number validator
|
||||
static String? validatePhoneNumber(String? value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
|
|
@ -35,30 +67,4 @@ class FormValidator {
|
|||
return null;
|
||||
}
|
||||
|
||||
static String? validateEmail(String? value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.isEmpty) {
|
||||
return 'The field is required';
|
||||
}
|
||||
|
||||
if (!EmailValidator.validate(value)) {
|
||||
return 'Invalid email format';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String? validatePassword(String? value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.isEmpty) {
|
||||
return 'The field is required';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
|
|||
const AccountPrivacyView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
AccountPrivacyViewModel viewModelBuilder(
|
||||
BuildContext context,
|
||||
) =>
|
||||
AccountPrivacyViewModel viewModelBuilder(BuildContext context) =>
|
||||
AccountPrivacyViewModel();
|
||||
|
||||
@override
|
||||
|
|
@ -108,7 +106,7 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
|
|||
|
||||
Widget _buildHeader(String title) => Text(
|
||||
title,
|
||||
style: style18DG600,
|
||||
style: style18DG700,
|
||||
);
|
||||
|
||||
Widget _buildLanguageMenu(AccountPrivacyViewModel viewModel) =>
|
||||
|
|
@ -147,8 +145,8 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
|
|||
);
|
||||
Widget _buildDeleteButton() => CustomElevatedButton(
|
||||
height: 55,
|
||||
text: 'Delete Account',
|
||||
borderRadius: 12,
|
||||
text: 'Delete Account',
|
||||
foregroundColor: kcRed,
|
||||
backgroundColor: kcRed.withOpacity(0.25),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:yimaru_app/app/app.router.dart';
|
|||
import '../../../app/app.locator.dart';
|
||||
|
||||
class AccountPrivacyViewModel extends BaseViewModel {
|
||||
// Dependency injection
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
// Navigation
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ class AssessmentView extends StackedView<AssessmentViewModel> {
|
|||
super.onViewModelReady(viewModel);
|
||||
}
|
||||
|
||||
@override
|
||||
AssessmentViewModel viewModelBuilder(BuildContext context) => AssessmentViewModel();
|
||||
|
||||
@override
|
||||
Widget builder(
|
||||
BuildContext context,
|
||||
|
|
@ -65,9 +68,5 @@ class AssessmentView extends StackedView<AssessmentViewModel> {
|
|||
|
||||
Widget _buildStartLesson() => const StartLessonScreen();
|
||||
|
||||
@override
|
||||
AssessmentViewModel viewModelBuilder(
|
||||
BuildContext context,
|
||||
) =>
|
||||
AssessmentViewModel();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import '../../common/ui_helpers.dart';
|
|||
import '../home/home_view.dart';
|
||||
|
||||
class AssessmentViewModel extends BaseViewModel {
|
||||
// Dependency injection
|
||||
final _apiService = locator<ApiService>();
|
||||
final _dialogService = locator<DialogService>();
|
||||
final _statusChecker = locator<StatusCheckerService>();
|
||||
|
|
@ -207,6 +208,7 @@ class AssessmentViewModel extends BaseViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
// In-app navigation
|
||||
void next({int? page}) async {
|
||||
if (page == null) {
|
||||
if (_previousPage != 0) {
|
||||
|
|
|
|||
|
|
@ -76,8 +76,8 @@ class AssessmentCompletionScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
|
||||
Widget _buildSubtitle() => Text(
|
||||
'We’re now analyzing your speaking skills',
|
||||
textAlign: TextAlign.center,
|
||||
style: style14MG400,
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
|
||||
Widget _buildContinueButtonWrapper(AssessmentViewModel viewModel) => Padding(
|
||||
|
|
|
|||
|
|
@ -77,9 +77,10 @@ class AssessmentFailureScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Your assessment wasn’t long enough for us to analyze your speaking level. You can retake the call to get accurate results ',
|
||||
textAlign: TextAlign.center,
|
||||
style: style14MG400,
|
||||
);
|
||||
textAlign: TextAlign.center,
|
||||
|
||||
);
|
||||
|
||||
Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -97,9 +98,9 @@ class AssessmentFailureScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
height: 55,
|
||||
safe: false,
|
||||
borderRadius: 12,
|
||||
foregroundColor: kcWhite,
|
||||
text: 'Continue Assessment',
|
||||
onTap: () => viewModel.next(),
|
||||
foregroundColor: kcWhite,
|
||||
backgroundColor: kcPrimaryColor,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -155,9 +155,6 @@ class AssessmentFormScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
height: 55,
|
||||
borderRadius: 12,
|
||||
foregroundColor: kcWhite,
|
||||
text: viewModel.currentQuestion == viewModel.assessments.length - 1
|
||||
? 'Finish'
|
||||
: 'Continue',
|
||||
backgroundColor:
|
||||
viewModel.selectedAnswers.containsKey(question.toString())
|
||||
? kcPrimaryColor
|
||||
|
|
@ -165,5 +162,8 @@ class AssessmentFormScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
onTap: viewModel.selectedAnswers.containsKey(question.toString())
|
||||
? () => viewModel.nextQuestion()
|
||||
: null,
|
||||
text: viewModel.currentQuestion == viewModel.assessments.length - 1
|
||||
? 'Finish'
|
||||
: 'Continue',
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
|
||||
|
||||
import '../../../common/app_colors.dart';
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
|
||||
Widget _buildPrimarySubtitle() => Text(
|
||||
'Great Job! Here’s your next step to keep improving.',
|
||||
textAlign: TextAlign.center,
|
||||
style: style14MG400,
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
|
||||
Widget _buildIconWrapper(AssessmentViewModel viewModel) =>
|
||||
|
|
|
|||
|
|
@ -100,9 +100,9 @@ class RetakeAssessmentScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
height: 55,
|
||||
safe: false,
|
||||
borderRadius: 12,
|
||||
foregroundColor: kcWhite,
|
||||
text: 'Retake Assessment',
|
||||
onTap: () => viewModel.next(),
|
||||
foregroundColor: kcWhite,
|
||||
backgroundColor: kcPrimaryColor,
|
||||
);
|
||||
|
||||
|
|
@ -116,9 +116,9 @@ class RetakeAssessmentScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
height: 55,
|
||||
text: 'Skip',
|
||||
borderRadius: 12,
|
||||
backgroundColor: kcWhite,
|
||||
borderColor: kcPrimaryColor,
|
||||
onTap: () => viewModel.next(),
|
||||
backgroundColor: kcWhite,
|
||||
foregroundColor: kcPrimaryColor,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
|
|||
const CallSupportView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
CallSupportViewModel viewModelBuilder(
|
||||
BuildContext context,
|
||||
) =>
|
||||
CallSupportViewModel viewModelBuilder(BuildContext context) =>
|
||||
CallSupportViewModel();
|
||||
|
||||
@override
|
||||
|
|
@ -85,9 +83,9 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildSubTitle('+2519012345678'),
|
||||
_buildSubtitle('+2519012345678'),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle('+2519012345678'),
|
||||
_buildSubtitle('+2519012345678'),
|
||||
];
|
||||
|
||||
Widget _buildIcon() =>
|
||||
|
|
@ -99,10 +97,10 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
|
|||
textAlign: TextAlign.center,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle(String title) => Text(
|
||||
Widget _buildSubtitle(String title) => Text(
|
||||
title,
|
||||
style: style14P400,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: kcPrimaryColor),
|
||||
);
|
||||
|
||||
Widget _buildContinueButtonWrapper(CallSupportViewModel viewModel) => Padding(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import 'package:stacked_services/stacked_services.dart';
|
|||
import '../../../app/app.locator.dart';
|
||||
|
||||
class CallSupportViewModel extends BaseViewModel {
|
||||
// Dependency injection
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
// Navigation
|
||||
void pop() => _navigationService.back();
|
||||
}
|
||||
|
|
|
|||
105
lib/ui/views/course/course_view.dart
Normal file
105
lib/ui/views/course/course_view.dart
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/ui/widgets/course_card.dart';
|
||||
|
||||
import '../../common/app_colors.dart';
|
||||
import '../../common/ui_helpers.dart';
|
||||
import '../../widgets/learn_app_bar.dart';
|
||||
import 'course_viewmodel.dart';
|
||||
|
||||
class CourseView extends StackedView<CourseViewModel> {
|
||||
const CourseView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
CourseViewModel viewModelBuilder(BuildContext context) => CourseViewModel();
|
||||
|
||||
@override
|
||||
Widget builder(
|
||||
BuildContext context,
|
||||
CourseViewModel viewModel,
|
||||
Widget? child,
|
||||
) =>
|
||||
_buildScaffoldWrapper(viewModel);
|
||||
|
||||
Widget _buildScaffoldWrapper(CourseViewModel viewModel) => Scaffold(
|
||||
backgroundColor: kcBackgroundColor,
|
||||
body: _buildScaffold(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildScaffold(CourseViewModel viewModel) =>
|
||||
SafeArea(child: _buildBody(viewModel));
|
||||
|
||||
Widget _buildBody(CourseViewModel viewModel) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: _buildColumn(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildColumn(CourseViewModel viewModel) => Column(
|
||||
children: [
|
||||
verticalSpaceMedium,
|
||||
_buildAppBar(viewModel),
|
||||
_buildCourseColumnWrapper(viewModel)
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildAppBar(CourseViewModel viewModel) => LearnAppBar(
|
||||
name: viewModel.user?.firstName,
|
||||
profileImage: viewModel.user?.profilePicture,
|
||||
);
|
||||
|
||||
Widget _buildCourseColumnWrapper(CourseViewModel viewModel) =>
|
||||
Expanded(child: _buildCourseColumnScrollView(viewModel));
|
||||
|
||||
Widget _buildCourseColumnScrollView(CourseViewModel viewModel) =>
|
||||
SingleChildScrollView(
|
||||
child: _buildCourseColumn(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildCourseColumn(CourseViewModel viewModel) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildLevelsColumnChildren(viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildLevelsColumnChildren(CourseViewModel viewModel) => [
|
||||
verticalSpaceLarge,
|
||||
_buildTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildListView(viewModel)
|
||||
];
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
'Courses',
|
||||
style: style20DG700,
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Choose a course to improve your professional or exam skills.',
|
||||
style: style14DG400,
|
||||
);
|
||||
|
||||
Widget _buildListView(CourseViewModel viewModel) => ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: viewModel.courses.length,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => _buildTile(
|
||||
title: viewModel.courses[index]['title'],
|
||||
subtitle: viewModel.courses[index]['subtitle'],
|
||||
onTap: () async => await viewModel.navigateToCourseModule(),
|
||||
),
|
||||
separatorBuilder: (context, index) => verticalSpaceSmall,
|
||||
);
|
||||
|
||||
//
|
||||
Widget _buildTile({
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required GestureTapCallback onTap,
|
||||
}) =>
|
||||
CourseCard(
|
||||
title: title,
|
||||
onTap: onTap,
|
||||
subtitle: subtitle,
|
||||
);
|
||||
}
|
||||
42
lib/ui/views/course/course_viewmodel.dart
Normal file
42
lib/ui/views/course/course_viewmodel.dart
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
import 'package:yimaru_app/app/app.router.dart';
|
||||
|
||||
import '../../../app/app.locator.dart';
|
||||
import '../../../models/user_model.dart';
|
||||
import '../../../services/authentication_service.dart';
|
||||
|
||||
class CourseViewModel extends ReactiveViewModel {
|
||||
// Dependency injection
|
||||
final _navigationService = locator<NavigationService>();
|
||||
final _authenticationService = locator<AuthenticationService>();
|
||||
|
||||
@override
|
||||
List<ListenableServiceMixin> get listenableServices =>
|
||||
[_authenticationService];
|
||||
|
||||
// Current user
|
||||
UserModel? get _user => _authenticationService.user;
|
||||
|
||||
UserModel? get user => _user;
|
||||
|
||||
// Courses
|
||||
final List<Map<String, dynamic>> _courses = [
|
||||
{
|
||||
'title': 'English Proficiency Exams',
|
||||
'subtitle':
|
||||
'Prepare for IELTS, TOEFL, or Duolingo with structured practice.',
|
||||
},
|
||||
{
|
||||
'title': 'Skill-Based Courses',
|
||||
'subtitle':
|
||||
'Learn English for the workplace, travel, and real-life communication.',
|
||||
},
|
||||
];
|
||||
|
||||
List<Map<String, dynamic>> get courses => _courses;
|
||||
|
||||
// Navigation
|
||||
Future<void> navigateToCourseModule() async =>
|
||||
_navigationService.navigateToCourseModuleView();
|
||||
}
|
||||
105
lib/ui/views/course_module/course_module_view.dart
Normal file
105
lib/ui/views/course_module/course_module_view.dart
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/ui/widgets/course_module_tile.dart';
|
||||
|
||||
import '../../common/app_colors.dart';
|
||||
import '../../common/ui_helpers.dart';
|
||||
import '../../widgets/small_app_bar.dart';
|
||||
import 'course_module_viewmodel.dart';
|
||||
|
||||
class CourseModuleView extends StackedView<CourseModuleViewModel> {
|
||||
const CourseModuleView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
CourseModuleViewModel viewModelBuilder(BuildContext context) =>
|
||||
CourseModuleViewModel();
|
||||
|
||||
@override
|
||||
Widget builder(
|
||||
BuildContext context,
|
||||
CourseModuleViewModel viewModel,
|
||||
Widget? child,
|
||||
) =>
|
||||
_buildScaffoldWrapper(viewModel);
|
||||
|
||||
Widget _buildScaffoldWrapper(CourseModuleViewModel viewModel) => Scaffold(
|
||||
backgroundColor: kcBackgroundColor,
|
||||
body: _buildScaffold(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildScaffold(CourseModuleViewModel viewModel) =>
|
||||
SafeArea(child: _buildBody(viewModel));
|
||||
|
||||
Widget _buildBody(CourseModuleViewModel viewModel) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: _buildColumn(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildColumn(CourseModuleViewModel viewModel) => Column(
|
||||
children: [
|
||||
verticalSpaceMedium,
|
||||
_buildAppBar(viewModel),
|
||||
_buildLevelsColumnWrapper(viewModel),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildAppBar(CourseModuleViewModel viewModel) => SmallAppBar(
|
||||
onTap: viewModel.pop,
|
||||
showBackButton: true,
|
||||
);
|
||||
|
||||
Widget _buildLevelsColumnWrapper(CourseModuleViewModel viewModel) =>
|
||||
Expanded(child: _buildLevelsColumnScrollView(viewModel));
|
||||
|
||||
Widget _buildLevelsColumnScrollView(CourseModuleViewModel viewModel) =>
|
||||
SingleChildScrollView(
|
||||
child: _buildLevelsColumn(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildLevelsColumn(CourseModuleViewModel viewModel) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildLevelsColumnChildren(viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildLevelsColumnChildren(CourseModuleViewModel viewModel) => [
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildListView(viewModel)
|
||||
];
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
'English Proficiency Exams',
|
||||
style: style20DG700,
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Select your target exam and start preparing',
|
||||
style: style14DG400,
|
||||
);
|
||||
|
||||
Widget _buildListView(CourseModuleViewModel viewModel) => ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: viewModel.modules.length,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => _buildTile(
|
||||
title: viewModel.modules[index]['title'],
|
||||
onPracticeTap: () async =>
|
||||
await viewModel.navigateToCoursePractice(),
|
||||
onCourseTap: () async => await viewModel.navigateToCoursePayment()),
|
||||
separatorBuilder: (context, index) => verticalSpaceMedium,
|
||||
);
|
||||
|
||||
Widget _buildTile({
|
||||
required String title,
|
||||
GestureTapCallback? onCourseTap,
|
||||
GestureTapCallback? onPracticeTap,
|
||||
}) =>
|
||||
CourseModuleTile(
|
||||
title: title,
|
||||
onCourseTap: onCourseTap,
|
||||
onPracticeTap: onPracticeTap,
|
||||
);
|
||||
}
|
||||
31
lib/ui/views/course_module/course_module_viewmodel.dart
Normal file
31
lib/ui/views/course_module/course_module_viewmodel.dart
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
import 'package:yimaru_app/app/app.router.dart';
|
||||
|
||||
import '../../../app/app.locator.dart';
|
||||
|
||||
class CourseModuleViewModel extends BaseViewModel {
|
||||
// Dependency injection
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
// Course modules
|
||||
final List<Map<String, dynamic>> _modules = [
|
||||
{
|
||||
'title': 'Duolingo English Test',
|
||||
},
|
||||
{
|
||||
'title': 'IELTS',
|
||||
},
|
||||
];
|
||||
|
||||
List<Map<String, dynamic>> get modules => _modules;
|
||||
|
||||
// Navigation
|
||||
void pop() => _navigationService.back();
|
||||
|
||||
Future<void> navigateToCoursePractice() async =>
|
||||
_navigationService.navigateToCoursePracticeView();
|
||||
|
||||
Future<void> navigateToCoursePayment() async =>
|
||||
_navigationService.navigateToCoursePaymentView();
|
||||
}
|
||||
91
lib/ui/views/course_payment/course_payment_view.dart
Normal file
91
lib/ui/views/course_payment/course_payment_view.dart
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/ui/widgets/course_payment_card.dart';
|
||||
import 'package:yimaru_app/ui/widgets/custom_list_tile.dart';
|
||||
|
||||
import '../../common/app_colors.dart';
|
||||
import '../../common/ui_helpers.dart';
|
||||
import '../../widgets/small_app_bar.dart';
|
||||
import 'course_payment_viewmodel.dart';
|
||||
|
||||
class CoursePaymentView extends StackedView<CoursePaymentViewModel> {
|
||||
const CoursePaymentView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
CoursePaymentViewModel viewModelBuilder(BuildContext context) =>
|
||||
CoursePaymentViewModel();
|
||||
|
||||
@override
|
||||
Widget builder(
|
||||
BuildContext context,
|
||||
CoursePaymentViewModel viewModel,
|
||||
Widget? child,
|
||||
) =>
|
||||
_buildScaffoldWrapper(viewModel);
|
||||
|
||||
Widget _buildScaffoldWrapper(CoursePaymentViewModel viewModel) => Scaffold(
|
||||
backgroundColor: kcBackgroundColor,
|
||||
body: _buildScaffold(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildScaffold(CoursePaymentViewModel viewModel) =>
|
||||
SafeArea(child: _buildBody(viewModel));
|
||||
|
||||
Widget _buildBody(CoursePaymentViewModel viewModel) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: _buildColumn(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildColumn(CoursePaymentViewModel viewModel) => Column(
|
||||
children: [
|
||||
verticalSpaceMedium,
|
||||
_buildAppBar(viewModel),
|
||||
_buildPracticeColumnWrapper(viewModel),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildAppBar(CoursePaymentViewModel viewModel) => SmallAppBar(
|
||||
onTap: viewModel.pop,
|
||||
showBackButton: true,
|
||||
);
|
||||
|
||||
Widget _buildPracticeColumnWrapper(CoursePaymentViewModel viewModel) =>
|
||||
Expanded(child: _buildPracticeColumnScrollView(viewModel));
|
||||
|
||||
Widget _buildPracticeColumnScrollView(CoursePaymentViewModel viewModel) =>
|
||||
SingleChildScrollView(
|
||||
child: _buildPracticeColumn(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildPracticeColumn(CoursePaymentViewModel viewModel) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: _buildPracticeColumnChildren(viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildPracticeColumnChildren(CoursePaymentViewModel viewModel) =>
|
||||
[
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildFirstCard(),
|
||||
verticalSpaceMedium,
|
||||
_buildSecondCard()
|
||||
];
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
'Unlock All Lessons & Practices',
|
||||
style: style20DG700,
|
||||
);
|
||||
|
||||
Widget _buildFirstCard() => const CoursePaymentCard(
|
||||
icon: Icons.school,
|
||||
title: '50+ New Lessons',
|
||||
subtitle: 'Access fresh, advanced content',
|
||||
);
|
||||
|
||||
Widget _buildSecondCard() => const CoursePaymentCard(
|
||||
icon: Icons.developer_board,
|
||||
title: 'Mastery Through Practice',
|
||||
subtitle: 'Practice All Question Types & Take Mock Exams',
|
||||
);
|
||||
}
|
||||
12
lib/ui/views/course_payment/course_payment_viewmodel.dart
Normal file
12
lib/ui/views/course_payment/course_payment_viewmodel.dart
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
|
||||
import '../../../app/app.locator.dart';
|
||||
|
||||
class CoursePaymentViewModel extends BaseViewModel {
|
||||
// Dependency injection
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
// Navigation
|
||||
void pop() => _navigationService.back();
|
||||
}
|
||||
103
lib/ui/views/course_practice/course_practice_view.dart
Normal file
103
lib/ui/views/course_practice/course_practice_view.dart
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/ui/widgets/course_practice_card.dart';
|
||||
|
||||
import '../../common/app_colors.dart';
|
||||
import '../../common/ui_helpers.dart';
|
||||
import '../../widgets/small_app_bar.dart';
|
||||
import 'course_practice_viewmodel.dart';
|
||||
|
||||
class CoursePracticeView extends StackedView<CoursePracticeViewModel> {
|
||||
const CoursePracticeView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
CoursePracticeViewModel viewModelBuilder(BuildContext context) =>
|
||||
CoursePracticeViewModel();
|
||||
|
||||
@override
|
||||
Widget builder(
|
||||
BuildContext context,
|
||||
CoursePracticeViewModel viewModel,
|
||||
Widget? child,
|
||||
) =>
|
||||
_buildScaffoldWrapper(viewModel);
|
||||
|
||||
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
|
||||
backgroundColor: kcBackgroundColor,
|
||||
body: _buildScaffold(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
|
||||
SafeArea(child: _buildBody(viewModel));
|
||||
|
||||
Widget _buildBody(CoursePracticeViewModel viewModel) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: _buildColumn(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildColumn(CoursePracticeViewModel viewModel) => Column(
|
||||
children: [
|
||||
verticalSpaceMedium,
|
||||
_buildAppBar(viewModel),
|
||||
_buildPracticeColumnWrapper(viewModel),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildAppBar(CoursePracticeViewModel viewModel) => SmallAppBar(
|
||||
onTap: viewModel.pop,
|
||||
showBackButton: true,
|
||||
);
|
||||
|
||||
Widget _buildPracticeColumnWrapper(CoursePracticeViewModel viewModel) =>
|
||||
Expanded(child: _buildPracticeColumnScrollView(viewModel));
|
||||
|
||||
Widget _buildPracticeColumnScrollView(CoursePracticeViewModel viewModel) =>
|
||||
SingleChildScrollView(
|
||||
child: _buildPracticeColumn(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildPracticeColumn(CoursePracticeViewModel viewModel) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildPracticeColumnChildren(viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildPracticeColumnChildren(
|
||||
CoursePracticeViewModel viewModel) =>
|
||||
[
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildListView(viewModel)
|
||||
];
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
'Duolingo Mock Tests',
|
||||
style: style20DG700,
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Select your target exam and start preparing',
|
||||
style: style14DG400,
|
||||
);
|
||||
|
||||
Widget _buildListView(CoursePracticeViewModel viewModel) => GridView.builder(
|
||||
itemCount: 6,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) =>
|
||||
_buildCard(title: viewModel.practices[index]['title']),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 15,
|
||||
crossAxisSpacing: 15,
|
||||
childAspectRatio: 1.45,
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildCard({
|
||||
required String title,
|
||||
}) =>
|
||||
CoursePracticeCard(title: title);
|
||||
}
|
||||
36
lib/ui/views/course_practice/course_practice_viewmodel.dart
Normal file
36
lib/ui/views/course_practice/course_practice_viewmodel.dart
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
|
||||
import '../../../app/app.locator.dart';
|
||||
|
||||
class CoursePracticeViewModel extends BaseViewModel {
|
||||
// Dependency injection
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
// Practices
|
||||
final List<Map<String, dynamic>> _practices = [
|
||||
{
|
||||
'title': 'Test 01',
|
||||
},
|
||||
{
|
||||
'title': 'Test 02',
|
||||
},
|
||||
{
|
||||
'title': 'Test 03',
|
||||
},
|
||||
{
|
||||
'title': 'Test 04',
|
||||
},
|
||||
{
|
||||
'title': 'Test 05',
|
||||
},
|
||||
{
|
||||
'title': 'Test 06',
|
||||
},
|
||||
];
|
||||
|
||||
List<Map<String, dynamic>> get practices => _practices;
|
||||
|
||||
// Navigation
|
||||
void pop() => _navigationService.back();
|
||||
}
|
||||
|
|
@ -13,9 +13,7 @@ class DownloadsView extends StackedView<DownloadsViewModel> {
|
|||
const DownloadsView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
DownloadsViewModel viewModelBuilder(
|
||||
BuildContext context,
|
||||
) =>
|
||||
DownloadsViewModel viewModelBuilder(BuildContext context) =>
|
||||
DownloadsViewModel();
|
||||
|
||||
@override
|
||||
|
|
@ -95,33 +93,21 @@ class DownloadsView extends StackedView<DownloadsViewModel> {
|
|||
List<Widget> _buildStorageInfoChildren(DownloadsViewModel viewModel) =>
|
||||
[_buildStorageInfo(), _buildManageButton(viewModel)];
|
||||
|
||||
Widget _buildStorageInfo() => const Text.rich(
|
||||
TextSpan(
|
||||
text: '1.2GB',
|
||||
style: TextStyle(
|
||||
color: kcPrimaryColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: ' used of 2GB',
|
||||
style: TextStyle(
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
)
|
||||
]),
|
||||
Widget _buildStorageInfo() => Text.rich(
|
||||
TextSpan(text: '1.2GB', style: style14P600, children: [
|
||||
TextSpan(
|
||||
text: ' used of 2GB',
|
||||
style: style14DG600,
|
||||
)
|
||||
]),
|
||||
);
|
||||
|
||||
Widget _buildManageButton(DownloadsViewModel viewModel) => TextButton(
|
||||
onPressed: viewModel.setShowDownload, child: _buildManageText());
|
||||
|
||||
Widget _buildManageText() => const Text(
|
||||
Widget _buildManageText() => Text(
|
||||
'Manage Storage',
|
||||
style: TextStyle(
|
||||
color: kcPrimaryColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style14P600,
|
||||
);
|
||||
|
||||
Widget _buildStorageIndicator() => const CustomLinearProgressIndicator(
|
||||
|
|
@ -188,20 +174,16 @@ class DownloadsView extends StackedView<DownloadsViewModel> {
|
|||
color: kcPrimaryColor,
|
||||
);
|
||||
|
||||
Widget _buildEmptyTitle() => const Text(
|
||||
Widget _buildEmptyTitle() => Text(
|
||||
'Looking for something to download?',
|
||||
style: style25DG600,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildEmptySubtitle() => const Text(
|
||||
Widget _buildEmptySubtitle() => Text(
|
||||
'Start by exploring your learning materials and save them for offline access.',
|
||||
style: style14MG400,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
);
|
||||
|
||||
Widget _buildGoButtonWrapper(DownloadsViewModel viewModel) => Padding(
|
||||
|
|
@ -211,9 +193,9 @@ class DownloadsView extends StackedView<DownloadsViewModel> {
|
|||
Widget _buildGoButton(DownloadsViewModel viewModel) => CustomElevatedButton(
|
||||
height: 55,
|
||||
borderRadius: 12,
|
||||
text: 'Go to Learn Section',
|
||||
onTap: viewModel.setShowDownload,
|
||||
foregroundColor: kcWhite,
|
||||
text: 'Go to Learn Section',
|
||||
backgroundColor: kcPrimaryColor,
|
||||
onTap: viewModel.setShowDownload,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,14 @@ import 'package:stacked_services/stacked_services.dart';
|
|||
import '../../../app/app.locator.dart';
|
||||
|
||||
class DownloadsViewModel extends BaseViewModel {
|
||||
// Dependency injection
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
// Show download
|
||||
bool _showDownload = false;
|
||||
|
||||
bool get showDownload => _showDownload;
|
||||
|
||||
// Downloads
|
||||
final List<Map<String, dynamic>> _downloads = [
|
||||
{
|
||||
|
|
@ -33,10 +36,12 @@ class DownloadsViewModel extends BaseViewModel {
|
|||
|
||||
List<Map<String, dynamic>> get downloads => _downloads;
|
||||
|
||||
// Show download
|
||||
void setShowDownload() {
|
||||
_showDownload = !_showDownload;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Navigation
|
||||
void pop() => _navigationService.back();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/scheduler.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/course_view.dart';
|
||||
import 'package:yimaru_app/ui/views/learn/learn_view.dart';
|
||||
import 'package:yimaru_app/ui/views/profile/profile_view.dart';
|
||||
import 'package:yimaru_app/ui/views/startup/startup_view.dart';
|
||||
|
|
@ -76,7 +77,7 @@ Widget getViewForIndex(int index) {
|
|||
case 0:
|
||||
return const LearnView();
|
||||
case 1:
|
||||
return const ComingSoon();
|
||||
return const CourseView();
|
||||
|
||||
default:
|
||||
return const ProfileView();
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ class LearnViewModel extends ReactiveViewModel {
|
|||
[_authenticationService];
|
||||
|
||||
// Current user
|
||||
UserModel? get user => _authenticationService.user;
|
||||
UserModel? get _user => _authenticationService.user;
|
||||
|
||||
UserModel? get user => _user;
|
||||
|
||||
final List<Map<String, dynamic>> _learnLevels = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
|
|||
|
||||
Widget _buildHeader() => Text(
|
||||
'Module 1: Greetings & Introductions',
|
||||
style: style18DG600,
|
||||
style: style18DG700,
|
||||
);
|
||||
|
||||
Widget _buildListView(LearnLessonViewModel viewModel) => ListView.builder(
|
||||
|
|
@ -124,7 +124,6 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
|
|||
onLessonTap: () async =>
|
||||
await viewModel.navigateToLearnLessonDetail(),
|
||||
onPracticeTap: () async => await viewModel.navigateToLearnPractice(),
|
||||
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,6 @@ class LearnLessonViewModel extends BaseViewModel {
|
|||
buttonLabel: 'Start Practice',
|
||||
title: 'Let \'s practice what you just learnt!',
|
||||
subtitle:
|
||||
'I’ll ask you a few questions, and you can respond naturally.',
|
||||
'I’ll ask you a few questions, and you can respond naturally.',
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ class LearnLevelViewModel extends BaseViewModel {
|
|||
await _navigationService.navigateToLearnPracticeView(
|
||||
title: 'Let’s Practice Level 1',
|
||||
buttonLabel: 'Begin Level Practice',
|
||||
subtitle:
|
||||
'Let’s quickly review what you’ve learned in this level! ',
|
||||
subtitle: 'Let’s quickly review what you’ve learned in this level! ',
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
|||
List<Widget> _buildLevelsColumnChildren(LearnModuleViewModel viewModel) => [
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildOverallProgress(),
|
||||
verticalSpaceMedium,
|
||||
|
|
@ -79,7 +79,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
|||
style: style18P600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Your Current Level',
|
||||
style: style14DG400,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import '../../common/enmus.dart';
|
|||
class LearnModuleViewModel extends BaseViewModel {
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
// Modules
|
||||
final List<Map<String, dynamic>> _modules = [
|
||||
{
|
||||
'status': ProgressStatuses.completed,
|
||||
|
|
@ -35,6 +36,7 @@ class LearnModuleViewModel extends BaseViewModel {
|
|||
|
||||
List<Map<String, dynamic>> get modules => _modules;
|
||||
|
||||
// Navigation
|
||||
void pop() => _navigationService.back();
|
||||
|
||||
Future<void> navigateToLearnLesson() async =>
|
||||
|
|
@ -44,7 +46,6 @@ class LearnModuleViewModel extends BaseViewModel {
|
|||
await _navigationService.navigateToLearnPracticeView(
|
||||
title: 'Let’s Practice Module 1',
|
||||
buttonLabel: 'Begin Module Practice',
|
||||
subtitle:
|
||||
'Let’s quickly review what you’ve learned in this module! ',
|
||||
subtitle: 'Let’s quickly review what you’ve learned in this module! ',
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
|
|||
_buildSpeakToLearnPracticeListenerScreen(),
|
||||
_buildFinishLearnPracticeScreen(),
|
||||
_buildLearnPracticeResultScreen(),
|
||||
_buildLearnPracticeCompletionScreen()
|
||||
_buildLearnPracticeCompletionScreen()
|
||||
];
|
||||
|
||||
Widget _buildLearnPracticeIntroScreen() => LearnPracticeIntroScreen(
|
||||
|
|
@ -93,6 +93,6 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
|
|||
|
||||
Widget _buildLearnPracticeResultScreen() => const LearnPracticeResultScreen();
|
||||
|
||||
Widget _buildLearnPracticeCompletionScreen() => const LearnPracticeCompletionScreen();
|
||||
|
||||
Widget _buildLearnPracticeCompletionScreen() =>
|
||||
const LearnPracticeCompletionScreen();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import '../../../common/app_colors.dart';
|
|||
import '../../../common/ui_helpers.dart';
|
||||
import '../../../widgets/custom_elevated_button.dart';
|
||||
|
||||
class LearnPracticeCompletionScreen extends ViewModelWidget<LearnPracticeViewModel> {
|
||||
class LearnPracticeCompletionScreen
|
||||
extends ViewModelWidget<LearnPracticeViewModel> {
|
||||
const LearnPracticeCompletionScreen({super.key});
|
||||
|
||||
@override
|
||||
|
|
@ -16,61 +17,60 @@ class LearnPracticeCompletionScreen extends ViewModelWidget<LearnPracticeViewMod
|
|||
_buildScaffoldWrapper(viewModel);
|
||||
|
||||
Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold(
|
||||
backgroundColor: kcBackgroundColor,
|
||||
body: _buildBodyWrapper(viewModel),
|
||||
);
|
||||
|
||||
|
||||
backgroundColor: kcBackgroundColor,
|
||||
body: _buildBodyWrapper(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildBodyWrapper(LearnPracticeViewModel viewModel) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: _buildBody(viewModel),
|
||||
);
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: _buildBody(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildBody(LearnPracticeViewModel viewModel) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: _buildBodyChildren(viewModel),
|
||||
);
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: _buildBodyChildren(viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildBodyChildren(LearnPracticeViewModel viewModel) =>
|
||||
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
|
||||
|
||||
Widget _buildUpperColumn(LearnPracticeViewModel viewModel) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: _buildUpperColumnChildren(viewModel),
|
||||
);
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: _buildUpperColumnChildren(viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildUpperColumnChildren(LearnPracticeViewModel viewModel) => [
|
||||
verticalSpaceMassive,
|
||||
_buildIcon(),
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubtitle(),
|
||||
];
|
||||
verticalSpaceMassive,
|
||||
_buildIcon(),
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubtitle(),
|
||||
];
|
||||
|
||||
Widget _buildIcon() => SvgPicture.asset(
|
||||
'assets/icons/complete.svg',
|
||||
);
|
||||
'assets/icons/complete.svg',
|
||||
);
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
'Yay, you’ve completed A1 ',
|
||||
style: style25DG600,
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
'Yay, you’ve completed A1 ',
|
||||
style: style25DG600,
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => Text(
|
||||
'We’re now analyzing your speaking skills',
|
||||
textAlign: TextAlign.center,
|
||||
style: style14MG400,
|
||||
);
|
||||
'We’re now analyzing your speaking skills',
|
||||
textAlign: TextAlign.center,
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildContinueButtonWrapper(LearnPracticeViewModel viewModel) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 50),
|
||||
child: _buildContinueButton(viewModel),
|
||||
);
|
||||
Widget _buildContinueButtonWrapper(LearnPracticeViewModel viewModel) =>
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 50),
|
||||
child: _buildContinueButton(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildContinueButton(LearnPracticeViewModel viewModel) =>
|
||||
CustomElevatedButton(
|
||||
|
|
@ -81,4 +81,4 @@ class LearnPracticeCompletionScreen extends ViewModelWidget<LearnPracticeViewMod
|
|||
onTap: () => viewModel.pop(),
|
||||
backgroundColor: kcPrimaryColor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,13 +105,13 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
|
|||
);
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
title,
|
||||
title,
|
||||
style: style25DG600,
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => Text(
|
||||
subtitle,
|
||||
subtitle,
|
||||
style: style14DG400,
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -95,7 +95,8 @@ class LearnPracticeResultScreen
|
|||
verticalSpaceMedium,
|
||||
];
|
||||
|
||||
Widget _buildContinueButton(LearnPracticeViewModel viewModel) => CustomElevatedButton(
|
||||
Widget _buildContinueButton(LearnPracticeViewModel viewModel) =>
|
||||
CustomElevatedButton(
|
||||
height: 55,
|
||||
text: 'Continue',
|
||||
borderRadius: 12,
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildAgeGroups(viewModel)
|
||||
];
|
||||
|
|
@ -91,7 +91,7 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'We’ll personalize your learning experience based on your age.',
|
||||
style: style14DG400,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ class EducationalBackgroundFormScreen
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildEducationalLevels(viewModel)
|
||||
];
|
||||
|
|
@ -97,7 +97,7 @@ class EducationalBackgroundFormScreen
|
|||
),
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => const Text(
|
||||
'This helps us tailor your lessons to your experience.',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/ui/widgets/course_level_card.dart';
|
||||
import 'package:yimaru_app/ui/widgets/course_progress_card.dart';
|
||||
import 'package:yimaru_app/ui/widgets/skill_progress.dart';
|
||||
import 'package:yimaru_app/ui/widgets/suggestion_card.dart';
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ class ProgressView extends StackedView<ProgressViewModel> {
|
|||
required String subtitle,
|
||||
required bool isCompleted,
|
||||
required ProgressViewModel viewModel}) =>
|
||||
CourseLevelCard(
|
||||
CourseProgressCard(
|
||||
icon: icon,
|
||||
title: title,
|
||||
color: color,
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class TelegramSupportView extends StackedView<TelegramSupportViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
];
|
||||
|
||||
Widget _buildIcon() =>
|
||||
|
|
@ -101,7 +101,7 @@ class TelegramSupportView extends StackedView<TelegramSupportViewModel> {
|
|||
),
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => const Text(
|
||||
'Connect with our support team instantly on Telegram for quick assistance and community updates',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class BirthdaySelector extends StatelessWidget {
|
|||
context: context,
|
||||
is24HourMode: false,
|
||||
isShowSeconds: false,
|
||||
title: _buildTitle(),
|
||||
lastDate: DateTime.now(),
|
||||
firstDate: DateTime(1900),
|
||||
barrierDismissible: true,
|
||||
|
|
@ -34,7 +35,6 @@ class BirthdaySelector extends StatelessWidget {
|
|||
type: OmniDateTimePickerType.date,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||
insetPadding: const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
|
||||
title: const Text('Birthday', style: TextStyle(fontSize: 16)),
|
||||
theme: ThemeData(
|
||||
colorScheme:
|
||||
const ColorScheme.light().copyWith(primary: kcPrimaryColor),
|
||||
|
|
@ -72,6 +72,8 @@ class BirthdaySelector extends StatelessWidget {
|
|||
child: _buildContainer(),
|
||||
);
|
||||
|
||||
Widget _buildTitle() => Text('Birthday', style: style16DG600);
|
||||
|
||||
Widget _buildContainer() => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class CancelLearnPracticeSheet extends StatelessWidget {
|
|||
);
|
||||
|
||||
Widget _buildMessage() => Text.rich(
|
||||
TextSpan(text: 'You’re almost there,', style: style18DG600, children: [
|
||||
TextSpan(text: 'You’re almost there,', style: style18DG700, children: [
|
||||
TextSpan(
|
||||
text: ' Johnny!',
|
||||
style: style18P600,
|
||||
|
|
|
|||
75
lib/ui/widgets/course_card.dart
Normal file
75
lib/ui/widgets/course_card.dart
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../common/app_colors.dart';
|
||||
import '../common/ui_helpers.dart';
|
||||
import 'custom_elevated_button.dart';
|
||||
|
||||
class CourseCard extends StatelessWidget {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final GestureTapCallback? onTap;
|
||||
|
||||
const CourseCard(
|
||||
{super.key, this.onTap, required this.title, required this.subtitle});
|
||||
|
||||
Color _getColor() {
|
||||
if (title == 'English Proficiency Exams') {
|
||||
return kcRed.withValues(alpha: 0.2);
|
||||
} else {
|
||||
return kcAquamarine.withValues(alpha: 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _buildContainer();
|
||||
|
||||
Widget _buildContainer() => Container(
|
||||
height: 200,
|
||||
padding: const EdgeInsets.all(15),
|
||||
decoration: BoxDecoration(
|
||||
color: _getColor(),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: _buildColumn(),
|
||||
);
|
||||
|
||||
Widget _buildColumn() => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildColumnChildren(),
|
||||
);
|
||||
|
||||
List<Widget> _buildColumnChildren() => [
|
||||
_buildTitle(),
|
||||
verticalSpaceTiny,
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
__buildStartButtonWrapper(),
|
||||
];
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
title,
|
||||
style: style18DG700,
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => Text(
|
||||
subtitle,
|
||||
style: style16DG400,
|
||||
);
|
||||
|
||||
Widget __buildStartButtonWrapper() => SizedBox(
|
||||
height: 40,
|
||||
child: _buildStartButton(),
|
||||
);
|
||||
|
||||
Widget _buildStartButton() => CustomElevatedButton(
|
||||
height: 50,
|
||||
width: 200,
|
||||
onTap: onTap,
|
||||
borderRadius: 12,
|
||||
text: 'Start Course',
|
||||
foregroundColor: kcWhite,
|
||||
backgroundColor: kcPrimaryColor,
|
||||
);
|
||||
}
|
||||
132
lib/ui/widgets/course_module_tile.dart
Normal file
132
lib/ui/widgets/course_module_tile.dart
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:iconsax/iconsax.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/ui/views/course_module/course_module_viewmodel.dart';
|
||||
import 'package:yimaru_app/ui/views/learn_module/learn_module_viewmodel.dart';
|
||||
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
|
||||
import 'package:yimaru_app/ui/widgets/finish_practice_sheet.dart';
|
||||
import 'package:yimaru_app/ui/widgets/progress_status.dart';
|
||||
|
||||
import '../common/app_colors.dart';
|
||||
import '../common/enmus.dart';
|
||||
import '../common/ui_helpers.dart';
|
||||
import 'custom_elevated_button.dart';
|
||||
|
||||
class CourseModuleTile extends StatelessWidget {
|
||||
final String title;
|
||||
final GestureTapCallback? onCourseTap;
|
||||
final GestureTapCallback? onPracticeTap;
|
||||
|
||||
const CourseModuleTile({
|
||||
super.key,
|
||||
this.onCourseTap,
|
||||
this.onPracticeTap,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _buildExpansionTileCard();
|
||||
|
||||
Widget _buildExpansionTileCard() => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
border: Border.all(
|
||||
color: kcPrimaryColor.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
child: _buildExpansionTile(),
|
||||
);
|
||||
|
||||
Widget _buildExpansionTile() => ExpansionTile(
|
||||
enabled: true,
|
||||
title: _buildTitle(),
|
||||
textColor: kcDarkGrey,
|
||||
showTrailingIcon: false,
|
||||
initiallyExpanded: false,
|
||||
collapsedIconColor: kcDarkGrey,
|
||||
collapsedTextColor: kcDarkGrey,
|
||||
shape: Border.all(color: kcTransparent),
|
||||
expandedAlignment: Alignment.centerLeft,
|
||||
backgroundColor: kcPrimaryColor.withOpacity(0.1),
|
||||
controlAffinity: ListTileControlAffinity.trailing,
|
||||
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||
collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1),
|
||||
childrenPadding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
children: _buildExpansionTileChildren(),
|
||||
);
|
||||
|
||||
List<Widget> _buildExpansionTileChildren() => [
|
||||
_buildProgressRow(),
|
||||
verticalSpaceSmall,
|
||||
_buildActionButtonWrapper(),
|
||||
verticalSpaceSmall
|
||||
];
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
title,
|
||||
style: style16P600,
|
||||
);
|
||||
|
||||
Widget _buildProgressRow() => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: _buildProgressChildren(),
|
||||
);
|
||||
|
||||
List<Widget> _buildProgressChildren() =>
|
||||
[_buildProgressStatusWrapper(), horizontalSpaceSmall, _buildProgress()];
|
||||
|
||||
Widget _buildProgressStatusWrapper() => Expanded(
|
||||
child: _buildProgressStatus(),
|
||||
);
|
||||
|
||||
Widget _buildProgressStatus() => const CustomLinearProgressIndicator(
|
||||
progress: 0.75,
|
||||
activeColor: kcPrimaryColor,
|
||||
backgroundColor: kcVeryLightGrey);
|
||||
|
||||
Widget _buildProgress() => const Text(
|
||||
'75%',
|
||||
style: TextStyle(color: kcDarkGrey),
|
||||
);
|
||||
|
||||
Widget _buildActionButtonWrapper() => SizedBox(
|
||||
height: 40,
|
||||
width: 300,
|
||||
child: _buildActionButtons(),
|
||||
);
|
||||
|
||||
Widget _buildActionButtons() => Row(
|
||||
children: [
|
||||
_buildStartButtonWrapper(),
|
||||
horizontalSpaceSmall,
|
||||
_buildExamButtonWrapper()
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildStartButtonWrapper() => Expanded(
|
||||
child: _buildStartButton(),
|
||||
);
|
||||
|
||||
Widget _buildStartButton() => CustomElevatedButton(
|
||||
height: 15,
|
||||
borderRadius: 8,
|
||||
onTap: onCourseTap,
|
||||
text: 'Start Course',
|
||||
foregroundColor: kcWhite,
|
||||
backgroundColor: kcPrimaryColor,
|
||||
);
|
||||
|
||||
Widget _buildExamButtonWrapper() => Expanded(
|
||||
child: _buildExamButton(),
|
||||
);
|
||||
|
||||
Widget _buildExamButton() => CustomElevatedButton(
|
||||
height: 15,
|
||||
borderRadius: 8,
|
||||
onTap: onPracticeTap,
|
||||
text: 'Take Mock Exam',
|
||||
backgroundColor: kcWhite,
|
||||
borderColor: kcPrimaryColor,
|
||||
foregroundColor: kcPrimaryColor,
|
||||
);
|
||||
}
|
||||
52
lib/ui/widgets/course_payment_card.dart
Normal file
52
lib/ui/widgets/course_payment_card.dart
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:yimaru_app/ui/common/app_colors.dart';
|
||||
|
||||
import '../common/ui_helpers.dart';
|
||||
|
||||
class CoursePaymentCard extends StatelessWidget {
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final String subtitle;
|
||||
|
||||
const CoursePaymentCard(
|
||||
{super.key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.subtitle});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _buildListTile();
|
||||
|
||||
Widget _buildListTile() => ListTile(
|
||||
title: _buildTitle(),
|
||||
leading: _buildLeading(),
|
||||
subtitle: _buildSubtitle(),
|
||||
tileColor: kcPrimaryColor.withValues(alpha: 0.1),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(color: kcPrimaryColor.withValues(alpha: 0.25)),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
title,
|
||||
style: style16DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => Text(
|
||||
subtitle,
|
||||
style: style14DG400,
|
||||
);
|
||||
|
||||
Widget _buildLeading() => CircleAvatar(
|
||||
radius: 25,
|
||||
backgroundColor: kcPrimaryColor.withValues(alpha: 0.25),
|
||||
child: _buildIcon(),
|
||||
);
|
||||
|
||||
Widget _buildIcon() => Icon(
|
||||
icon,
|
||||
size: 25,
|
||||
color: kcPrimaryColor,
|
||||
);
|
||||
}
|
||||
53
lib/ui/widgets/course_practice_card.dart
Normal file
53
lib/ui/widgets/course_practice_card.dart
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:yimaru_app/ui/common/app_colors.dart';
|
||||
|
||||
import '../common/ui_helpers.dart';
|
||||
import 'custom_elevated_button.dart';
|
||||
|
||||
class CoursePracticeCard extends StatelessWidget {
|
||||
final String title;
|
||||
|
||||
const CoursePracticeCard({super.key, required this.title});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _buildContainer();
|
||||
|
||||
Widget _buildContainer() => Container(
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: kcPrimaryColor.withValues(alpha: 0.25),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 12),
|
||||
child: _buildColumn(),
|
||||
);
|
||||
|
||||
Widget _buildColumn() =>
|
||||
Column(mainAxisSize: MainAxisSize.min, children: _buildColumnChildren());
|
||||
|
||||
List<Widget> _buildColumnChildren() => [
|
||||
verticalSpaceTiny,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildStartButtonWrapper()
|
||||
];
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
title,
|
||||
style: style18DG700,
|
||||
);
|
||||
|
||||
Widget _buildStartButtonWrapper() => SizedBox(
|
||||
height: 40,
|
||||
child: _buildStartButton(),
|
||||
);
|
||||
|
||||
Widget _buildStartButton() => CustomElevatedButton(
|
||||
height: 50,
|
||||
width: 200,
|
||||
borderRadius: 8,
|
||||
text: 'Start Test',
|
||||
foregroundColor: kcWhite,
|
||||
backgroundColor: kcPrimaryColor,
|
||||
);
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import '../common/app_colors.dart';
|
|||
import '../common/ui_helpers.dart';
|
||||
import 'custom_elevated_button.dart';
|
||||
|
||||
class CourseLevelCard extends StatelessWidget {
|
||||
class CourseProgressCard extends StatelessWidget {
|
||||
final Color color;
|
||||
final String icon;
|
||||
final String title;
|
||||
|
|
@ -15,7 +15,7 @@ class CourseLevelCard extends StatelessWidget {
|
|||
final bool isCompleted;
|
||||
final GestureTapCallback? onTap;
|
||||
|
||||
const CourseLevelCard({
|
||||
const CourseProgressCard({
|
||||
super.key,
|
||||
this.onTap,
|
||||
required this.icon,
|
||||
|
|
@ -56,7 +56,7 @@ class CourseLevelCard extends StatelessWidget {
|
|||
verticalSpaceSmall,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildActionButton()
|
||||
];
|
||||
|
|
@ -89,7 +89,7 @@ class CourseLevelCard extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => Expanded(
|
||||
Widget _buildSubtitle() => Expanded(
|
||||
child: Text(
|
||||
subtitle,
|
||||
maxLines: 3,
|
||||
|
|
@ -68,7 +68,7 @@ class CustomLargeRadioButton extends StatelessWidget {
|
|||
|
||||
Widget _buildTitle() => Text(
|
||||
title,
|
||||
style: style18DG600,
|
||||
style: style18DG700,
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => Text(
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ class LearnLessonTile extends StatelessWidget {
|
|||
_buildLessonButton(),
|
||||
];
|
||||
|
||||
Widget _buildPracticeButton() => CustomElevatedButton(
|
||||
Widget _buildPracticeButton() => CustomElevatedButton(
|
||||
height: 15,
|
||||
width: 135,
|
||||
text: 'Practice',
|
||||
|
|
|
|||
|
|
@ -27,24 +27,19 @@ class LearnPracticeTipSection extends StatelessWidget {
|
|||
List<Widget> _buildColumnChildren() =>
|
||||
[_buildTitleWrapper(), verticalSpaceTiny, _buildContent()];
|
||||
|
||||
Widget _buildTitleWrapper() =>
|
||||
Row(
|
||||
children: [
|
||||
_buildLeading(),horizontalSpaceSmall,_buildTitle()
|
||||
],
|
||||
Widget _buildTitleWrapper() => Row(
|
||||
children: [_buildLeading(), horizontalSpaceSmall, _buildTitle()],
|
||||
);
|
||||
|
||||
|
||||
Widget _buildLeading() => const Icon(
|
||||
Icons.lightbulb_outline_rounded,
|
||||
color: kcBlue,
|
||||
);
|
||||
Icons.lightbulb_outline_rounded,
|
||||
color: kcBlue,
|
||||
);
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
'Quick Tip',
|
||||
style: style16B600,
|
||||
);
|
||||
|
||||
'Quick Tip',
|
||||
style: style16B600,
|
||||
);
|
||||
|
||||
Widget _buildContent() => Text(
|
||||
"You can always do better!\nSpeak in full sentences instead of short phrases.\nMaintain a steady pace, not too fast, not too slow.\nUse varied vocabulary to make your answers richer.\nPause naturally instead of using fillers like “um” or “uh”.",
|
||||
|
|
|
|||
|
|
@ -123,14 +123,14 @@ class LearnSubLevelTile extends ViewModelWidget<LearnLevelViewModel> {
|
|||
);
|
||||
|
||||
Widget _buildPracticeButton(LearnLevelViewModel viewModel) =>
|
||||
CustomElevatedButton(
|
||||
height: 15,
|
||||
text: 'Practice',
|
||||
borderRadius: 12,
|
||||
backgroundColor: kcWhite,
|
||||
borderColor: kcPrimaryColor,
|
||||
foregroundColor: kcPrimaryColor,
|
||||
onTap: () async => await viewModel.navigateToLearnPractice()
|
||||
// onTap: () async => await viewModel.navigateToLearnLevel(),
|
||||
);
|
||||
CustomElevatedButton(
|
||||
height: 15,
|
||||
text: 'Practice',
|
||||
borderRadius: 12,
|
||||
backgroundColor: kcWhite,
|
||||
borderColor: kcPrimaryColor,
|
||||
foregroundColor: kcPrimaryColor,
|
||||
onTap: () async => await viewModel.navigateToLearnPractice()
|
||||
// onTap: () async => await viewModel.navigateToLearnLevel(),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,7 @@ class PracticeResponseCard extends StatelessWidget {
|
|||
);
|
||||
|
||||
Widget _buildRow() => Row(
|
||||
children: [
|
||||
_buildPlayButton(),
|
||||
_buildColumnWrapper()
|
||||
],
|
||||
children: [_buildPlayButton(), _buildColumnWrapper()],
|
||||
);
|
||||
|
||||
Widget _buildPlayButton() => ElevatedButton(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ class PracticeResultCard extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) => _buildColumnWrapper();
|
||||
|
||||
Widget _buildColumnWrapper() => SizedBox(height: 100,width: double.maxFinite,child: _buildColumn(),);
|
||||
Widget _buildColumnWrapper() => SizedBox(
|
||||
height: 100,
|
||||
width: double.maxFinite,
|
||||
child: _buildColumn(),
|
||||
);
|
||||
|
||||
Widget _buildColumn() => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -19,7 +23,8 @@ class PracticeResultCard extends StatelessWidget {
|
|||
children: _buildColumnChildren(),
|
||||
);
|
||||
|
||||
List<Widget> _buildColumnChildren() => [_buildQuestion(),verticalSpaceSmall, _buildRow()];
|
||||
List<Widget> _buildColumnChildren() =>
|
||||
[_buildQuestion(), verticalSpaceSmall, _buildRow()];
|
||||
|
||||
Widget _buildQuestion() => Text(
|
||||
'$index. ${data['question']}',
|
||||
|
|
@ -30,8 +35,11 @@ class PracticeResultCard extends StatelessWidget {
|
|||
children: _buildRowChildren(),
|
||||
);
|
||||
|
||||
List<Widget> _buildRowChildren() =>
|
||||
[_buildSampleResponseWrapper(),horizontalSpaceSmall, _buildActualResponseWrapper()];
|
||||
List<Widget> _buildRowChildren() => [
|
||||
_buildSampleResponseWrapper(),
|
||||
horizontalSpaceSmall,
|
||||
_buildActualResponseWrapper()
|
||||
];
|
||||
|
||||
Widget _buildSampleResponseWrapper() =>
|
||||
Expanded(child: _buildSampleResponse());
|
||||
|
|
|
|||
1414
test/helpers/test_helpers.mocks.dart
Normal file
1414
test/helpers/test_helpers.mocks.dart
Normal file
File diff suppressed because it is too large
Load Diff
11
test/viewmodels/course_module_viewmodel_test.dart
Normal file
11
test/viewmodels/course_module_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('CourseModuleViewModel Tests -', () {
|
||||
setUp(() => registerServices());
|
||||
tearDown(() => locator.reset());
|
||||
});
|
||||
}
|
||||
11
test/viewmodels/course_payment_viewmodel_test.dart
Normal file
11
test/viewmodels/course_payment_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('CoursePaymentViewModel Tests -', () {
|
||||
setUp(() => registerServices());
|
||||
tearDown(() => locator.reset());
|
||||
});
|
||||
}
|
||||
11
test/viewmodels/course_practice_viewmodel_test.dart
Normal file
11
test/viewmodels/course_practice_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('CoursePracticeViewModel Tests -', () {
|
||||
setUp(() => registerServices());
|
||||
tearDown(() => locator.reset());
|
||||
});
|
||||
}
|
||||
11
test/viewmodels/course_viewmodel_test.dart
Normal file
11
test/viewmodels/course_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('CourseViewModel Tests -', () {
|
||||
setUp(() => registerServices());
|
||||
tearDown(() => locator.reset());
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user